@did-btcr2/method 0.36.1 → 0.38.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 (169) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/browser.js +1149 -126
  3. package/dist/browser.mjs +1149 -126
  4. package/dist/cjs/index.js +1244 -208
  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 +12 -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 +104 -11
  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 +116 -20
  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/fee-estimator.js +8 -0
  55. package/dist/esm/core/beacon/fee-estimator.js.map +1 -1
  56. package/dist/esm/core/beacon/singleton-beacon.js +2 -2
  57. package/dist/esm/core/beacon/singleton-beacon.js.map +1 -1
  58. package/dist/esm/core/beacon/smt-beacon.js +3 -3
  59. package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
  60. package/dist/esm/core/resolver.js +8 -8
  61. package/dist/esm/core/resolver.js.map +1 -1
  62. package/dist/esm/core/updater.js +6 -6
  63. package/dist/esm/core/updater.js.map +1 -1
  64. package/dist/esm/did-btcr2.js +2 -2
  65. package/dist/esm/index.js +3 -0
  66. package/dist/esm/index.js.map +1 -1
  67. package/dist/types/core/aggregation/beacon-strategy.d.ts +8 -4
  68. package/dist/types/core/aggregation/beacon-strategy.d.ts.map +1 -1
  69. package/dist/types/core/aggregation/cohort.d.ts +88 -17
  70. package/dist/types/core/aggregation/cohort.d.ts.map +1 -1
  71. package/dist/types/core/aggregation/conditions.d.ts +28 -0
  72. package/dist/types/core/aggregation/conditions.d.ts.map +1 -1
  73. package/dist/types/core/aggregation/fallback-spend.d.ts +94 -0
  74. package/dist/types/core/aggregation/fallback-spend.d.ts.map +1 -0
  75. package/dist/types/core/aggregation/logger.d.ts +1 -1
  76. package/dist/types/core/aggregation/messages/base.d.ts +6 -0
  77. package/dist/types/core/aggregation/messages/base.d.ts.map +1 -1
  78. package/dist/types/core/aggregation/messages/bodies.d.ts +50 -2
  79. package/dist/types/core/aggregation/messages/bodies.d.ts.map +1 -1
  80. package/dist/types/core/aggregation/messages/constants.d.ts +8 -0
  81. package/dist/types/core/aggregation/messages/constants.d.ts.map +1 -1
  82. package/dist/types/core/aggregation/messages/factories.d.ts +40 -0
  83. package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -1
  84. package/dist/types/core/aggregation/messages/guards.d.ts.map +1 -1
  85. package/dist/types/core/aggregation/participant.d.ts +41 -7
  86. package/dist/types/core/aggregation/participant.d.ts.map +1 -1
  87. package/dist/types/core/aggregation/phases.d.ts +8 -2
  88. package/dist/types/core/aggregation/phases.d.ts.map +1 -1
  89. package/dist/types/core/aggregation/recovery-policy.d.ts +132 -0
  90. package/dist/types/core/aggregation/recovery-policy.d.ts.map +1 -0
  91. package/dist/types/core/aggregation/recovery-spend.d.ts +94 -0
  92. package/dist/types/core/aggregation/recovery-spend.d.ts.map +1 -0
  93. package/dist/types/core/aggregation/runner/aggregation-runner.d.ts +19 -3
  94. package/dist/types/core/aggregation/runner/aggregation-runner.d.ts.map +1 -1
  95. package/dist/types/core/aggregation/runner/events.d.ts +28 -7
  96. package/dist/types/core/aggregation/runner/events.d.ts.map +1 -1
  97. package/dist/types/core/aggregation/runner/participant-runner.d.ts +3 -3
  98. package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
  99. package/dist/types/core/aggregation/runner/service-runner.d.ts +47 -5
  100. package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -1
  101. package/dist/types/core/aggregation/service.d.ts +26 -2
  102. package/dist/types/core/aggregation/service.d.ts.map +1 -1
  103. package/dist/types/core/aggregation/signing-session.d.ts +2 -2
  104. package/dist/types/core/aggregation/signing-session.d.ts.map +1 -1
  105. package/dist/types/core/aggregation/transport/http/envelope.d.ts +3 -3
  106. package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts +1 -1
  107. package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts +1 -1
  108. package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts +1 -1
  109. package/dist/types/core/aggregation/transport/http/request-auth.d.ts +1 -1
  110. package/dist/types/core/aggregation/transport/http/server.d.ts +1 -1
  111. package/dist/types/core/aggregation/transport/http/sse-stream.d.ts +2 -2
  112. package/dist/types/core/aggregation/transport/http/sse-writer.d.ts +1 -1
  113. package/dist/types/core/aggregation/transport/in-memory.d.ts +1 -1
  114. package/dist/types/core/aggregation/transport/nostr.d.ts +4 -4
  115. package/dist/types/core/aggregation/transport/transport.d.ts +4 -4
  116. package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -1
  117. package/dist/types/core/beacon/beacon.d.ts +66 -10
  118. package/dist/types/core/beacon/beacon.d.ts.map +1 -1
  119. package/dist/types/core/beacon/cas-beacon.d.ts +2 -2
  120. package/dist/types/core/beacon/fee-estimator.d.ts +8 -0
  121. package/dist/types/core/beacon/fee-estimator.d.ts.map +1 -1
  122. package/dist/types/core/beacon/interfaces.d.ts +1 -1
  123. package/dist/types/core/resolver.d.ts +3 -3
  124. package/dist/types/core/updater.d.ts +4 -4
  125. package/dist/types/did-btcr2.d.ts +2 -2
  126. package/dist/types/index.d.ts +3 -0
  127. package/dist/types/index.d.ts.map +1 -1
  128. package/package.json +4 -4
  129. package/src/core/aggregation/beacon-strategy.ts +27 -9
  130. package/src/core/aggregation/cohort.ts +171 -35
  131. package/src/core/aggregation/conditions.ts +56 -0
  132. package/src/core/aggregation/fallback-spend.ts +223 -0
  133. package/src/core/aggregation/logger.ts +1 -1
  134. package/src/core/aggregation/messages/base.ts +6 -0
  135. package/src/core/aggregation/messages/bodies.ts +94 -1
  136. package/src/core/aggregation/messages/constants.ts +12 -0
  137. package/src/core/aggregation/messages/factories.ts +57 -0
  138. package/src/core/aggregation/messages/guards.ts +6 -0
  139. package/src/core/aggregation/participant.ts +255 -23
  140. package/src/core/aggregation/phases.ts +9 -0
  141. package/src/core/aggregation/recovery-policy.ts +237 -0
  142. package/src/core/aggregation/recovery-spend.ts +192 -0
  143. package/src/core/aggregation/runner/aggregation-runner.ts +34 -5
  144. package/src/core/aggregation/runner/events.ts +26 -7
  145. package/src/core/aggregation/runner/participant-runner.ts +75 -23
  146. package/src/core/aggregation/runner/service-runner.ts +144 -12
  147. package/src/core/aggregation/service.ts +260 -14
  148. package/src/core/aggregation/signing-session.ts +3 -3
  149. package/src/core/aggregation/transport/http/envelope.ts +3 -3
  150. package/src/core/aggregation/transport/http/inbox-buffer.ts +1 -1
  151. package/src/core/aggregation/transport/http/nonce-cache.ts +1 -1
  152. package/src/core/aggregation/transport/http/rate-limiter.ts +1 -1
  153. package/src/core/aggregation/transport/http/request-auth.ts +1 -1
  154. package/src/core/aggregation/transport/http/server.ts +1 -1
  155. package/src/core/aggregation/transport/http/sse-stream.ts +2 -2
  156. package/src/core/aggregation/transport/http/sse-writer.ts +1 -1
  157. package/src/core/aggregation/transport/in-memory.ts +1 -1
  158. package/src/core/aggregation/transport/nostr.ts +6 -6
  159. package/src/core/aggregation/transport/transport.ts +4 -4
  160. package/src/core/beacon/beacon.ts +149 -25
  161. package/src/core/beacon/cas-beacon.ts +7 -7
  162. package/src/core/beacon/fee-estimator.ts +9 -0
  163. package/src/core/beacon/interfaces.ts +1 -1
  164. package/src/core/beacon/singleton-beacon.ts +2 -2
  165. package/src/core/beacon/smt-beacon.ts +3 -3
  166. package/src/core/resolver.ts +8 -8
  167. package/src/core/updater.ts +6 -6
  168. package/src/did-btcr2.ts +2 -2
  169. package/src/index.ts +3 -0
package/dist/cjs/index.js CHANGED
@@ -55,6 +55,7 @@ __export(index_exports, {
55
55
  BeaconUtils: () => BeaconUtils,
56
56
  CASBeacon: () => CASBeacon,
57
57
  CASBeaconError: () => CASBeaconError,
58
+ CHANGE_OUTPUT_VBYTES: () => CHANGE_OUTPUT_VBYTES,
58
59
  COHORT_ADVERT: () => COHORT_ADVERT,
59
60
  COHORT_OPT_IN: () => COHORT_OPT_IN,
60
61
  COHORT_OPT_IN_ACCEPT: () => COHORT_OPT_IN_ACCEPT,
@@ -63,17 +64,23 @@ __export(index_exports, {
63
64
  DEFAULT_ADVERT_REPEAT_INTERVAL_MS: () => DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
64
65
  DEFAULT_BROADCAST_LOOKBACK_MS: () => DEFAULT_BROADCAST_LOOKBACK_MS,
65
66
  DEFAULT_CLOCK_SKEW_SEC: () => DEFAULT_CLOCK_SKEW_SEC,
67
+ DEFAULT_FEE_ESTIMATOR: () => DEFAULT_FEE_ESTIMATOR,
68
+ DEFAULT_FUNDING_MODEL: () => DEFAULT_FUNDING_MODEL,
66
69
  DEFAULT_MAX_UPDATE_SIZE_BYTES: () => DEFAULT_MAX_UPDATE_SIZE_BYTES,
67
70
  DEFAULT_NONCE_LEN_BYTES: () => DEFAULT_NONCE_LEN_BYTES,
68
71
  DEFAULT_NOSTR_RELAYS: () => DEFAULT_NOSTR_RELAYS,
72
+ DEFAULT_RECOVERY_SEQUENCE: () => DEFAULT_RECOVERY_SEQUENCE,
69
73
  DID_REGEX: () => DID_REGEX,
70
74
  DISTRIBUTE_AGGREGATED_DATA: () => DISTRIBUTE_AGGREGATED_DATA,
75
+ DUST_LIMIT_SATS: () => DUST_LIMIT_SATS2,
71
76
  DidBtcr2: () => DidBtcr2,
72
77
  DidCommTransport: () => DidCommTransport,
73
78
  DidDocument: () => DidDocument,
74
79
  DidDocumentBuilder: () => DidDocumentBuilder,
75
80
  DidVerificationMethod: () => DidVerificationMethod,
76
81
  Document: () => Document,
82
+ FALLBACK_AUTHORIZATION_REQUEST: () => FALLBACK_AUTHORIZATION_REQUEST,
83
+ FALLBACK_SIGNATURE: () => FALLBACK_SIGNATURE,
77
84
  GenesisDocument: () => GenesisDocument,
78
85
  HTTP_ENVELOPE_VERSION: () => HTTP_ENVELOPE_VERSION,
79
86
  HTTP_ROUTE: () => HTTP_ROUTE,
@@ -87,7 +94,9 @@ __export(index_exports, {
87
94
  InMemoryTransport: () => InMemoryTransport,
88
95
  InboxBuffer: () => InboxBuffer,
89
96
  KNOWN_BEACON_TYPES: () => KNOWN_BEACON_TYPES,
97
+ KNOWN_FUNDING_MODELS: () => KNOWN_FUNDING_MODELS,
90
98
  KeyPairAggregationSigner: () => KeyPairAggregationSigner,
99
+ MAX_RECOVERY_SEQUENCE: () => MAX_RECOVERY_SEQUENCE,
91
100
  NONCE_CONTRIBUTION: () => NONCE_CONTRIBUTION,
92
101
  NonceCache: () => NonceCache,
93
102
  NostrTransport: () => NostrTransport,
@@ -104,6 +113,7 @@ __export(index_exports, {
104
113
  SMTBeacon: () => SMTBeacon,
105
114
  SMTBeaconError: () => SMTBeaconError,
106
115
  SSE_EVENT: () => SSE_EVENT,
116
+ SUBMIT_NONINCLUDED: () => SUBMIT_NONINCLUDED,
107
117
  SUBMIT_UPDATE: () => SUBMIT_UPDATE,
108
118
  ServiceCohortPhase: () => ServiceCohortPhase,
109
119
  SigningSessionError: () => SigningSessionError,
@@ -112,13 +122,20 @@ __export(index_exports, {
112
122
  SingletonBeacon: () => SingletonBeacon,
113
123
  SingletonBeaconError: () => SingletonBeaconError,
114
124
  StaticFeeEstimator: () => StaticFeeEstimator,
125
+ TAPROOT_LEAF_VERSION: () => TAPROOT_LEAF_VERSION,
115
126
  TransportAdapterError: () => TransportAdapterError,
116
127
  TransportError: () => TransportError,
117
128
  TransportFactory: () => TransportFactory,
118
129
  TypedEventEmitter: () => TypedEventEmitter,
119
130
  Updater: () => Updater,
120
131
  VALIDATION_ACK: () => VALIDATION_ACK,
132
+ beaconTxVsize: () => beaconTxVsize,
121
133
  buildAggregationBeaconTx: () => buildAggregationBeaconTx,
134
+ buildFallbackLeaf: () => buildFallbackLeaf,
135
+ buildFallbackSpend: () => buildFallbackSpend,
136
+ buildRecoveryLeaves: () => buildRecoveryLeaves,
137
+ buildRecoveryScript: () => buildRecoveryScript,
138
+ buildRecoverySpend: () => buildRecoverySpend,
122
139
  buildRequestAuth: () => buildRequestAuth,
123
140
  createAggregatedNonceMessage: () => createAggregatedNonceMessage,
124
141
  createAuthorizationRequestMessage: () => createAuthorizationRequestMessage,
@@ -127,13 +144,17 @@ __export(index_exports, {
127
144
  createCohortOptInMessage: () => createCohortOptInMessage,
128
145
  createCohortReadyMessage: () => createCohortReadyMessage,
129
146
  createDistributeAggregatedDataMessage: () => createDistributeAggregatedDataMessage,
147
+ createFallbackAuthorizationRequestMessage: () => createFallbackAuthorizationRequestMessage,
148
+ createFallbackSignatureMessage: () => createFallbackSignatureMessage,
130
149
  createNonceContributionMessage: () => createNonceContributionMessage,
131
150
  createSignatureAuthorizationMessage: () => createSignatureAuthorizationMessage,
151
+ createSubmitNonIncludedMessage: () => createSubmitNonIncludedMessage,
132
152
  createSubmitUpdateMessage: () => createSubmitUpdateMessage,
133
153
  createValidationAckMessage: () => createValidationAckMessage,
134
154
  defaultReconnectBackoff: () => defaultReconnectBackoff,
135
155
  deriveSingletonAddress: () => deriveSingletonAddress,
136
156
  detectSingletonScriptKind: () => detectSingletonScriptKind,
157
+ fallbackSighash: () => fallbackSighash,
137
158
  formatSseComment: () => formatSseComment,
138
159
  formatSseEvent: () => formatSseEvent,
139
160
  getBeaconStrategy: () => getBeaconStrategy,
@@ -145,10 +166,13 @@ __export(index_exports, {
145
166
  isCohortOptInMessage: () => isCohortOptInMessage,
146
167
  isCohortReadyMessage: () => isCohortReadyMessage,
147
168
  isDistributeAggregatedDataMessage: () => isDistributeAggregatedDataMessage,
169
+ isFallbackAuthorizationRequestMessage: () => isFallbackAuthorizationRequestMessage,
170
+ isFallbackSignatureMessage: () => isFallbackSignatureMessage,
148
171
  isKeygenMessageType: () => isKeygenMessageType,
149
172
  isNonceContributionMessage: () => isNonceContributionMessage,
150
173
  isSignMessageType: () => isSignMessageType,
151
174
  isSignatureAuthorizationMessage: () => isSignatureAuthorizationMessage,
175
+ isSubmitNonIncludedMessage: () => isSubmitNonIncludedMessage,
152
176
  isSubmitUpdateMessage: () => isSubmitUpdateMessage,
153
177
  isUpdateMessageType: () => isUpdateMessageType,
154
178
  isValidationAckMessage: () => isValidationAckMessage,
@@ -157,8 +181,11 @@ __export(index_exports, {
157
181
  parseRequestAuth: () => parseRequestAuth,
158
182
  parseSseStream: () => parseSseStream,
159
183
  registerBeaconStrategy: () => registerBeaconStrategy,
184
+ resolveChangeAddress: () => resolveChangeAddress,
185
+ resolveFallbackThreshold: () => resolveFallbackThreshold,
160
186
  reviveFromWire: () => reviveFromWire,
161
187
  signEnvelope: () => signEnvelope,
188
+ tapLeafHash: () => tapLeafHash,
162
189
  validateCohortConditions: () => validateCohortConditions,
163
190
  verifyEnvelope: () => verifyEnvelope,
164
191
  verifyRequestAuth: () => verifyRequestAuth
@@ -168,7 +195,8 @@ module.exports = __toCommonJS(index_exports);
168
195
  // src/core/aggregation/service.ts
169
196
  var import_common4 = require("@did-btcr2/common");
170
197
  var import_cryptosuite = require("@did-btcr2/cryptosuite");
171
- var import_utils2 = require("@noble/hashes/utils");
198
+ var import_secp256k14 = require("@noble/curves/secp256k1.js");
199
+ var import_utils3 = require("@noble/hashes/utils");
172
200
 
173
201
  // src/core/aggregation/beacon-strategy.ts
174
202
  var import_common = require("@did-btcr2/common");
@@ -181,9 +209,12 @@ var CAS_STRATEGY = {
181
209
  getDistributePayload(cohort) {
182
210
  return { casAnnouncement: cohort.casAnnouncement };
183
211
  },
184
- validateParticipantView({ participantDid, expectedHash, body }) {
212
+ validateParticipantView({ participantDid, included, expectedHash, body }) {
185
213
  const casAnnouncement = body.casAnnouncement;
186
214
  if (!casAnnouncement) return { matches: false };
215
+ if (!included) {
216
+ return { matches: !(participantDid in casAnnouncement), casAnnouncement };
217
+ }
187
218
  return {
188
219
  matches: casAnnouncement[participantDid] === expectedHash,
189
220
  casAnnouncement
@@ -199,15 +230,20 @@ var SMT_STRATEGY = {
199
230
  const proof = cohort.smtProofs?.get(participantDid);
200
231
  return { smtProof: proof };
201
232
  },
202
- validateParticipantView({ participantDid, submittedUpdate, body }) {
233
+ validateParticipantView({ participantDid, included, submittedUpdate, body }) {
203
234
  const smtProof = body.smtProof;
204
- if (!smtProof?.updateId || !smtProof?.nonce) return { matches: false };
235
+ const index = (0, import_smt.didToIndex)(participantDid);
236
+ if (!included) {
237
+ if (!smtProof?.nonce || smtProof?.updateId) return { matches: false, smtProof };
238
+ const candidateHash2 = (0, import_smt.blockHash)((0, import_smt.blockHash)((0, import_smt.base64UrlToHash)(smtProof.nonce)));
239
+ return { matches: (0, import_smt.verifySerializedProof)(smtProof, index, candidateHash2), smtProof };
240
+ }
241
+ if (!smtProof?.updateId || !smtProof?.nonce || !submittedUpdate) return { matches: false, smtProof };
205
242
  const canonicalBytes = new TextEncoder().encode((0, import_common.canonicalize)(submittedUpdate));
206
243
  const expectedUpdateId = (0, import_smt.hashToBase64Url)((0, import_smt.blockHash)(canonicalBytes));
207
244
  if (smtProof.updateId !== expectedUpdateId) {
208
245
  return { matches: false, smtProof };
209
246
  }
210
- const index = (0, import_smt.didToIndex)(participantDid);
211
247
  const candidateHash = (0, import_smt.blockHash)((0, import_smt.blockHash)((0, import_smt.base64UrlToHash)(smtProof.nonce)), (0, import_smt.base64UrlToHash)(smtProof.updateId));
212
248
  return {
213
249
  matches: (0, import_smt.verifySerializedProof)(smtProof, index, candidateHash),
@@ -230,10 +266,10 @@ function getBeaconStrategy(type) {
230
266
  var import_bitcoin = require("@did-btcr2/bitcoin");
231
267
  var import_common3 = require("@did-btcr2/common");
232
268
  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");
269
+ var import_secp256k12 = require("@noble/curves/secp256k1.js");
270
+ var import_utils2 = require("@noble/hashes/utils");
271
+ var import_btc_signer2 = require("@scure/btc-signer");
272
+ var import_musig22 = require("@scure/btc-signer/musig2");
237
273
 
238
274
  // src/core/aggregation/errors.ts
239
275
  var import_common2 = require("@did-btcr2/common");
@@ -258,6 +294,105 @@ var SigningSessionError = class extends import_common2.MethodError {
258
294
  }
259
295
  };
260
296
 
297
+ // src/core/aggregation/recovery-policy.ts
298
+ var import_secp256k1 = require("@noble/curves/secp256k1.js");
299
+ var import_utils = require("@noble/hashes/utils");
300
+ var import_btc_signer = require("@scure/btc-signer");
301
+ var import_musig2 = require("@scure/btc-signer/musig2");
302
+ var DEFAULT_FUNDING_MODEL = "operator-funded";
303
+ var DEFAULT_RECOVERY_SEQUENCE = 144;
304
+ var MAX_RECOVERY_SEQUENCE = 65535;
305
+ var TAPROOT_LEAF_VERSION = 192;
306
+ function compactSize(n) {
307
+ if (n < 253) return new Uint8Array([n]);
308
+ if (n <= 65535) return new Uint8Array([253, n & 255, n >> 8 & 255]);
309
+ return new Uint8Array([254, n & 255, n >> 8 & 255, n >> 16 & 255, n >> 24 & 255]);
310
+ }
311
+ function tapLeafHash(script, leafVersion = TAPROOT_LEAF_VERSION) {
312
+ return import_secp256k1.schnorr.utils.taggedHash(
313
+ "TapLeaf",
314
+ (0, import_utils.concatBytes)(new Uint8Array([leafVersion]), compactSize(script.length), script)
315
+ );
316
+ }
317
+ function resolveFallbackThreshold(advertised, n) {
318
+ return advertised ?? Math.max(1, n - 1);
319
+ }
320
+ function assertFallbackParams({ cohortKeys, fallbackThreshold }) {
321
+ const n = cohortKeys.length;
322
+ if (n === 0) {
323
+ throw new AggregationCohortError(
324
+ "Cannot build fallback leaf: no cohort keys.",
325
+ "NO_COHORT_KEYS"
326
+ );
327
+ }
328
+ for (const key of cohortKeys) {
329
+ if (key.length !== 33) {
330
+ throw new AggregationCohortError(
331
+ `Cohort key must be a 33-byte compressed public key, got ${key.length} bytes.`,
332
+ "INVALID_COHORT_KEY",
333
+ { length: key.length }
334
+ );
335
+ }
336
+ }
337
+ if (!Number.isInteger(fallbackThreshold) || fallbackThreshold < 1 || fallbackThreshold > n) {
338
+ throw new AggregationCohortError(
339
+ `Fallback threshold must be an integer in [1, ${n}] (k-of-n), got ${fallbackThreshold}.`,
340
+ "INVALID_FALLBACK_THRESHOLD",
341
+ { fallbackThreshold, n }
342
+ );
343
+ }
344
+ }
345
+ function buildFallbackLeaf(params) {
346
+ assertFallbackParams(params);
347
+ const xOnlyKeys = (0, import_musig2.sortKeys)(params.cohortKeys).map((k) => k.slice(1));
348
+ return (0, import_btc_signer.p2tr_ms)(params.fallbackThreshold, xOnlyKeys).script;
349
+ }
350
+ function assertRecoveryParams({ recoveryKey, recoverySequence }) {
351
+ if (recoveryKey.length !== 32) {
352
+ throw new AggregationCohortError(
353
+ `Recovery key must be a 32-byte x-only public key, got ${recoveryKey.length} bytes.`,
354
+ "INVALID_RECOVERY_KEY"
355
+ );
356
+ }
357
+ if (!Number.isInteger(recoverySequence) || recoverySequence < 1 || recoverySequence > MAX_RECOVERY_SEQUENCE) {
358
+ throw new AggregationCohortError(
359
+ `Recovery sequence must be a block-based BIP-68 relative timelock in [1, ${MAX_RECOVERY_SEQUENCE}], got ${recoverySequence}.`,
360
+ "INVALID_RECOVERY_SEQUENCE"
361
+ );
362
+ }
363
+ }
364
+ function buildRecoveryScript(params) {
365
+ assertRecoveryParams(params);
366
+ return import_btc_signer.Script.encode([
367
+ params.recoverySequence,
368
+ "CHECKSEQUENCEVERIFY",
369
+ "DROP",
370
+ params.recoveryKey,
371
+ "CHECKSIG"
372
+ ]);
373
+ }
374
+ function buildRecoveryLeaves(fundingModel, params) {
375
+ switch (fundingModel) {
376
+ case "operator-funded":
377
+ return [
378
+ { script: buildFallbackLeaf(params) },
379
+ { script: buildRecoveryScript(params) }
380
+ ];
381
+ case "participant-funded":
382
+ throw new AggregationCohortError(
383
+ "Funding model 'participant-funded' is reserved and not yet implemented.",
384
+ "UNSUPPORTED_FUNDING_MODEL",
385
+ { fundingModel }
386
+ );
387
+ default:
388
+ throw new AggregationCohortError(
389
+ `Unknown funding model: ${fundingModel}.`,
390
+ "UNKNOWN_FUNDING_MODEL",
391
+ { fundingModel }
392
+ );
393
+ }
394
+ }
395
+
261
396
  // src/core/aggregation/cohort.ts
262
397
  var AggregationCohort = class {
263
398
  /** Unique identifier for the cohort. */
@@ -273,8 +408,8 @@ var AggregationCohort = class {
273
408
  /** List of participant DIDs that have been accepted into the cohort. */
274
409
  participants = [];
275
410
  /**
276
- * Mapping from participant DID their compressed secp256k1 public key.
277
- * Distinct from {@link cohortKeys} (which is sorted per BIP-327) this lets
411
+ * Mapping from participant DID to their compressed secp256k1 public key.
412
+ * Distinct from {@link cohortKeys} (which is sorted per BIP-327): this lets
278
413
  * callers look up a participant's key without knowing their position in the
279
414
  * sorted array. Populated by the service at `acceptParticipant` time.
280
415
  */
@@ -282,16 +417,41 @@ var AggregationCohort = class {
282
417
  /** Sorted list of cohort participants' compressed public keys. */
283
418
  #cohortKeys = [];
284
419
  /**
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.
420
+ * BIP-341 TapTweak scalar: `taggedHash("TapTweak", internalPubkey || tapMerkleRoot)`.
421
+ * The beacon output is an internal key (the MuSig2 aggregate) plus a script
422
+ * tree (the recovery leaves), so the tweak commits to the tree's Merkle root.
423
+ * The MuSig2 signing session applies this as an x-only tweak; a value that does
424
+ * not match the root the funded address was derived from silently yields an
425
+ * invalid key-path signature.
288
426
  */
289
427
  tapTweak = new Uint8Array();
290
- /** The n-of-n MuSig2 Taproot beacon address. */
428
+ /** The n-of-n MuSig2 aggregate internal key, x-only (32 bytes), set by computeBeaconAddress(). */
429
+ internalKey = new Uint8Array();
430
+ /** BIP-341 Taproot Merkle root of the recovery script tree, set by computeBeaconAddress(). */
431
+ tapMerkleRoot = new Uint8Array();
432
+ /** Operator recovery key, x-only (32 bytes). Used to build the CSV recovery leaf. */
433
+ recoveryKey;
434
+ /** Relative-timelock (BIP-68 nSequence) before the recovery leaf is spendable. */
435
+ recoverySequence;
436
+ /** Funding model governing the recovery leaves (default 'operator-funded'). */
437
+ fundingModel;
438
+ /**
439
+ * Advertised k of the k-of-n fallback leaf, or undefined to default to n-1 at
440
+ * address computation. Read the resolved value via {@link effectiveFallbackThreshold}.
441
+ */
442
+ fallbackThreshold;
443
+ /** The Taproot beacon address: key path is the MuSig2 aggregate, script path is fallback + recovery. */
291
444
  beaconAddress = "";
292
445
  /** Pending DID updates submitted by participants, keyed by DID. */
293
446
  pendingUpdates = /* @__PURE__ */ new Map();
294
- /** CAS Beacon Announcement Map (DID → updateHash), set by buildCASAnnouncement(). */
447
+ /**
448
+ * Participant DIDs that explicitly declined to submit an update this round
449
+ * (cooperative non-inclusion). A decliner is absent from the CAS Announcement
450
+ * Map and carries a non-inclusion leaf in the SMT, yet still signs. Kept
451
+ * disjoint from {@link pendingUpdates} so CAS correctness holds by construction.
452
+ */
453
+ nonIncluded = /* @__PURE__ */ new Set();
454
+ /** CAS Beacon Announcement Map (DID to updateHash), set by buildCASAnnouncement(). */
295
455
  casAnnouncement;
296
456
  /** Per-participant SMT proofs, set by buildSMTTree(). */
297
457
  smtProofs;
@@ -301,23 +461,45 @@ var AggregationCohort = class {
301
461
  validationAcks = /* @__PURE__ */ new Set();
302
462
  /** Set of participant DIDs that have rejected the aggregated data. */
303
463
  validationRejections = /* @__PURE__ */ new Set();
304
- constructor({ id, minParticipants, serviceDid, network, beaconType }) {
464
+ constructor({ id, minParticipants, serviceDid, network, beaconType, recoveryKey, recoverySequence, fundingModel, fallbackThreshold }) {
305
465
  this.id = id || crypto.randomUUID();
306
466
  this.minParticipants = minParticipants ?? 2;
307
467
  this.serviceDid = serviceDid || "";
308
468
  this.network = network;
309
469
  this.beaconType = beaconType || "CASBeacon";
470
+ this.recoveryKey = recoveryKey;
471
+ this.recoverySequence = recoverySequence;
472
+ this.fundingModel = fundingModel ?? DEFAULT_FUNDING_MODEL;
473
+ this.fallbackThreshold = fallbackThreshold;
310
474
  }
311
475
  /** Sorted cohort keys (sorted on assignment per BIP-327). */
312
476
  get cohortKeys() {
313
477
  return this.#cohortKeys;
314
478
  }
315
479
  set cohortKeys(keys) {
316
- this.#cohortKeys = (0, import_musig2.sortKeys)(keys);
480
+ this.#cohortKeys = (0, import_musig22.sortKeys)(keys);
481
+ }
482
+ /**
483
+ * The resolved k of the k-of-n fallback leaf: the advertised
484
+ * {@link fallbackThreshold}, or n-1 (floored at 1) when unadvertised, where n is
485
+ * the current cohort size. This is the value the beacon address commits to and
486
+ * the spend builders must reproduce. Returns 0 before any cohort keys are set.
487
+ */
488
+ get effectiveFallbackThreshold() {
489
+ if (this.#cohortKeys.length === 0) return 0;
490
+ return resolveFallbackThreshold(this.fallbackThreshold, this.#cohortKeys.length);
317
491
  }
318
492
  /**
319
- * Computes the n-of-n MuSig2 Taproot beacon address from cohort keys.
320
- * Sets `tapTweak` to the BIP-341 key-path-only tweak.
493
+ * Computes the Taproot beacon address from the cohort keys and recovery params.
494
+ *
495
+ * The output's key path is the n-of-n MuSig2 aggregate of the cohort keys; its
496
+ * script path is the recovery tree (a CSV recovery leaf so a missing signer
497
+ * cannot permanently lock the UTXO). Sets `internalKey`, `tapMerkleRoot`, and
498
+ * the `tapTweak` the MuSig2 session must apply for the key-path spend.
499
+ *
500
+ * The tweak is derived from the Merkle root the address was built with (read
501
+ * back from the payment), never recomputed by hand, so the MuSig2 key-path
502
+ * signature is guaranteed to validate against the funded address.
321
503
  */
322
504
  computeBeaconAddress() {
323
505
  if (this.#cohortKeys.length === 0) {
@@ -327,10 +509,25 @@ var AggregationCohort = class {
327
509
  { cohortId: this.id }
328
510
  );
329
511
  }
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);
512
+ if (!this.recoveryKey || this.recoveryKey.length === 0 || this.recoverySequence === void 0) {
513
+ throw new AggregationCohortError(
514
+ "Cannot compute beacon address: missing recovery key or sequence.",
515
+ "NO_RECOVERY_PARAMS",
516
+ { cohortId: this.id }
517
+ );
518
+ }
519
+ const keyAggContext = (0, import_musig22.keyAggregate)(this.#cohortKeys);
520
+ const aggPubkey = (0, import_musig22.keyAggExport)(keyAggContext);
521
+ const leaves = buildRecoveryLeaves(this.fundingModel, {
522
+ recoveryKey: this.recoveryKey,
523
+ recoverySequence: this.recoverySequence,
524
+ cohortKeys: this.#cohortKeys,
525
+ fallbackThreshold: resolveFallbackThreshold(this.fallbackThreshold, this.#cohortKeys.length)
526
+ });
527
+ const payment = (0, import_btc_signer2.p2tr)(aggPubkey, leaves, (0, import_bitcoin.getNetwork)(this.network), true);
528
+ this.internalKey = aggPubkey;
529
+ this.tapMerkleRoot = payment.tapMerkleRoot;
530
+ this.tapTweak = import_secp256k12.schnorr.utils.taggedHash("TapTweak", (0, import_utils2.concatBytes)(aggPubkey, payment.tapMerkleRoot));
334
531
  if (!payment.address) {
335
532
  throw new AggregationCohortError(
336
533
  "Failed to compute Taproot address",
@@ -354,7 +551,7 @@ var AggregationCohort = class {
354
551
  { cohortId: this.id, participantPkHex }
355
552
  );
356
553
  }
357
- this.cohortKeys = cohortKeysHex.map((k) => (0, import_utils.hexToBytes)(k));
554
+ this.cohortKeys = cohortKeysHex.map((k) => (0, import_utils2.hexToBytes)(k));
358
555
  const computed = this.computeBeaconAddress();
359
556
  if (computed !== expectedBeaconAddress) {
360
557
  throw new AggregationCohortError(
@@ -386,20 +583,54 @@ var AggregationCohort = class {
386
583
  }
387
584
  this.pendingUpdates.set(participantDid, signedUpdate);
388
585
  }
586
+ /**
587
+ * Record that a participant declined to submit an update this round
588
+ * (cooperative non-inclusion). The member stays in the cohort and still signs.
589
+ */
590
+ addNonInclusion(participantDid) {
591
+ if (!this.participants.includes(participantDid)) {
592
+ throw new AggregationCohortError(
593
+ `Participant ${participantDid} is not in cohort ${this.id}.`,
594
+ "UNKNOWN_PARTICIPANT",
595
+ { cohortId: this.id, participantDid }
596
+ );
597
+ }
598
+ if (this.pendingUpdates.has(participantDid)) {
599
+ throw new AggregationCohortError(
600
+ `Participant ${participantDid} already submitted an update; cannot also decline.`,
601
+ "CONFLICTING_RESPONSE",
602
+ { cohortId: this.id, participantDid }
603
+ );
604
+ }
605
+ this.nonIncluded.add(participantDid);
606
+ }
389
607
  hasAllUpdates() {
390
608
  return this.pendingUpdates.size === this.participants.length;
391
609
  }
610
+ /**
611
+ * True when every participant has responded for this round, either with an
612
+ * update or with an explicit non-inclusion. This is the aggregation gate when
613
+ * non-inclusion is in play; it generalizes {@link hasAllUpdates} the same way
614
+ * {@link hasAllValidationResponses} generalizes a unanimous ack.
615
+ */
616
+ hasAllResponses() {
617
+ return this.pendingUpdates.size + this.nonIncluded.size === this.participants.length;
618
+ }
392
619
  /**
393
620
  * Builds a CAS Announcement Map from collected updates.
394
- * Maps each participant DID base64url canonical hash of their signed update.
621
+ * Maps each participant DID to base64url canonical hash of their signed update.
395
622
  * Computes signal bytes as SHA-256 of canonicalized announcement.
623
+ *
624
+ * Members who declined (cooperative non-inclusion) are naturally absent from
625
+ * the map: the body iterates {@link pendingUpdates}, which never holds a
626
+ * decliner. Absence from the map is exactly the CAS non-inclusion signal.
396
627
  */
397
628
  buildCASAnnouncement() {
398
- if (!this.hasAllUpdates()) {
629
+ if (!this.hasAllResponses()) {
399
630
  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 }
631
+ "Cannot build CAS Announcement: not all participants have responded.",
632
+ "INCOMPLETE_RESPONSES",
633
+ { cohortId: this.id, updates: this.pendingUpdates.size, declined: this.nonIncluded.size, total: this.participants.length }
403
634
  );
404
635
  }
405
636
  const announcement = {};
@@ -411,31 +642,41 @@ var AggregationCohort = class {
411
642
  return announcement;
412
643
  }
413
644
  /**
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.
645
+ * Builds an SMT tree with one leaf per participant.
646
+ *
647
+ * A member who submitted an update gets an inclusion leaf
648
+ * (SHA-256(SHA-256(nonce) || SHA-256(update))); a member who declined gets a
649
+ * non-inclusion leaf (SHA-256(SHA-256(nonce)), the `signedUpdate` entry field
650
+ * omitted). The cohort mints each member's nonce and returns it inside that
651
+ * member's serialized proof, so a decliner can self-validate its own
652
+ * non-inclusion slot and the resolver can recompute the leaf. Stores
653
+ * per-participant proofs and the SMT root as signalBytes.
417
654
  */
418
655
  buildSMTTree() {
419
- if (!this.hasAllUpdates()) {
656
+ if (!this.hasAllResponses()) {
420
657
  throw new AggregationCohortError(
421
- "Cannot build SMT tree: not all updates collected.",
422
- "INCOMPLETE_UPDATES",
423
- { cohortId: this.id }
658
+ "Cannot build SMT tree: not all participants have responded.",
659
+ "INCOMPLETE_RESPONSES",
660
+ { cohortId: this.id, updates: this.pendingUpdates.size, declined: this.nonIncluded.size, total: this.participants.length }
424
661
  );
425
662
  }
426
663
  const tree = new import_smt2.BTCR2MerkleTree();
427
664
  const entries = [];
428
665
  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 });
666
+ for (const did of this.participants) {
667
+ const nonce = (0, import_utils2.randomBytes)(32);
668
+ const signedUpdate = this.pendingUpdates.get(did);
669
+ if (signedUpdate) {
670
+ entries.push({ did, nonce, signedUpdate: encoder.encode((0, import_common3.canonicalize)(signedUpdate)) });
671
+ } else {
672
+ entries.push({ did, nonce });
673
+ }
433
674
  }
434
675
  tree.addEntries(entries);
435
676
  tree.finalize();
436
677
  this.signalBytes = tree.rootHash;
437
678
  this.smtProofs = /* @__PURE__ */ new Map();
438
- for (const [did] of this.pendingUpdates) {
679
+ for (const did of this.participants) {
439
680
  this.smtProofs.set(did, tree.proof(did));
440
681
  }
441
682
  return this.smtProofs;
@@ -461,7 +702,7 @@ var AggregationCohort = class {
461
702
  return this.validationAcks.size + this.validationRejections.size === this.participants.length;
462
703
  }
463
704
  /**
464
- * True when all participants approved. Note: differs from {@link hasAllValidationResponses}
705
+ * True when all participants approved. Note: differs from {@link hasAllValidationResponses},
465
706
  * this returns false if any participant rejected, even if all responses are in.
466
707
  */
467
708
  isFullyValidated() {
@@ -471,6 +712,7 @@ var AggregationCohort = class {
471
712
 
472
713
  // src/core/aggregation/conditions.ts
473
714
  var KNOWN_BEACON_TYPES = ["CASBeacon", "SMTBeacon"];
715
+ var KNOWN_FUNDING_MODELS = ["operator-funded", "participant-funded"];
474
716
  function checkPair(problems, label, min, max) {
475
717
  if (min !== void 0 && (!Number.isInteger(min) || min < 0)) {
476
718
  problems.push(`min${label} must be an integer >= 0`);
@@ -514,11 +756,130 @@ function validateCohortConditions(c) {
514
756
  if (c.pendingUpdateTrigger !== void 0 && (!Number.isInteger(c.pendingUpdateTrigger) || c.pendingUpdateTrigger < 1)) {
515
757
  problems.push("pendingUpdateTrigger must be an integer >= 1");
516
758
  }
759
+ if (c.fallbackThreshold !== void 0) {
760
+ if (!Number.isInteger(c.fallbackThreshold) || c.fallbackThreshold < 1) {
761
+ problems.push("fallbackThreshold must be an integer >= 1");
762
+ } else if (c.maxParticipants !== void 0 && Number.isInteger(c.maxParticipants) && c.fallbackThreshold > c.maxParticipants) {
763
+ problems.push("fallbackThreshold must be <= maxParticipants");
764
+ }
765
+ }
517
766
  checkCost(problems, "costOfEnrollment", c.costOfEnrollment);
518
767
  checkCost(problems, "costPerAnnouncement", c.costPerAnnouncement);
768
+ if (typeof c.recoveryKey !== "string" || !/^[0-9a-fA-F]{64}$/.test(c.recoveryKey)) {
769
+ problems.push("recoveryKey must be a 64-character hex string (x-only public key)");
770
+ }
771
+ if (!Number.isInteger(c.recoverySequence) || c.recoverySequence < 1 || c.recoverySequence > MAX_RECOVERY_SEQUENCE) {
772
+ problems.push(`recoverySequence must be a block-based BIP-68 relative timelock in [1, ${MAX_RECOVERY_SEQUENCE}]`);
773
+ }
774
+ if (c.fundingModel !== void 0 && !KNOWN_FUNDING_MODELS.includes(c.fundingModel)) {
775
+ problems.push(`fundingModel must be one of ${KNOWN_FUNDING_MODELS.join(", ")}`);
776
+ }
519
777
  return problems;
520
778
  }
521
779
 
780
+ // src/core/aggregation/fallback-spend.ts
781
+ var import_bitcoin2 = require("@did-btcr2/bitcoin");
782
+ var import_secp256k13 = require("@noble/curves/secp256k1.js");
783
+ var import_btc_signer3 = require("@scure/btc-signer");
784
+ var import_musig23 = require("@scure/btc-signer/musig2");
785
+ function bytesEqual(a, b) {
786
+ return a.length === b.length && a.every((x, i) => x === b[i]);
787
+ }
788
+ function fallbackSighash(tx, inputIndex, prevOutScript, prevOutValue, fallbackLeafScript) {
789
+ return tx.preimageWitnessV1(
790
+ inputIndex,
791
+ [prevOutScript],
792
+ import_btc_signer3.SigHash.DEFAULT,
793
+ [prevOutValue],
794
+ void 0,
795
+ fallbackLeafScript,
796
+ TAPROOT_LEAF_VERSION
797
+ );
798
+ }
799
+ function buildFallbackSpend(params) {
800
+ const {
801
+ pendingTx,
802
+ cohortKeys,
803
+ recoveryKey,
804
+ recoverySequence,
805
+ fundingModel,
806
+ network,
807
+ prevOutScript,
808
+ prevOutValue,
809
+ signatures
810
+ } = params;
811
+ const inputIndex = params.inputIndex ?? 0;
812
+ if (cohortKeys.length === 0) {
813
+ throw new AggregationCohortError(
814
+ "Cannot build fallback spend: no cohort keys.",
815
+ "NO_COHORT_KEYS"
816
+ );
817
+ }
818
+ const fallbackThreshold = resolveFallbackThreshold(params.fallbackThreshold, cohortKeys.length);
819
+ const internalKey = (0, import_musig23.keyAggExport)((0, import_musig23.keyAggregate)((0, import_musig23.sortKeys)(cohortKeys)));
820
+ const leaves = buildRecoveryLeaves(fundingModel ?? DEFAULT_FUNDING_MODEL, {
821
+ recoveryKey,
822
+ recoverySequence,
823
+ cohortKeys,
824
+ fallbackThreshold
825
+ });
826
+ const net = (0, import_bitcoin2.getNetwork)(network);
827
+ const payment = (0, import_btc_signer3.p2tr)(internalKey, leaves, net, true);
828
+ if (!bytesEqual(payment.script, prevOutScript)) {
829
+ throw new AggregationCohortError(
830
+ "Reconstructed beacon output script does not match the spent prevout script.",
831
+ "PREVOUT_SCRIPT_MISMATCH"
832
+ );
833
+ }
834
+ const fallbackLeaf = buildFallbackLeaf({ cohortKeys, fallbackThreshold });
835
+ const leafEntry = payment.tapLeafScript?.find(([, scriptVer]) => {
836
+ const script = scriptVer.slice(0, scriptVer.length - 1);
837
+ return bytesEqual(script, fallbackLeaf);
838
+ });
839
+ if (!leafEntry) {
840
+ throw new AggregationCohortError(
841
+ "Could not locate the fallback leaf in the reconstructed beacon output.",
842
+ "FALLBACK_LEAF_NOT_FOUND"
843
+ );
844
+ }
845
+ const cohortXOnly = (0, import_musig23.sortKeys)(cohortKeys).map((k) => k.slice(1));
846
+ const tx = pendingTx;
847
+ const sighash = fallbackSighash(tx, inputIndex, prevOutScript, prevOutValue, fallbackLeaf);
848
+ const leafHash = tapLeafHash(fallbackLeaf);
849
+ const accepted = /* @__PURE__ */ new Map();
850
+ for (const { pubKey, signature } of signatures) {
851
+ if (pubKey.length !== 32 || signature.length !== 64) continue;
852
+ const isCohortKey = cohortXOnly.some((k) => bytesEqual(k, pubKey));
853
+ if (!isCohortKey) continue;
854
+ const hex = Array.from(pubKey, (b) => b.toString(16).padStart(2, "0")).join("");
855
+ if (accepted.has(hex)) continue;
856
+ let ok = false;
857
+ try {
858
+ ok = import_secp256k13.schnorr.verify(signature, sighash, pubKey);
859
+ } catch {
860
+ ok = false;
861
+ }
862
+ if (ok) accepted.set(hex, signature);
863
+ }
864
+ if (accepted.size < fallbackThreshold) {
865
+ throw new AggregationCohortError(
866
+ `Not enough valid fallback signatures: have ${accepted.size}, need ${fallbackThreshold}.`,
867
+ "NOT_ENOUGH_FALLBACK_SIGNATURES",
868
+ { have: accepted.size, need: fallbackThreshold }
869
+ );
870
+ }
871
+ const chosen = cohortXOnly.filter((k) => accepted.has(Array.from(k, (b) => b.toString(16).padStart(2, "0")).join(""))).slice(0, fallbackThreshold);
872
+ tx.updateInput(inputIndex, {
873
+ tapLeafScript: [leafEntry],
874
+ tapScriptSig: chosen.map((pubKey) => {
875
+ const hex = Array.from(pubKey, (b) => b.toString(16).padStart(2, "0")).join("");
876
+ return [{ pubKey, leafHash }, accepted.get(hex)];
877
+ })
878
+ });
879
+ tx.finalize();
880
+ return tx;
881
+ }
882
+
522
883
  // src/core/aggregation/messages/base.ts
523
884
  var AGGREGATION_WIRE_VERSION = 1;
524
885
  var BaseMessage = class {
@@ -556,12 +917,15 @@ var COHORT_OPT_IN = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in`;
556
917
  var COHORT_OPT_IN_ACCEPT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in_accept`;
557
918
  var COHORT_READY = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_ready`;
558
919
  var SUBMIT_UPDATE = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_update`;
920
+ var SUBMIT_NONINCLUDED = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_nonincluded`;
559
921
  var DISTRIBUTE_AGGREGATED_DATA = `${AGGREGATION_MESSAGE_PREFIX}/update/distribute_aggregated_data`;
560
922
  var VALIDATION_ACK = `${AGGREGATION_MESSAGE_PREFIX}/update/validation_ack`;
561
923
  var AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/authorization_request`;
562
924
  var NONCE_CONTRIBUTION = `${AGGREGATION_MESSAGE_PREFIX}/sign/nonce_contribution`;
563
925
  var AGGREGATED_NONCE = `${AGGREGATION_MESSAGE_PREFIX}/sign/aggregated_nonce`;
564
926
  var SIGNATURE_AUTHORIZATION = `${AGGREGATION_MESSAGE_PREFIX}/sign/signature_authorization`;
927
+ var FALLBACK_AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/fallback_authorization_request`;
928
+ var FALLBACK_SIGNATURE = `${AGGREGATION_MESSAGE_PREFIX}/sign/fallback_signature`;
565
929
 
566
930
  // src/core/aggregation/messages/factories.ts
567
931
  function createCohortAdvertMessage(fields) {
@@ -584,6 +948,10 @@ function createSubmitUpdateMessage(fields) {
584
948
  const { from: from2, to, ...body } = fields;
585
949
  return new BaseMessage({ type: SUBMIT_UPDATE, from: from2, to, body });
586
950
  }
951
+ function createSubmitNonIncludedMessage(fields) {
952
+ const { from: from2, to, ...body } = fields;
953
+ return new BaseMessage({ type: SUBMIT_NONINCLUDED, from: from2, to, body });
954
+ }
587
955
  function createDistributeAggregatedDataMessage(fields) {
588
956
  const { from: from2, to, ...body } = fields;
589
957
  return new BaseMessage({ type: DISTRIBUTE_AGGREGATED_DATA, from: from2, to, body });
@@ -608,6 +976,14 @@ function createSignatureAuthorizationMessage(fields) {
608
976
  const { from: from2, to, ...body } = fields;
609
977
  return new BaseMessage({ type: SIGNATURE_AUTHORIZATION, from: from2, to, body });
610
978
  }
979
+ function createFallbackAuthorizationRequestMessage(fields) {
980
+ const { from: from2, to, ...body } = fields;
981
+ return new BaseMessage({ type: FALLBACK_AUTHORIZATION_REQUEST, from: from2, to, body });
982
+ }
983
+ function createFallbackSignatureMessage(fields) {
984
+ const { from: from2, to, ...body } = fields;
985
+ return new BaseMessage({ type: FALLBACK_SIGNATURE, from: from2, to, body });
986
+ }
611
987
 
612
988
  // src/core/aggregation/phases.ts
613
989
  var ServiceCohortPhase = /* @__PURE__ */ ((ServiceCohortPhase2) => {
@@ -621,6 +997,7 @@ var ServiceCohortPhase = /* @__PURE__ */ ((ServiceCohortPhase2) => {
621
997
  ServiceCohortPhase2["SigningStarted"] = "SigningStarted";
622
998
  ServiceCohortPhase2["NoncesCollected"] = "NoncesCollected";
623
999
  ServiceCohortPhase2["AwaitingPartialSigs"] = "AwaitingPartialSigs";
1000
+ ServiceCohortPhase2["FallbackRequested"] = "FallbackRequested";
624
1001
  ServiceCohortPhase2["Complete"] = "Complete";
625
1002
  ServiceCohortPhase2["Failed"] = "Failed";
626
1003
  return ServiceCohortPhase2;
@@ -630,11 +1007,13 @@ var ParticipantCohortPhase = /* @__PURE__ */ ((ParticipantCohortPhase2) => {
630
1007
  ParticipantCohortPhase2["OptedIn"] = "OptedIn";
631
1008
  ParticipantCohortPhase2["CohortReady"] = "CohortReady";
632
1009
  ParticipantCohortPhase2["UpdateSubmitted"] = "UpdateSubmitted";
1010
+ ParticipantCohortPhase2["NonIncluded"] = "NonIncluded";
633
1011
  ParticipantCohortPhase2["AwaitingValidation"] = "AwaitingValidation";
634
1012
  ParticipantCohortPhase2["ValidationSent"] = "ValidationSent";
635
1013
  ParticipantCohortPhase2["AwaitingSigning"] = "AwaitingSigning";
636
1014
  ParticipantCohortPhase2["NonceSent"] = "NonceSent";
637
1015
  ParticipantCohortPhase2["AwaitingPartialSig"] = "AwaitingPartialSig";
1016
+ ParticipantCohortPhase2["AwaitingFallbackSig"] = "AwaitingFallbackSig";
638
1017
  ParticipantCohortPhase2["Complete"] = "Complete";
639
1018
  ParticipantCohortPhase2["Failed"] = "Failed";
640
1019
  return ParticipantCohortPhase2;
@@ -651,7 +1030,7 @@ var SigningSessionPhase = /* @__PURE__ */ ((SigningSessionPhase2) => {
651
1030
 
652
1031
  // src/core/aggregation/signing-session.ts
653
1032
  var import_keypair = require("@did-btcr2/keypair");
654
- var import_btc_signer2 = require("@scure/btc-signer");
1033
+ var import_btc_signer4 = require("@scure/btc-signer");
655
1034
  var musig2 = __toESM(require("@scure/btc-signer/musig2"), 1);
656
1035
  var BeaconSigningSession = class {
657
1036
  /** Unique identifier for this signing session. */
@@ -664,11 +1043,11 @@ var BeaconSigningSession = class {
664
1043
  prevOutScripts;
665
1044
  /** Previous output values for Taproot sighash computation. */
666
1045
  prevOutValues;
667
- /** Map of participant publicKey-hex public nonce contribution. */
1046
+ /** Map of participant publicKey-hex to public nonce contribution. */
668
1047
  nonceContributions = /* @__PURE__ */ new Map();
669
1048
  /** Aggregated MuSig2 nonce (66 bytes). */
670
1049
  aggregatedNonce;
671
- /** Map of participant DID partial signature. */
1050
+ /** Map of participant DID to partial signature. */
672
1051
  partialSignatures = /* @__PURE__ */ new Map();
673
1052
  /** Final 64-byte Schnorr signature. */
674
1053
  signature;
@@ -703,7 +1082,7 @@ var BeaconSigningSession = class {
703
1082
  return this.pendingTx.preimageWitnessV1(
704
1083
  0,
705
1084
  this.prevOutScripts,
706
- import_btc_signer2.SigHash.DEFAULT,
1085
+ import_btc_signer4.SigHash.DEFAULT,
707
1086
  this.prevOutValues
708
1087
  );
709
1088
  }
@@ -851,7 +1230,7 @@ var BeaconSigningSession = class {
851
1230
  throw new SigningSessionError("Aggregated nonce not available.", "MISSING_AGGREGATED_NONCE");
852
1231
  }
853
1232
  if (!this.#secretNonce) {
854
- throw new SigningSessionError("Secret nonce not available \u2014 generateNonceContribution() must be called first.", "MISSING_SECRET_NONCE");
1233
+ throw new SigningSessionError("Secret nonce not available - generateNonceContribution() must be called first.", "MISSING_SECRET_NONCE");
855
1234
  }
856
1235
  const session = new musig2.Session(
857
1236
  this.aggregatedNonce,
@@ -921,6 +1300,9 @@ var AggregationService = class {
921
1300
  case SUBMIT_UPDATE:
922
1301
  this.#handleSubmitUpdate(message);
923
1302
  break;
1303
+ case SUBMIT_NONINCLUDED:
1304
+ this.#handleSubmitNonInclusion(message);
1305
+ break;
924
1306
  case VALIDATION_ACK:
925
1307
  this.#handleValidationAck(message);
926
1308
  break;
@@ -930,6 +1312,9 @@ var AggregationService = class {
930
1312
  case SIGNATURE_AUTHORIZATION:
931
1313
  this.#handleSignatureAuthorization(message);
932
1314
  break;
1315
+ case FALLBACK_SIGNATURE:
1316
+ this.#handleFallbackSignature(message);
1317
+ break;
933
1318
  default:
934
1319
  break;
935
1320
  }
@@ -948,7 +1333,7 @@ var AggregationService = class {
948
1333
  }
949
1334
  /**
950
1335
  * Create a new cohort with the given config. Returns the cohort ID.
951
- * Cohort starts in `Created` phase call `advertise()` to broadcast.
1336
+ * Cohort starts in `Created` phase - call `advertise()` to broadcast.
952
1337
  */
953
1338
  createCohort(config) {
954
1339
  const problems = validateCohortConditions(config);
@@ -963,7 +1348,11 @@ var AggregationService = class {
963
1348
  serviceDid: this.did,
964
1349
  minParticipants: config.minParticipants,
965
1350
  network: config.network,
966
- beaconType: config.beaconType
1351
+ beaconType: config.beaconType,
1352
+ recoveryKey: (0, import_utils3.hexToBytes)(config.recoveryKey),
1353
+ recoverySequence: config.recoverySequence,
1354
+ fundingModel: config.fundingModel,
1355
+ fallbackThreshold: config.fallbackThreshold
967
1356
  });
968
1357
  this.#cohortStates.set(cohort.id, {
969
1358
  phase: "Created" /* Created */,
@@ -1164,11 +1553,64 @@ var AggregationService = class {
1164
1553
  });
1165
1554
  return;
1166
1555
  }
1556
+ if (state.cohort.nonIncluded.has(message.from)) {
1557
+ state.rejections.push({
1558
+ from: message.from,
1559
+ code: "UPDATE_MALFORMED",
1560
+ reason: "Participant already declined this round; cannot also submit an update"
1561
+ });
1562
+ return;
1563
+ }
1564
+ if (state.cohort.pendingUpdates.has(message.from)) {
1565
+ state.rejections.push({
1566
+ from: message.from,
1567
+ code: "UPDATE_MALFORMED",
1568
+ reason: "Participant already submitted an update this round; cannot resubmit"
1569
+ });
1570
+ return;
1571
+ }
1167
1572
  state.cohort.addUpdate(message.from, signedUpdate);
1168
1573
  if (state.phase === "CohortSet" /* CohortSet */) {
1169
1574
  state.phase = "CollectingUpdates" /* CollectingUpdates */;
1170
1575
  }
1171
- if (state.cohort.hasAllUpdates()) {
1576
+ if (state.cohort.hasAllResponses()) {
1577
+ state.phase = "UpdatesCollected" /* UpdatesCollected */;
1578
+ }
1579
+ }
1580
+ /**
1581
+ * Handle an incoming SUBMIT_NONINCLUDED message: a member declares it has no
1582
+ * update this round (cooperative non-inclusion). Membership is proven by the
1583
+ * signed transport envelope, so the body carries only the cohortId. The member
1584
+ * stays in the cohort and still signs; it is absent from the CAS map and
1585
+ * carries a non-inclusion leaf in the SMT.
1586
+ */
1587
+ #handleSubmitNonInclusion(message) {
1588
+ const cohortId = message.body?.cohortId;
1589
+ if (!cohortId) return;
1590
+ const state = this.#cohortStates.get(cohortId);
1591
+ if (!state) return;
1592
+ if (state.phase !== "CohortSet" /* CohortSet */ && state.phase !== "CollectingUpdates" /* CollectingUpdates */) return;
1593
+ if (!state.cohort.participants.includes(message.from)) {
1594
+ state.rejections.push({
1595
+ from: message.from,
1596
+ code: "UPDATE_MALFORMED",
1597
+ reason: "Sender is not a member of this cohort"
1598
+ });
1599
+ return;
1600
+ }
1601
+ if (state.cohort.pendingUpdates.has(message.from) || state.cohort.nonIncluded.has(message.from)) {
1602
+ state.rejections.push({
1603
+ from: message.from,
1604
+ code: "UPDATE_MALFORMED",
1605
+ reason: "Participant already responded this round"
1606
+ });
1607
+ return;
1608
+ }
1609
+ state.cohort.addNonInclusion(message.from);
1610
+ if (state.phase === "CohortSet" /* CohortSet */) {
1611
+ state.phase = "CollectingUpdates" /* CollectingUpdates */;
1612
+ }
1613
+ if (state.cohort.hasAllResponses()) {
1172
1614
  state.phase = "UpdatesCollected" /* UpdatesCollected */;
1173
1615
  }
1174
1616
  }
@@ -1227,7 +1669,7 @@ var AggregationService = class {
1227
1669
  );
1228
1670
  }
1229
1671
  strategy.buildAggregatedData(state.cohort);
1230
- const signalBytesHex = (0, import_utils2.bytesToHex)(state.cohort.signalBytes);
1672
+ const signalBytesHex = (0, import_utils3.bytesToHex)(state.cohort.signalBytes);
1231
1673
  state.phase = "DataDistributed" /* DataDistributed */;
1232
1674
  const messages = [];
1233
1675
  for (const participantDid of state.cohort.participants) {
@@ -1278,7 +1720,7 @@ var AggregationService = class {
1278
1720
  }
1279
1721
  /**
1280
1722
  * Start a signing session by creating auth requests for all participants.
1281
- * The caller provides the transaction data typically built via
1723
+ * The caller provides the transaction data - typically built via
1282
1724
  * `buildBeaconTransaction()` against a Bitcoin connection.
1283
1725
  */
1284
1726
  startSigning(cohortId, txData) {
@@ -1317,7 +1759,7 @@ var AggregationService = class {
1317
1759
  cohortId,
1318
1760
  sessionId: session.id,
1319
1761
  pendingTx: txData.tx.hex,
1320
- prevOutScriptHex: (0, import_utils2.bytesToHex)(prevOutScript),
1762
+ prevOutScriptHex: (0, import_utils3.bytesToHex)(prevOutScript),
1321
1763
  prevOutValue: txData.prevOutValues[0]?.toString() ?? "0"
1322
1764
  }));
1323
1765
  }
@@ -1385,11 +1827,144 @@ var AggregationService = class {
1385
1827
  state.result = {
1386
1828
  cohortId,
1387
1829
  signature,
1388
- signedTx: state.signingSession.pendingTx
1830
+ signedTx: state.signingSession.pendingTx,
1831
+ path: "key-path"
1389
1832
  };
1390
1833
  state.phase = "Complete" /* Complete */;
1391
1834
  }
1392
1835
  }
1836
+ /**
1837
+ * Abandon the optimistic n-of-n key path and ask members to authorize the
1838
+ * k-of-n fallback (script-path) spend of the SAME beacon transaction (graceful
1839
+ * liveness, ADR 042). Reuses the in-flight signing session's transaction and
1840
+ * spent output, so the announcement and its outputs are unchanged: only the
1841
+ * witness path differs. Returns one FALLBACK_AUTHORIZATION_REQUEST per
1842
+ * participant.
1843
+ *
1844
+ * Callable once optimistic signing has started (the session and its tx exist)
1845
+ * and before it completes. A cohort can take exactly one of the two paths: the
1846
+ * caller (runner) must commit to fallback and stop driving the optimistic path.
1847
+ */
1848
+ startFallbackSigning(cohortId) {
1849
+ const state = this.#cohortStates.get(cohortId);
1850
+ if (!state) {
1851
+ throw new AggregationServiceError(`Cohort ${cohortId} not found.`, "COHORT_NOT_FOUND", { cohortId });
1852
+ }
1853
+ if (!state.signingSession) {
1854
+ throw new AggregationServiceError(
1855
+ `Cannot start fallback for cohort ${cohortId}: no signing session.`,
1856
+ "NO_SIGNING_SESSION",
1857
+ { cohortId }
1858
+ );
1859
+ }
1860
+ const signingPhases = [
1861
+ "SigningStarted" /* SigningStarted */,
1862
+ "NoncesCollected" /* NoncesCollected */,
1863
+ "AwaitingPartialSigs" /* AwaitingPartialSigs */
1864
+ ];
1865
+ if (!signingPhases.includes(state.phase)) {
1866
+ throw new AggregationServiceError(
1867
+ `Cannot start fallback for cohort ${cohortId}: phase is ${state.phase}.`,
1868
+ "INVALID_PHASE",
1869
+ { cohortId, phase: state.phase }
1870
+ );
1871
+ }
1872
+ const session = state.signingSession;
1873
+ const prevOutScript = session.prevOutScripts[0];
1874
+ const prevOutValue = session.prevOutValues[0];
1875
+ if (!prevOutScript || prevOutValue === void 0) {
1876
+ throw new AggregationServiceError(
1877
+ `Cannot start fallback for cohort ${cohortId}: signing session missing prevout data.`,
1878
+ "MISSING_PREV_OUT",
1879
+ { cohortId }
1880
+ );
1881
+ }
1882
+ const fallbackLeaf = buildFallbackLeaf({
1883
+ cohortKeys: state.cohort.cohortKeys,
1884
+ fallbackThreshold: state.cohort.effectiveFallbackThreshold
1885
+ });
1886
+ state.fallbackSignatures = /* @__PURE__ */ new Map();
1887
+ state.phase = "FallbackRequested" /* FallbackRequested */;
1888
+ const messages = [];
1889
+ for (const participantDid of state.cohort.participants) {
1890
+ messages.push(createFallbackAuthorizationRequestMessage({
1891
+ from: this.did,
1892
+ to: participantDid,
1893
+ cohortId,
1894
+ sessionId: session.id,
1895
+ pendingTx: session.pendingTx.hex,
1896
+ prevOutScriptHex: (0, import_utils3.bytesToHex)(prevOutScript),
1897
+ prevOutValue: prevOutValue.toString(),
1898
+ fallbackLeafScriptHex: (0, import_utils3.bytesToHex)(fallbackLeaf)
1899
+ }));
1900
+ }
1901
+ return messages;
1902
+ }
1903
+ /**
1904
+ * Handle an incoming FALLBACK_SIGNATURE: a member's standalone BIP-340
1905
+ * signature over the fallback script-path sighash. The signature is
1906
+ * authenticated to the sender (its `signerPk` must be the sender's own cohort
1907
+ * key and the signature must verify against the sighash) and collected. Once k
1908
+ * valid signatures are in, the k-of-n fallback spend is assembled and the
1909
+ * cohort completes via the script path.
1910
+ */
1911
+ #handleFallbackSignature(message) {
1912
+ const cohortId = message.body?.cohortId;
1913
+ if (!cohortId) return;
1914
+ const state = this.#cohortStates.get(cohortId);
1915
+ if (!state || !state.signingSession || !state.fallbackSignatures) return;
1916
+ if (state.phase !== "FallbackRequested" /* FallbackRequested */) return;
1917
+ const sessionId = message.body?.sessionId;
1918
+ if (sessionId !== state.signingSession.id) return;
1919
+ const signerPk = message.body?.signerPk;
1920
+ const fallbackSignature = message.body?.fallbackSignature;
1921
+ if (!signerPk || !fallbackSignature) return;
1922
+ const prevOutScript = state.signingSession.prevOutScripts[0];
1923
+ const prevOutValue = state.signingSession.prevOutValues[0];
1924
+ if (!prevOutScript || prevOutValue === void 0) return;
1925
+ const memberKey = state.cohort.participantKeys.get(message.from);
1926
+ if (!memberKey) {
1927
+ state.rejections.push({ from: message.from, code: "UPDATE_MALFORMED", reason: "Fallback signature from a non-member" });
1928
+ return;
1929
+ }
1930
+ const memberXOnly = memberKey.slice(1);
1931
+ if (signerPk.length !== 32 || !memberXOnly.every((b, i) => b === signerPk[i])) {
1932
+ state.rejections.push({ from: message.from, code: "UPDATE_MALFORMED", reason: "Fallback signerPk does not match the sender cohort key" });
1933
+ return;
1934
+ }
1935
+ const fallbackLeaf = buildFallbackLeaf({
1936
+ cohortKeys: state.cohort.cohortKeys,
1937
+ fallbackThreshold: state.cohort.effectiveFallbackThreshold
1938
+ });
1939
+ const sighash = fallbackSighash(state.signingSession.pendingTx, 0, prevOutScript, prevOutValue, fallbackLeaf);
1940
+ let valid = false;
1941
+ try {
1942
+ valid = fallbackSignature.length === 64 && import_secp256k14.schnorr.verify(fallbackSignature, sighash, signerPk);
1943
+ } catch {
1944
+ valid = false;
1945
+ }
1946
+ if (!valid) {
1947
+ state.rejections.push({ from: message.from, code: "UPDATE_VERIFICATION_FAILED", reason: "Fallback signature failed verification" });
1948
+ return;
1949
+ }
1950
+ state.fallbackSignatures.set(message.from, { pubKey: signerPk, signature: fallbackSignature });
1951
+ if (state.fallbackSignatures.size >= state.cohort.effectiveFallbackThreshold) {
1952
+ const signedTx = buildFallbackSpend({
1953
+ pendingTx: state.signingSession.pendingTx,
1954
+ cohortKeys: state.cohort.cohortKeys,
1955
+ fallbackThreshold: state.cohort.effectiveFallbackThreshold,
1956
+ recoveryKey: state.cohort.recoveryKey,
1957
+ recoverySequence: state.cohort.recoverySequence,
1958
+ fundingModel: state.cohort.fundingModel,
1959
+ network: state.cohort.network,
1960
+ prevOutScript,
1961
+ prevOutValue,
1962
+ signatures: [...state.fallbackSignatures.values()]
1963
+ });
1964
+ state.result = { cohortId, signature: new Uint8Array(), signedTx, path: "script-path" };
1965
+ state.phase = "Complete" /* Complete */;
1966
+ }
1967
+ }
1393
1968
  getResult(cohortId) {
1394
1969
  return this.#cohortStates.get(cohortId)?.result;
1395
1970
  }
@@ -1421,8 +1996,9 @@ var AggregationService = class {
1421
1996
 
1422
1997
  // src/core/aggregation/participant.ts
1423
1998
  var import_common5 = require("@did-btcr2/common");
1424
- var import_utils3 = require("@noble/hashes/utils");
1425
- var import_btc_signer3 = require("@scure/btc-signer");
1999
+ var import_secp256k15 = require("@noble/curves/secp256k1.js");
2000
+ var import_utils4 = require("@noble/hashes/utils");
2001
+ var import_btc_signer5 = require("@scure/btc-signer");
1426
2002
 
1427
2003
  // src/core/aggregation/messages/bodies.ts
1428
2004
  var hasStr = (b, k) => !!b && typeof b[k] === "string";
@@ -1434,6 +2010,14 @@ var optIntMin = (b, k, min) => {
1434
2010
  const v = b ? b[k] : void 0;
1435
2011
  return v === void 0 || typeof v === "number" && Number.isInteger(v) && v >= min;
1436
2012
  };
2013
+ var intInRange = (b, k, min, max) => {
2014
+ const v = b ? b[k] : void 0;
2015
+ return typeof v === "number" && Number.isInteger(v) && v >= min && v <= max;
2016
+ };
2017
+ var optStrOneOf = (b, k, allowed) => {
2018
+ const v = b ? b[k] : void 0;
2019
+ return v === void 0 || typeof v === "string" && allowed.includes(v);
2020
+ };
1437
2021
  var hasBool = (b, k) => !!b && typeof b[k] === "boolean";
1438
2022
  var hasBytes = (b, k) => !!b && b[k] instanceof Uint8Array;
1439
2023
  var hasBytesArray = (b, k) => {
@@ -1441,7 +2025,7 @@ var hasBytesArray = (b, k) => {
1441
2025
  return Array.isArray(v) && v.every((x) => x instanceof Uint8Array);
1442
2026
  };
1443
2027
  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");
2028
+ 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
2029
  }
1446
2030
  function isCohortOptInMessage(m) {
1447
2031
  return m.type === COHORT_OPT_IN && hasStr(m.body, "cohortId") && hasBytes(m.body, "participantPk") && hasBytes(m.body, "communicationPk");
@@ -1455,6 +2039,9 @@ function isCohortReadyMessage(m) {
1455
2039
  function isSubmitUpdateMessage(m) {
1456
2040
  return m.type === SUBMIT_UPDATE && hasStr(m.body, "cohortId") && !!m.body && typeof m.body.signedUpdate === "object";
1457
2041
  }
2042
+ function isSubmitNonIncludedMessage(m) {
2043
+ return m.type === SUBMIT_NONINCLUDED && hasStr(m.body, "cohortId");
2044
+ }
1458
2045
  function isDistributeAggregatedDataMessage(m) {
1459
2046
  return m.type === DISTRIBUTE_AGGREGATED_DATA && hasStr(m.body, "cohortId") && hasStr(m.body, "beaconType") && hasStr(m.body, "signalBytesHex");
1460
2047
  }
@@ -1473,8 +2060,38 @@ function isAggregatedNonceMessage(m) {
1473
2060
  function isSignatureAuthorizationMessage(m) {
1474
2061
  return m.type === SIGNATURE_AUTHORIZATION && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "partialSignature");
1475
2062
  }
2063
+ function isFallbackAuthorizationRequestMessage(m) {
2064
+ 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");
2065
+ }
2066
+ function isFallbackSignatureMessage(m) {
2067
+ return m.type === FALLBACK_SIGNATURE && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "signerPk") && hasBytes(m.body, "fallbackSignature");
2068
+ }
1476
2069
 
1477
2070
  // src/core/aggregation/participant.ts
2071
+ function txEmbedsSignal(tx, signalHex) {
2072
+ let expected;
2073
+ try {
2074
+ expected = (0, import_utils4.hexToBytes)(signalHex);
2075
+ } catch {
2076
+ return false;
2077
+ }
2078
+ if (expected.length === 0) return false;
2079
+ for (let i = 0; i < tx.outputsLength; i++) {
2080
+ const script = tx.getOutput(i)?.script;
2081
+ if (!script) continue;
2082
+ let decoded;
2083
+ try {
2084
+ decoded = import_btc_signer5.Script.decode(script);
2085
+ } catch {
2086
+ continue;
2087
+ }
2088
+ if (decoded.length === 2 && decoded[0] === "RETURN" && decoded[1] instanceof Uint8Array) {
2089
+ const payload = decoded[1];
2090
+ if (payload.length === expected.length && payload.every((b, j) => b === expected[j])) return true;
2091
+ }
2092
+ }
2093
+ return false;
2094
+ }
1478
2095
  var AggregationParticipant = class {
1479
2096
  did;
1480
2097
  /** MuSig2 signing capability. The raw secret never lives as a field here. */
@@ -1491,7 +2108,7 @@ var AggregationParticipant = class {
1491
2108
  }
1492
2109
  /**
1493
2110
  * Process an incoming message. Updates internal state but never produces
1494
- * outgoing messages those come exclusively from action methods.
2111
+ * outgoing messages: those come exclusively from action methods.
1495
2112
  */
1496
2113
  receive(message) {
1497
2114
  if (message.version === void 0 || message.version !== AGGREGATION_WIRE_VERSION) {
@@ -1517,6 +2134,9 @@ var AggregationParticipant = class {
1517
2134
  case AGGREGATED_NONCE:
1518
2135
  this.#handleAggregatedNonce(message);
1519
2136
  break;
2137
+ case FALLBACK_AUTHORIZATION_REQUEST:
2138
+ this.#handleFallbackAuthorizationRequest(message);
2139
+ break;
1520
2140
  default:
1521
2141
  break;
1522
2142
  }
@@ -1562,12 +2182,17 @@ var AggregationParticipant = class {
1562
2182
  { cohortId, phase: state?.phase }
1563
2183
  );
1564
2184
  }
2185
+ const advert = state.advert;
1565
2186
  const cohort = new AggregationCohort({
1566
2187
  id: cohortId,
1567
2188
  serviceDid: state.serviceDid,
1568
- minParticipants: state.advert.minParticipants,
1569
- network: state.advert.network,
1570
- beaconType: state.advert.beaconType
2189
+ minParticipants: advert.minParticipants,
2190
+ network: advert.network,
2191
+ beaconType: advert.beaconType,
2192
+ recoveryKey: advert.recoveryKey ? (0, import_utils4.hexToBytes)(advert.recoveryKey) : void 0,
2193
+ recoverySequence: advert.recoverySequence,
2194
+ fundingModel: advert.fundingModel,
2195
+ fallbackThreshold: advert.fallbackThreshold
1571
2196
  });
1572
2197
  state.cohort = cohort;
1573
2198
  state.phase = "OptedIn" /* OptedIn */;
@@ -1583,7 +2208,7 @@ var AggregationParticipant = class {
1583
2208
  #handleOptInAccept(message) {
1584
2209
  void message;
1585
2210
  }
1586
- /** Cohorts that have been finalized beacon address available. */
2211
+ /** Cohorts that have been finalized: beacon address available. */
1587
2212
  get joinedCohorts() {
1588
2213
  const map = /* @__PURE__ */ new Map();
1589
2214
  for (const [id, state] of this.#cohortStates) {
@@ -1607,8 +2232,8 @@ var AggregationParticipant = class {
1607
2232
  const beaconAddress = message.body?.beaconAddress;
1608
2233
  const cohortKeys = message.body?.cohortKeys;
1609
2234
  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)));
2235
+ const participantPkHex = (0, import_utils4.bytesToHex)(this.publicKey);
2236
+ const cohortKeysHex = cohortKeys.map((k) => (0, import_utils4.bytesToHex)(new Uint8Array(k)));
1612
2237
  state.cohort.validateMembership(participantPkHex, cohortKeysHex, beaconAddress);
1613
2238
  state.phase = "CohortReady" /* CohortReady */;
1614
2239
  }
@@ -1626,6 +2251,7 @@ var AggregationParticipant = class {
1626
2251
  );
1627
2252
  }
1628
2253
  state.submittedUpdate = signedUpdate;
2254
+ state.included = true;
1629
2255
  state.phase = "UpdateSubmitted" /* UpdateSubmitted */;
1630
2256
  const message = createSubmitUpdateMessage({
1631
2257
  from: this.did,
@@ -1635,6 +2261,30 @@ var AggregationParticipant = class {
1635
2261
  });
1636
2262
  return [message];
1637
2263
  }
2264
+ /**
2265
+ * User action: decline to submit an update this round (cooperative
2266
+ * non-inclusion). The member stays in the cohort and still signs; it will be
2267
+ * absent from the CAS Announcement Map, or carry a non-inclusion leaf in the
2268
+ * SMT. Returns the SUBMIT_NONINCLUDED message to send.
2269
+ */
2270
+ declineUpdate(cohortId) {
2271
+ const state = this.#cohortStates.get(cohortId);
2272
+ if (!state || state.phase !== "CohortReady" /* CohortReady */) {
2273
+ throw new AggregationParticipantError(
2274
+ `Cannot decline in cohort ${cohortId}: not in CohortReady phase.`,
2275
+ "INVALID_PHASE",
2276
+ { cohortId, phase: state?.phase }
2277
+ );
2278
+ }
2279
+ state.included = false;
2280
+ state.phase = "NonIncluded" /* NonIncluded */;
2281
+ const message = createSubmitNonIncludedMessage({
2282
+ from: this.did,
2283
+ to: state.serviceDid,
2284
+ cohortId
2285
+ });
2286
+ return [message];
2287
+ }
1638
2288
  /** Aggregated data awaiting user validation. */
1639
2289
  get pendingValidations() {
1640
2290
  const map = /* @__PURE__ */ new Map();
@@ -1648,9 +2298,9 @@ var AggregationParticipant = class {
1648
2298
  /**
1649
2299
  * The validated aggregated data retained for a cohort, regardless of phase.
1650
2300
  * 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
2301
+ * the validate decision), this returns the stored validation, including the
2302
+ * participant's sidecar (the CAS Announcement map or its SMT inclusion proof),
2303
+ * so it is still readable once the cohort reaches Complete. Returns
1654
2304
  * undefined before aggregated data has been received.
1655
2305
  */
1656
2306
  getValidation(cohortId) {
@@ -1660,18 +2310,20 @@ var AggregationParticipant = class {
1660
2310
  const cohortId = message.body?.cohortId;
1661
2311
  if (!cohortId) return;
1662
2312
  const state = this.#cohortStates.get(cohortId);
1663
- if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */) return;
1664
- if (!state.submittedUpdate) return;
2313
+ if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */ && state.phase !== "NonIncluded" /* NonIncluded */) return;
2314
+ const declined = state.included === false;
2315
+ if (!declined && !state.submittedUpdate) return;
1665
2316
  const beaconType = message.body?.beaconType;
1666
2317
  if (!beaconType) return;
1667
2318
  const strategy = getBeaconStrategy(beaconType);
1668
2319
  if (!strategy) return;
1669
2320
  const signalBytesHex = message.body?.signalBytesHex ?? "";
1670
- const expectedHash = (0, import_common5.canonicalHash)(state.submittedUpdate);
2321
+ const expectedHash = declined ? "" : (0, import_common5.canonicalHash)(state.submittedUpdate);
1671
2322
  const result = strategy.validateParticipantView({
1672
2323
  participantDid: this.did,
1673
- submittedUpdate: state.submittedUpdate,
1674
- expectedHash,
2324
+ included: !declined,
2325
+ submittedUpdate: declined ? void 0 : state.submittedUpdate,
2326
+ expectedHash: declined ? void 0 : expectedHash,
1675
2327
  body: message.body
1676
2328
  });
1677
2329
  state.validation = {
@@ -1681,7 +2333,8 @@ var AggregationParticipant = class {
1681
2333
  expectedHash,
1682
2334
  matches: result.matches,
1683
2335
  casAnnouncement: result.casAnnouncement,
1684
- smtProof: result.smtProof
2336
+ smtProof: result.smtProof,
2337
+ included: !declined
1685
2338
  };
1686
2339
  state.phase = "AwaitingValidation" /* AwaitingValidation */;
1687
2340
  }
@@ -1755,6 +2408,31 @@ var AggregationParticipant = class {
1755
2408
  };
1756
2409
  state.phase = "AwaitingSigning" /* AwaitingSigning */;
1757
2410
  }
2411
+ /**
2412
+ * Bind a signing approval to the announcement the member validated: a beacon
2413
+ * transaction MUST carry an OP_RETURN with the exact 32-byte signal stored when
2414
+ * the aggregated data was distributed. Both the optimistic nonce approval and
2415
+ * the fallback approval sign with SIGHASH_DEFAULT (committing to every output)
2416
+ * while the coordinator drives output selection, so without this check a
2417
+ * coordinator could anchor a different signal under the member's signature.
2418
+ */
2419
+ #assertTxAnchorsValidatedSignal(cohortId, state, tx) {
2420
+ const signalHex = state.validation?.signalBytesHex;
2421
+ if (!signalHex) {
2422
+ throw new AggregationParticipantError(
2423
+ `Cohort ${cohortId} has no validated signal to bind the signature to.`,
2424
+ "MISSING_STATE",
2425
+ { cohortId }
2426
+ );
2427
+ }
2428
+ if (!txEmbedsSignal(tx, signalHex)) {
2429
+ throw new AggregationParticipantError(
2430
+ `Transaction for cohort ${cohortId} does not anchor the validated signal.`,
2431
+ "SIGNAL_MISMATCH",
2432
+ { cohortId }
2433
+ );
2434
+ }
2435
+ }
1758
2436
  /**
1759
2437
  * User action: approve signing and generate nonce contribution.
1760
2438
  */
@@ -1774,8 +2452,9 @@ var AggregationParticipant = class {
1774
2452
  { cohortId }
1775
2453
  );
1776
2454
  }
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)];
2455
+ const tx = import_btc_signer5.Transaction.fromRaw((0, import_utils4.hexToBytes)(state.signingRequest.pendingTxHex), { allowUnknownOutputs: true });
2456
+ this.#assertTxAnchorsValidatedSignal(cohortId, state, tx);
2457
+ const prevOutScripts = [(0, import_utils4.hexToBytes)(state.signingRequest.prevOutScriptHex)];
1779
2458
  const prevOutValues = [BigInt(state.signingRequest.prevOutValue)];
1780
2459
  const session = new BeaconSigningSession({
1781
2460
  id: state.signingRequest.sessionId,
@@ -1810,7 +2489,7 @@ var AggregationParticipant = class {
1810
2489
  }
1811
2490
  /**
1812
2491
  * User action: generate and return the partial signature.
1813
- * In most UIs this is automatic after AwaitingPartialSig but exposing it
2492
+ * In most UIs this is automatic after AwaitingPartialSig, but exposing it
1814
2493
  * as an explicit action lets the client UI confirm before signing if desired.
1815
2494
  */
1816
2495
  generatePartialSignature(cohortId) {
@@ -1842,6 +2521,83 @@ var AggregationParticipant = class {
1842
2521
  partialSignature: partialSig
1843
2522
  })];
1844
2523
  }
2524
+ /** Fallback signing requests awaiting user approval (UI: review the fallback spend). */
2525
+ get pendingFallbackRequests() {
2526
+ const map = /* @__PURE__ */ new Map();
2527
+ for (const [id, state] of this.#cohortStates) {
2528
+ if (state.phase === "AwaitingFallbackSig" /* AwaitingFallbackSig */ && state.fallbackRequest) {
2529
+ map.set(id, state.fallbackRequest);
2530
+ }
2531
+ }
2532
+ return map;
2533
+ }
2534
+ #handleFallbackAuthorizationRequest(message) {
2535
+ const cohortId = message.body?.cohortId;
2536
+ if (!cohortId) return;
2537
+ const state = this.#cohortStates.get(cohortId);
2538
+ if (!state || !state.cohort) return;
2539
+ const acceptFrom = [
2540
+ "ValidationSent" /* ValidationSent */,
2541
+ "AwaitingSigning" /* AwaitingSigning */,
2542
+ "NonceSent" /* NonceSent */,
2543
+ "AwaitingPartialSig" /* AwaitingPartialSig */,
2544
+ "Complete" /* Complete */
2545
+ ];
2546
+ if (!acceptFrom.includes(state.phase)) return;
2547
+ const sessionId = message.body?.sessionId;
2548
+ const pendingTxHex = message.body?.pendingTx;
2549
+ const prevOutScriptHex = message.body?.prevOutScriptHex;
2550
+ const prevOutValue = message.body?.prevOutValue;
2551
+ const fallbackLeafScriptHex = message.body?.fallbackLeafScriptHex;
2552
+ if (!sessionId || !pendingTxHex || !prevOutScriptHex || !prevOutValue || !fallbackLeafScriptHex) return;
2553
+ state.fallbackRequest = { cohortId, sessionId, pendingTxHex, prevOutScriptHex, prevOutValue, fallbackLeafScriptHex };
2554
+ state.signingSession?.clearSecrets();
2555
+ state.phase = "AwaitingFallbackSig" /* AwaitingFallbackSig */;
2556
+ }
2557
+ /**
2558
+ * User action: authorize the fallback spend. Recomputes the k-of-n fallback
2559
+ * leaf from the member's OWN cohort state (not the service-provided script),
2560
+ * computes the BIP-341 script-path sighash over the requested transaction, and
2561
+ * returns a standalone BIP-340 signature (no nonce round). The member completes
2562
+ * once it has contributed; the service needs only k of these.
2563
+ */
2564
+ approveFallback(cohortId) {
2565
+ const state = this.#cohortStates.get(cohortId);
2566
+ if (!state || state.phase !== "AwaitingFallbackSig" /* AwaitingFallbackSig */) {
2567
+ throw new AggregationParticipantError(
2568
+ `Cannot approve fallback for cohort ${cohortId}: not in AwaitingFallbackSig phase.`,
2569
+ "INVALID_PHASE",
2570
+ { cohortId, phase: state?.phase }
2571
+ );
2572
+ }
2573
+ if (!state.fallbackRequest || !state.cohort) {
2574
+ throw new AggregationParticipantError(
2575
+ `Cohort ${cohortId} missing fallback request or cohort state.`,
2576
+ "MISSING_STATE",
2577
+ { cohortId }
2578
+ );
2579
+ }
2580
+ const req = state.fallbackRequest;
2581
+ const tx = import_btc_signer5.Transaction.fromRaw((0, import_utils4.hexToBytes)(req.pendingTxHex), { allowUnknownOutputs: true });
2582
+ const prevOutScript = (0, import_utils4.hexToBytes)(req.prevOutScriptHex);
2583
+ const prevOutValue = BigInt(req.prevOutValue);
2584
+ this.#assertTxAnchorsValidatedSignal(cohortId, state, tx);
2585
+ const fallbackLeaf = buildFallbackLeaf({
2586
+ cohortKeys: state.cohort.cohortKeys,
2587
+ fallbackThreshold: state.cohort.effectiveFallbackThreshold
2588
+ });
2589
+ const sighash = fallbackSighash(tx, 0, prevOutScript, prevOutValue, fallbackLeaf);
2590
+ const signature = this.#signer.withSecret((secretKey) => import_secp256k15.schnorr.sign(sighash, secretKey));
2591
+ state.phase = "Complete" /* Complete */;
2592
+ return [createFallbackSignatureMessage({
2593
+ from: this.did,
2594
+ to: state.serviceDid,
2595
+ cohortId,
2596
+ sessionId: req.sessionId,
2597
+ signerPk: this.publicKey.slice(1),
2598
+ fallbackSignature: signature
2599
+ })];
2600
+ }
1845
2601
  getCohortPhase(cohortId) {
1846
2602
  return this.#cohortStates.get(cohortId)?.phase;
1847
2603
  }
@@ -1878,6 +2634,87 @@ var KeyPairAggregationSigner = class {
1878
2634
  }
1879
2635
  };
1880
2636
 
2637
+ // src/core/aggregation/recovery-spend.ts
2638
+ var import_bitcoin3 = require("@did-btcr2/bitcoin");
2639
+ var import_secp256k16 = require("@noble/curves/secp256k1.js");
2640
+ var import_btc_signer6 = require("@scure/btc-signer");
2641
+ var import_musig24 = require("@scure/btc-signer/musig2");
2642
+ var DUST_LIMIT_SATS = 546n;
2643
+ function bytesEqual2(a, b) {
2644
+ return a.length === b.length && a.every((x, i) => x === b[i]);
2645
+ }
2646
+ function buildRecoverySpend(params) {
2647
+ const {
2648
+ cohortKeys,
2649
+ recoverySecretKey,
2650
+ recoveryKey,
2651
+ recoverySequence,
2652
+ fallbackThreshold,
2653
+ fundingModel,
2654
+ network,
2655
+ utxo,
2656
+ destinationAddress,
2657
+ fee,
2658
+ beaconAddress
2659
+ } = params;
2660
+ if (cohortKeys.length === 0) {
2661
+ throw new AggregationCohortError(
2662
+ "Cannot build recovery spend: no cohort keys.",
2663
+ "NO_COHORT_KEYS"
2664
+ );
2665
+ }
2666
+ const derivedPub = import_secp256k16.schnorr.getPublicKey(recoverySecretKey);
2667
+ if (!bytesEqual2(derivedPub, recoveryKey)) {
2668
+ throw new AggregationCohortError(
2669
+ "Recovery secret key does not correspond to the committed recovery key.",
2670
+ "RECOVERY_KEY_MISMATCH"
2671
+ );
2672
+ }
2673
+ const out = utxo.value - fee;
2674
+ if (out <= 0n) {
2675
+ throw new AggregationCohortError(
2676
+ `Recovery fee ${fee} exceeds UTXO value ${utxo.value}.`,
2677
+ "FEE_EXCEEDS_VALUE",
2678
+ { fee: fee.toString(), value: utxo.value.toString() }
2679
+ );
2680
+ }
2681
+ if (out < DUST_LIMIT_SATS) {
2682
+ throw new AggregationCohortError(
2683
+ `Recovered output ${out} is below the dust limit ${DUST_LIMIT_SATS}; the spend would not relay.`,
2684
+ "DUST_OUTPUT",
2685
+ { output: out.toString(), dustLimit: DUST_LIMIT_SATS.toString() }
2686
+ );
2687
+ }
2688
+ const internalKey = (0, import_musig24.keyAggExport)((0, import_musig24.keyAggregate)((0, import_musig24.sortKeys)(cohortKeys)));
2689
+ const leaves = buildRecoveryLeaves(fundingModel ?? DEFAULT_FUNDING_MODEL, {
2690
+ recoveryKey,
2691
+ recoverySequence,
2692
+ cohortKeys,
2693
+ fallbackThreshold: resolveFallbackThreshold(fallbackThreshold, cohortKeys.length)
2694
+ });
2695
+ const net = (0, import_bitcoin3.getNetwork)(network);
2696
+ const payment = (0, import_btc_signer6.p2tr)(internalKey, leaves, net, true);
2697
+ if (beaconAddress !== void 0 && payment.address !== beaconAddress) {
2698
+ throw new AggregationCohortError(
2699
+ `Reconstructed beacon address ${payment.address} does not match the cohort's funded address ${beaconAddress}.`,
2700
+ "BEACON_ADDRESS_MISMATCH",
2701
+ { reconstructed: payment.address, expected: beaconAddress }
2702
+ );
2703
+ }
2704
+ const tx = new import_btc_signer6.Transaction({ version: 2, allowUnknownInputs: true });
2705
+ tx.addInput({
2706
+ txid: utxo.txid,
2707
+ index: utxo.vout,
2708
+ witnessUtxo: { script: payment.script, amount: utxo.value },
2709
+ tapLeafScript: payment.tapLeafScript,
2710
+ sequence: recoverySequence
2711
+ });
2712
+ tx.addOutputAddress(destinationAddress, out, net);
2713
+ tx.signIdx(recoverySecretKey, 0);
2714
+ tx.finalize();
2715
+ return tx;
2716
+ }
2717
+
1881
2718
  // src/core/aggregation/logger.ts
1882
2719
  var CONSOLE_LOGGER = {
1883
2720
  debug: (msg, ...args) => console.debug(msg, ...args),
@@ -1905,6 +2742,7 @@ var KEYGEN_VALUES = /* @__PURE__ */ new Set([
1905
2742
  ]);
1906
2743
  var UPDATE_VALUES = /* @__PURE__ */ new Set([
1907
2744
  SUBMIT_UPDATE,
2745
+ SUBMIT_NONINCLUDED,
1908
2746
  DISTRIBUTE_AGGREGATED_DATA,
1909
2747
  VALIDATION_ACK
1910
2748
  ]);
@@ -1912,7 +2750,9 @@ var SIGN_VALUES = /* @__PURE__ */ new Set([
1912
2750
  AUTHORIZATION_REQUEST,
1913
2751
  NONCE_CONTRIBUTION,
1914
2752
  AGGREGATED_NONCE,
1915
- SIGNATURE_AUTHORIZATION
2753
+ SIGNATURE_AUTHORIZATION,
2754
+ FALLBACK_AUTHORIZATION_REQUEST,
2755
+ FALLBACK_SIGNATURE
1916
2756
  ]);
1917
2757
  function isAggregationMessageType(type) {
1918
2758
  return KEYGEN_VALUES.has(type) || UPDATE_VALUES.has(type) || SIGN_VALUES.has(type);
@@ -2140,7 +2980,7 @@ var Identifier = class _Identifier {
2140
2980
 
2141
2981
  // src/core/aggregation/transport/http/envelope.ts
2142
2982
  var import_common8 = require("@did-btcr2/common");
2143
- var import_utils4 = require("@noble/hashes/utils");
2983
+ var import_utils5 = require("@noble/hashes/utils");
2144
2984
 
2145
2985
  // src/core/aggregation/transport/http/errors.ts
2146
2986
  var HttpTransportError = class extends TransportAdapterError {
@@ -2168,7 +3008,7 @@ var DEFAULT_NONCE_LEN_BYTES = 16;
2168
3008
  // src/core/aggregation/transport/http/envelope.ts
2169
3009
  function signEnvelope(message, sender, opts = {}) {
2170
3010
  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));
3011
+ const nonce = opts.nonce ?? (0, import_utils5.bytesToHex)((0, import_utils5.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
2172
3012
  const messageJson = normalizeForWire(normalizeMessage(message));
2173
3013
  const unsigned = {
2174
3014
  v: HTTP_ENVELOPE_VERSION,
@@ -2180,7 +3020,7 @@ function signEnvelope(message, sender, opts = {}) {
2180
3020
  };
2181
3021
  const digest = (0, import_common8.canonicalHashBytes)(unsigned);
2182
3022
  const sig = sender.keys.secretKey.sign(digest, { scheme: "schnorr" });
2183
- return { ...unsigned, sig: (0, import_utils4.bytesToHex)(sig) };
3023
+ return { ...unsigned, sig: (0, import_utils5.bytesToHex)(sig) };
2184
3024
  }
2185
3025
  function verifyEnvelope(envelope, senderPk, opts = {}) {
2186
3026
  if (envelope.v !== HTTP_ENVELOPE_VERSION) {
@@ -2217,7 +3057,7 @@ function verifyEnvelope(envelope, senderPk, opts = {}) {
2217
3057
  }
2218
3058
  let sigBytes;
2219
3059
  try {
2220
- sigBytes = (0, import_utils4.hexToBytes)(envelope.sig);
3060
+ sigBytes = (0, import_utils5.hexToBytes)(envelope.sig);
2221
3061
  } catch {
2222
3062
  throw new HttpTransportError(
2223
3063
  "Envelope signature is not valid hex",
@@ -2250,7 +3090,7 @@ function normalizeMessage(message) {
2250
3090
  }
2251
3091
  function normalizeForWire(value) {
2252
3092
  if (value instanceof Uint8Array) {
2253
- return { __bytes: (0, import_utils4.bytesToHex)(value) };
3093
+ return { __bytes: (0, import_utils5.bytesToHex)(value) };
2254
3094
  }
2255
3095
  if (Array.isArray(value)) {
2256
3096
  return value.map((v) => normalizeForWire(v));
@@ -2269,7 +3109,7 @@ function reviveFromWire(value) {
2269
3109
  const rec = value;
2270
3110
  const keys = Object.keys(rec);
2271
3111
  if (keys.length === 1 && keys[0] === "__bytes" && typeof rec.__bytes === "string") {
2272
- return (0, import_utils4.hexToBytes)(rec.__bytes);
3112
+ return (0, import_utils5.hexToBytes)(rec.__bytes);
2273
3113
  }
2274
3114
  const out = {};
2275
3115
  for (const [k, v] of Object.entries(rec)) out[k] = reviveFromWire(v);
@@ -2283,11 +3123,11 @@ function reviveFromWire(value) {
2283
3123
 
2284
3124
  // src/core/aggregation/transport/http/request-auth.ts
2285
3125
  var import_common9 = require("@did-btcr2/common");
2286
- var import_utils5 = require("@noble/hashes/utils");
3126
+ var import_utils6 = require("@noble/hashes/utils");
2287
3127
  var REQUEST_AUTH_SCHEME = "BTCR2-Sig";
2288
3128
  function buildRequestAuth(did, keys, path, opts = {}) {
2289
3129
  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));
3130
+ const nonce = opts.nonce ?? (0, import_utils6.bytesToHex)((0, import_utils6.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
2291
3131
  const digest = (0, import_common9.canonicalHashBytes)({
2292
3132
  v: HTTP_ENVELOPE_VERSION,
2293
3133
  did,
@@ -2296,7 +3136,7 @@ function buildRequestAuth(did, keys, path, opts = {}) {
2296
3136
  path
2297
3137
  });
2298
3138
  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)}`;
3139
+ return `${REQUEST_AUTH_SCHEME} v=${HTTP_ENVELOPE_VERSION},did=${did},ts=${ts},nonce=${nonce},sig=${(0, import_utils6.bytesToHex)(sig)}`;
2300
3140
  }
2301
3141
  function parseRequestAuth(headerValue) {
2302
3142
  const prefix = `${REQUEST_AUTH_SCHEME} `;
@@ -2347,7 +3187,7 @@ function verifyRequestAuth(headerValue, expectedPath, senderPk, opts = {}) {
2347
3187
  }
2348
3188
  let sigBytes;
2349
3189
  try {
2350
- sigBytes = (0, import_utils5.hexToBytes)(parsed.sig);
3190
+ sigBytes = (0, import_utils6.hexToBytes)(parsed.sig);
2351
3191
  } catch {
2352
3192
  throw new HttpTransportError("Auth signature is not valid hex", "REQUEST_AUTH_SIG_HEX");
2353
3193
  }
@@ -3326,7 +4166,7 @@ function flattenMessage2(msg) {
3326
4166
 
3327
4167
  // src/core/aggregation/transport/nostr.ts
3328
4168
  var import_keypair6 = require("@did-btcr2/keypair");
3329
- var import_utils6 = require("@noble/hashes/utils");
4169
+ var import_utils7 = require("@noble/hashes/utils");
3330
4170
  var import_nostr_tools = require("nostr-tools");
3331
4171
  var import_pool = require("nostr-tools/pool");
3332
4172
  var DEFAULT_NOSTR_RELAYS = [
@@ -3524,14 +4364,14 @@ var NostrTransport = class _NostrTransport {
3524
4364
  }
3525
4365
  const senderKeys = actor.keys;
3526
4366
  const tags = [
3527
- ["p", (0, import_utils6.bytesToHex)(senderKeys.publicKey.x)],
4367
+ ["p", (0, import_utils7.bytesToHex)(senderKeys.publicKey.x)],
3528
4368
  ["t", type]
3529
4369
  ];
3530
4370
  if (to) {
3531
4371
  const recipientPkBytes = this.#peerRegistry.get(to);
3532
4372
  if (recipientPkBytes) {
3533
4373
  const recipientPk = new import_keypair6.CompressedSecp256k1PublicKey(recipientPkBytes);
3534
- tags.push(["p", (0, import_utils6.bytesToHex)(recipientPk.x)]);
4374
+ tags.push(["p", (0, import_utils7.bytesToHex)(recipientPk.x)]);
3535
4375
  }
3536
4376
  }
3537
4377
  if (isKeygenMessageType(type)) {
@@ -3564,7 +4404,7 @@ var NostrTransport = class _NostrTransport {
3564
4404
  const recipientPk = new import_keypair6.CompressedSecp256k1PublicKey(recipientPkBytes);
3565
4405
  const conversationKey = import_nostr_tools.nip44.v2.utils.getConversationKey(
3566
4406
  senderKeys.secretKey.bytes,
3567
- (0, import_utils6.bytesToHex)(recipientPk.x)
4407
+ (0, import_utils7.bytesToHex)(recipientPk.x)
3568
4408
  );
3569
4409
  const content = import_nostr_tools.nip44.v2.encrypt(JSON.stringify(message, _NostrTransport.#jsonReplacer), conversationKey);
3570
4410
  const event = (0, import_nostr_tools.finalizeEvent)({
@@ -3587,7 +4427,7 @@ var NostrTransport = class _NostrTransport {
3587
4427
  * backfill historical events to late subscribers: republishing gives late
3588
4428
  * joiners a window to discover the message without requiring protocol
3589
4429
  * changes. Relay rate-limit / publish failures inside the interval are
3590
- * caught and logged rather than propagated the caller should stop the
4430
+ * caught and logged rather than propagated - the caller should stop the
3591
4431
  * repeater once the protocol condition is satisfied.
3592
4432
  */
3593
4433
  publishRepeating(message, sender, intervalMs, recipient) {
@@ -3618,7 +4458,7 @@ var NostrTransport = class _NostrTransport {
3618
4458
  */
3619
4459
  #subscribeDirected(did, entry) {
3620
4460
  if (!this.pool) return;
3621
- const pkHex = (0, import_utils6.bytesToHex)(entry.keys.publicKey.x);
4461
+ const pkHex = (0, import_utils7.bytesToHex)(entry.keys.publicKey.x);
3622
4462
  const sub = this.pool.subscribeMany(this.#relays, { kinds: [1, 1059], "#p": [pkHex] }, {
3623
4463
  onclose: (reasons) => this.#logger.debug(`Nostr directed subscription closed for ${did}`, reasons),
3624
4464
  onevent: this.#makeActorEventHandler(did)
@@ -3636,7 +4476,7 @@ var NostrTransport = class _NostrTransport {
3636
4476
  return async (event) => {
3637
4477
  const actor = this.#actors.get(actorDid);
3638
4478
  if (!actor) return;
3639
- if (event.pubkey === (0, import_utils6.bytesToHex)(actor.keys.publicKey.x)) return;
4479
+ if (event.pubkey === (0, import_utils7.bytesToHex)(actor.keys.publicKey.x)) return;
3640
4480
  let message;
3641
4481
  try {
3642
4482
  if (event.kind === 1) {
@@ -3749,7 +4589,7 @@ var NostrTransport = class _NostrTransport {
3749
4589
  */
3750
4590
  static #jsonReplacer(_key, value) {
3751
4591
  if (value instanceof Uint8Array) {
3752
- return { __bytes: (0, import_utils6.bytesToHex)(value) };
4592
+ return { __bytes: (0, import_utils7.bytesToHex)(value) };
3753
4593
  }
3754
4594
  return value;
3755
4595
  }
@@ -3767,7 +4607,7 @@ var NostrTransport = class _NostrTransport {
3767
4607
  */
3768
4608
  static #jsonReviver(_key, value) {
3769
4609
  if (value && typeof value === "object" && "__bytes" in value) {
3770
- return (0, import_utils6.hexToBytes)(value.__bytes);
4610
+ return (0, import_utils7.hexToBytes)(value.__bytes);
3771
4611
  }
3772
4612
  return value;
3773
4613
  }
@@ -3802,7 +4642,7 @@ var TransportFactory = class {
3802
4642
  };
3803
4643
 
3804
4644
  // src/core/aggregation/transport/in-memory.ts
3805
- var import_utils7 = require("@noble/hashes/utils");
4645
+ var import_utils8 = require("@noble/hashes/utils");
3806
4646
  var InMemoryBus = class {
3807
4647
  #transports = /* @__PURE__ */ new Set();
3808
4648
  /** Attach a transport to this bus. Called by the transport's constructor. */
@@ -3821,8 +4661,8 @@ var InMemoryBus = class {
3821
4661
  async deliver(message, _sender, recipient) {
3822
4662
  const type = message.type;
3823
4663
  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;
4664
+ const replacer = (_k, v) => v instanceof Uint8Array ? { __bytes: (0, import_utils8.bytesToHex)(v) } : v;
4665
+ const reviver = (_k, v) => v && typeof v === "object" && "__bytes" in v ? (0, import_utils8.hexToBytes)(v.__bytes) : v;
3826
4666
  const raw = JSON.parse(JSON.stringify(message, replacer), reviver);
3827
4667
  const serialized = { ...raw, ...raw.body ?? {} };
3828
4668
  if (!recipient) {
@@ -4021,6 +4861,27 @@ var TypedEventEmitter = class {
4021
4861
  }
4022
4862
  };
4023
4863
 
4864
+ // src/core/beacon/fee-estimator.ts
4865
+ var StaticFeeEstimator = class {
4866
+ satsPerVbyte;
4867
+ /**
4868
+ * @param satsPerVbyte Fee rate in satoshis per virtual byte. Default: 5 sat/vB.
4869
+ */
4870
+ constructor(satsPerVbyte = 5) {
4871
+ if (satsPerVbyte < 0 || !Number.isFinite(satsPerVbyte)) {
4872
+ throw new Error(`Invalid satsPerVbyte: ${satsPerVbyte}`);
4873
+ }
4874
+ this.satsPerVbyte = satsPerVbyte;
4875
+ }
4876
+ async estimateFee(vsize) {
4877
+ if (vsize < 0 || !Number.isFinite(vsize)) {
4878
+ throw new Error(`Invalid vsize: ${vsize}`);
4879
+ }
4880
+ return BigInt(Math.ceil(vsize * this.satsPerVbyte));
4881
+ }
4882
+ };
4883
+ var DEFAULT_FEE_ESTIMATOR = new StaticFeeEstimator(5);
4884
+
4024
4885
  // src/core/aggregation/runner/service-runner.ts
4025
4886
  var DEFAULT_ADVERT_REPEAT_INTERVAL_MS = 6e4;
4026
4887
  var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEventEmitter {
@@ -4032,9 +4893,17 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
4032
4893
  #onOptInReceived;
4033
4894
  #onReadyToFinalize;
4034
4895
  #onProvideTxData;
4896
+ #feeEstimator;
4035
4897
  #cohortTtlMs;
4036
4898
  #phaseTimeoutMs;
4037
4899
  #advertRepeatIntervalMs;
4900
+ #autoFallbackOnStall;
4901
+ /** Phases during which a stall can be salvaged by the k-of-n fallback (ADR 042). */
4902
+ static #SIGNING_PHASES = [
4903
+ "SigningStarted" /* SigningStarted */,
4904
+ "NoncesCollected" /* NoncesCollected */,
4905
+ "AwaitingPartialSigs" /* AwaitingPartialSigs */
4906
+ ];
4038
4907
  /** Per-cohort run state, keyed by cohortId. */
4039
4908
  #contexts = /* @__PURE__ */ new Map();
4040
4909
  #handlersRegistered = false;
@@ -4049,9 +4918,11 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
4049
4918
  finalize: acceptedCount >= minRequired
4050
4919
  }));
4051
4920
  this.#onProvideTxData = options.onProvideTxData;
4921
+ this.#feeEstimator = options.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
4052
4922
  this.#cohortTtlMs = options.cohortTtlMs;
4053
4923
  this.#phaseTimeoutMs = options.phaseTimeoutMs;
4054
4924
  this.#advertRepeatIntervalMs = options.advertRepeatIntervalMs ?? DEFAULT_ADVERT_REPEAT_INTERVAL_MS;
4925
+ this.#autoFallbackOnStall = options.autoFallbackOnStall ?? false;
4055
4926
  this.session = new AggregationService({
4056
4927
  // The coordinator never signs, so the state machine receives only the
4057
4928
  // public half of the operator's keypair (see ADR 038). The full keypair
@@ -4213,11 +5084,42 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
4213
5084
  ctx.phaseTimer = void 0;
4214
5085
  if (this.#phaseTimeoutMs === void 0) return;
4215
5086
  ctx.phaseTimer = setTimeout(() => {
5087
+ const phase = this.session.getCohortPhase(ctx.cohortId);
5088
+ const inSigning = phase !== void 0 && _AggregationServiceRunner.#SIGNING_PHASES.includes(phase);
5089
+ if (this.#autoFallbackOnStall && inSigning && !ctx.committedPath && !ctx.settled) {
5090
+ this.triggerFallback(ctx.cohortId).catch((err) => this.#failCohort(ctx, err));
5091
+ return;
5092
+ }
4216
5093
  const reason = `Cohort ${ctx.cohortId} stalled in phase ${ctx.lastObservedPhase ?? "?"} for ${this.#phaseTimeoutMs}ms`;
4217
5094
  this.emit("cohort-failed", { cohortId: ctx.cohortId, reason });
4218
5095
  this.#failCohort(ctx, new Error(reason));
4219
5096
  }, this.#phaseTimeoutMs);
4220
5097
  }
5098
+ /**
5099
+ * Abandon the optimistic n-of-n key path for a cohort and collect k-of-n
5100
+ * fallback (script-path) signatures instead (ADR 042). Idempotent and safe
5101
+ * against the optimistic completion: it commits the cohort to the fallback
5102
+ * path synchronously (the `committedPath` latch) before sending anything, so a
5103
+ * late optimistic signature can no longer complete-and-broadcast a competing
5104
+ * spend of the same UTXO. No-op if the cohort is unknown, already settled, or
5105
+ * already committed to a path.
5106
+ *
5107
+ * Wired automatically to the phase-stall timer when `autoFallbackOnStall` is
5108
+ * set; otherwise call it from an operator decision (a UI "fall back now"
5109
+ * action). Throws only if the underlying state machine rejects the transition
5110
+ * (e.g. signing has not started).
5111
+ */
5112
+ async triggerFallback(cohortId) {
5113
+ const ctx = this.#contexts.get(cohortId);
5114
+ if (!ctx || ctx.settled || ctx.committedPath) return;
5115
+ const messages = this.session.startFallbackSigning(cohortId);
5116
+ ctx.committedPath = "fallback";
5117
+ this.#stopAdvertRepeating(ctx);
5118
+ this.#onPhaseMaybeChanged(ctx);
5119
+ const sessionId = this.session.getSigningSessionId(cohortId) ?? "";
5120
+ this.emit("fallback-started", { cohortId, sessionId });
5121
+ await this.#sendAll(messages);
5122
+ }
4221
5123
  /** Detect a phase change for a cohort since the last observation and reset its phase timer. */
4222
5124
  #onPhaseMaybeChanged(ctx) {
4223
5125
  const phase = this.session.getCohortPhase(ctx.cohortId);
@@ -4306,13 +5208,15 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
4306
5208
  static #HANDLED_MESSAGE_TYPES = [
4307
5209
  COHORT_OPT_IN,
4308
5210
  SUBMIT_UPDATE,
5211
+ SUBMIT_NONINCLUDED,
4309
5212
  VALIDATION_ACK,
4310
5213
  NONCE_CONTRIBUTION,
4311
- SIGNATURE_AUTHORIZATION
5214
+ SIGNATURE_AUTHORIZATION,
5215
+ FALLBACK_SIGNATURE
4312
5216
  ];
4313
5217
  /**
4314
5218
  * Internal: handler registration with the transport. Idempotent. Handlers
4315
- * are DID-scoped and cohort-agnostic one registration serves every cohort
5219
+ * are DID-scoped and cohort-agnostic - one registration serves every cohort
4316
5220
  * this runner drives; demux to the right {@link RunContext} happens in each
4317
5221
  * handler via the inbound message's cohortId.
4318
5222
  */
@@ -4321,9 +5225,11 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
4321
5225
  this.#handlersRegistered = true;
4322
5226
  this.#transport.registerMessageHandler(this.#did, COHORT_OPT_IN, this.#handleOptIn.bind(this));
4323
5227
  this.#transport.registerMessageHandler(this.#did, SUBMIT_UPDATE, this.#handleSubmitUpdate.bind(this));
5228
+ this.#transport.registerMessageHandler(this.#did, SUBMIT_NONINCLUDED, this.#handleSubmitUpdate.bind(this));
4324
5229
  this.#transport.registerMessageHandler(this.#did, VALIDATION_ACK, this.#handleValidationAck.bind(this));
4325
5230
  this.#transport.registerMessageHandler(this.#did, NONCE_CONTRIBUTION, this.#handleNonceContribution.bind(this));
4326
5231
  this.#transport.registerMessageHandler(this.#did, SIGNATURE_AUTHORIZATION, this.#handleSignatureAuthorization.bind(this));
5232
+ this.#transport.registerMessageHandler(this.#did, FALLBACK_SIGNATURE, this.#handleFallbackSignature.bind(this));
4327
5233
  }
4328
5234
  /** Internal: detach from the transport. Safe to call repeatedly. */
4329
5235
  #unregisterHandlers() {
@@ -4443,7 +5349,8 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
4443
5349
  const txData = await this.#onProvideTxData({
4444
5350
  cohortId: ctx.cohortId,
4445
5351
  beaconAddress: cohort.beaconAddress,
4446
- signalBytes: cohort.signalBytes
5352
+ signalBytes: cohort.signalBytes,
5353
+ feeEstimator: this.#feeEstimator
4447
5354
  });
4448
5355
  const authMsgs = this.session.startSigning(ctx.cohortId, txData);
4449
5356
  const sessionId = this.session.getSigningSessionId(ctx.cohortId) ?? "";
@@ -4484,6 +5391,32 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
4484
5391
  * @returns {Promise<void>} Resolves when handling is complete.
4485
5392
  */
4486
5393
  async #handleSignatureAuthorization(msg) {
5394
+ if (this.#stopped) return;
5395
+ const ctx = this.#contextFor(msg);
5396
+ if (!ctx) return;
5397
+ try {
5398
+ this.session.receive(msg);
5399
+ this.#drainRejections(ctx);
5400
+ this.#onPhaseMaybeChanged(ctx);
5401
+ if (ctx.committedPath === "fallback") return;
5402
+ const result = this.session.getResult(ctx.cohortId);
5403
+ if (result) {
5404
+ ctx.committedPath = "optimistic";
5405
+ this.#completeCohort(ctx, result);
5406
+ }
5407
+ } catch (err) {
5408
+ this.#failCohort(ctx, err);
5409
+ }
5410
+ }
5411
+ /**
5412
+ * Handler for receiving fallback (k-of-n script-path) signatures. The state
5413
+ * machine assembles and finalizes the fallback spend once k valid signatures
5414
+ * are in; the result is then emitted and the cohort's completion resolves. The
5415
+ * cohort is already committed to the fallback path (via {@link triggerFallback}).
5416
+ * @param {BaseMessage} msg - The incoming FALLBACK_SIGNATURE message.
5417
+ * @returns {Promise<void>} Resolves when handling is complete.
5418
+ */
5419
+ async #handleFallbackSignature(msg) {
4487
5420
  if (this.#stopped) return;
4488
5421
  const ctx = this.#contextFor(msg);
4489
5422
  if (!ctx) return;
@@ -4556,7 +5489,8 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
4556
5489
  COHORT_READY,
4557
5490
  DISTRIBUTE_AGGREGATED_DATA,
4558
5491
  AUTHORIZATION_REQUEST,
4559
- AGGREGATED_NONCE
5492
+ AGGREGATED_NONCE,
5493
+ FALLBACK_AUTHORIZATION_REQUEST
4560
5494
  ];
4561
5495
  /** Internal: detach from the transport. Safe to call repeatedly. */
4562
5496
  #unregisterHandlers() {
@@ -4591,7 +5525,7 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
4591
5525
  *
4592
5526
  * For an open-ended, long-lived subscriber (no fixed count), construct an
4593
5527
  * {@link AggregationParticipantRunner} directly, set `shouldJoin`, call
4594
- * `start()`, and listen for `cohort-complete` the runner already drives
5528
+ * `start()`, and listen for `cohort-complete` - the runner already drives
4595
5529
  * any number of cohorts concurrently.
4596
5530
  *
4597
5531
  * @param options Participant runner options (set `shouldJoin` to select cohorts).
@@ -4626,6 +5560,7 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
4626
5560
  this.#transport.registerMessageHandler(this.#did, DISTRIBUTE_AGGREGATED_DATA, this.#handleDistributeData.bind(this));
4627
5561
  this.#transport.registerMessageHandler(this.#did, AUTHORIZATION_REQUEST, this.#handleAuthorizationRequest.bind(this));
4628
5562
  this.#transport.registerMessageHandler(this.#did, AGGREGATED_NONCE, this.#handleAggregatedNonce.bind(this));
5563
+ this.#transport.registerMessageHandler(this.#did, FALLBACK_AUTHORIZATION_REQUEST, this.#handleFallbackAuthorizationRequest.bind(this));
4629
5564
  }
4630
5565
  /**
4631
5566
  * Internal: handler for cohort adverts. Applies the `shouldJoin` filter and joins if approved.
@@ -4681,8 +5616,13 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
4681
5616
  cohortId,
4682
5617
  beaconAddress: info.beaconAddress
4683
5618
  });
4684
- await this.#sendAll(this.session.submitUpdate(cohortId, signedUpdate));
4685
- this.emit("update-submitted", { cohortId });
5619
+ if (signedUpdate === null) {
5620
+ await this.#sendAll(this.session.declineUpdate(cohortId));
5621
+ this.emit("update-declined", { cohortId });
5622
+ } else {
5623
+ await this.#sendAll(this.session.submitUpdate(cohortId, signedUpdate));
5624
+ this.emit("update-submitted", { cohortId });
5625
+ }
4686
5626
  } catch (err) {
4687
5627
  this.emit("error", err);
4688
5628
  }
@@ -4758,23 +5698,62 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
4758
5698
  const cohortId = msg.body?.cohortId;
4759
5699
  if (!cohortId) return;
4760
5700
  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
- }
5701
+ this.#emitCohortCompleteIfDone(cohortId);
5702
+ } catch (err) {
5703
+ this.emit("error", err);
5704
+ }
5705
+ }
5706
+ /**
5707
+ * Internal: handler for fallback authorization requests. The service abandoned
5708
+ * the optimistic key path; the member authorizes the k-of-n script-path spend
5709
+ * of the same beacon transaction (ADR 042). Reuses `onApproveSigning` to gate
5710
+ * the decision, signs the fallback, and completes once it has contributed (the
5711
+ * service needs only k members).
5712
+ * @param {BaseMessage} msg - The received fallback authorization request message.
5713
+ * @returns {Promise<void>} Resolves when processing is complete.
5714
+ */
5715
+ async #handleFallbackAuthorizationRequest(msg) {
5716
+ if (this.#stopped) return;
5717
+ try {
5718
+ this.session.receive(msg);
5719
+ const cohortId = msg.body?.cohortId;
5720
+ if (!cohortId) return;
5721
+ const req = this.session.pendingFallbackRequests.get(cohortId);
5722
+ if (!req) return;
5723
+ this.emit("fallback-requested", req);
5724
+ const decision = await this.#onApproveSigning(req);
5725
+ if (!decision.approved) {
5726
+ this.emit("cohort-failed", { cohortId, reason: "Fallback signing rejected by participant" });
5727
+ return;
4773
5728
  }
5729
+ await this.#sendAll(this.session.approveFallback(cohortId));
5730
+ this.#emitCohortCompleteIfDone(cohortId);
4774
5731
  } catch (err) {
4775
5732
  this.emit("error", err);
4776
5733
  }
4777
5734
  }
5735
+ /**
5736
+ * Internal: emit `cohort-complete` with the participant's retained sidecar once
5737
+ * the cohort has reached the Complete phase. Surfaces the CAS Announcement map
5738
+ * (CAS beacons) or the SMT proof (SMT beacons) the participant keeps for future
5739
+ * DID resolution. Read via getValidation (not pendingValidations, which lists
5740
+ * only the AwaitingValidation phase) so the sidecar is still available at
5741
+ * Complete. Shared by the optimistic and fallback completion paths.
5742
+ */
5743
+ #emitCohortCompleteIfDone(cohortId) {
5744
+ if (this.session.getCohortPhase(cohortId) !== "Complete" /* Complete */) return;
5745
+ const info = this.session.joinedCohorts.get(cohortId);
5746
+ if (!info) return;
5747
+ const validation = this.session.getValidation(cohortId);
5748
+ this.emit("cohort-complete", {
5749
+ cohortId,
5750
+ beaconAddress: info.beaconAddress,
5751
+ beaconType: validation?.beaconType ?? "",
5752
+ included: validation?.included ?? true,
5753
+ casAnnouncement: validation?.casAnnouncement,
5754
+ smtProof: validation?.smtProof
5755
+ });
5756
+ }
4778
5757
  /**
4779
5758
  * Internal: send helper to ensure messages are sent sequentially. This is important for protocol
4780
5759
  * correctness, as some transports may not guarantee message order if sent in parallel.
@@ -4790,6 +5769,7 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
4790
5769
  };
4791
5770
 
4792
5771
  // src/core/aggregation/runner/aggregation-runner.ts
5772
+ var import_utils9 = require("@noble/hashes/utils");
4793
5773
  var AggregationRunner = class {
4794
5774
  /**
4795
5775
  * Run a cohort of ONE participant entirely in-process and return the
@@ -4797,8 +5777,8 @@ var AggregationRunner = class {
4797
5777
  *
4798
5778
  * One party plays both the coordinating service and the lone participant,
4799
5779
  * 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
5780
+ * makes the single-participant aggregate-beacon path (the N=1 corner of the
5781
+ * two-axis beacon matrix, see ADR 037) first-class, useful for generating
4802
5782
  * and reproducing single-participant aggregate test vectors.
4803
5783
  *
4804
5784
  * The service advertises a cohort with `minParticipants: 1`; the participant
@@ -4816,12 +5796,15 @@ var AggregationRunner = class {
4816
5796
  transport.registerPeer(options.participant.did, options.participant.keys.publicKey.compressed);
4817
5797
  transport.registerPeer(options.service.did, options.service.keys.publicKey.compressed);
4818
5798
  transport.start();
5799
+ const recoveryKey = options.config.recoveryKey ?? (0, import_utils9.bytesToHex)(options.service.keys.publicKey.compressed.slice(1));
5800
+ const recoverySequence = options.config.recoverySequence ?? DEFAULT_RECOVERY_SEQUENCE;
4819
5801
  const service = new AggregationServiceRunner({
4820
5802
  transport,
4821
5803
  did: options.service.did,
4822
5804
  keys: options.service.keys,
4823
- config: { minParticipants: 1, network: options.config.network, beaconType: options.config.beaconType },
5805
+ config: { minParticipants: 1, network: options.config.network, beaconType: options.config.beaconType, recoveryKey, recoverySequence, fundingModel: DEFAULT_FUNDING_MODEL },
4824
5806
  onProvideTxData: options.onProvideTxData,
5807
+ feeEstimator: options.feeEstimator,
4825
5808
  cohortTtlMs: options.cohortTtlMs,
4826
5809
  phaseTimeoutMs: options.phaseTimeoutMs,
4827
5810
  // In-process bus with the participant already listening: a single advert
@@ -4846,8 +5829,8 @@ var AggregationRunner = class {
4846
5829
  };
4847
5830
 
4848
5831
  // src/core/beacon/beacon.ts
4849
- var import_utils8 = require("@noble/hashes/utils.js");
4850
- var import_btc_signer4 = require("@scure/btc-signer");
5832
+ var import_utils10 = require("@noble/hashes/utils.js");
5833
+ var import_btc_signer7 = require("@scure/btc-signer");
4851
5834
 
4852
5835
  // src/core/beacon/error.ts
4853
5836
  var import_common12 = require("@did-btcr2/common");
@@ -4877,28 +5860,7 @@ var SMTBeaconError = class extends import_common12.MethodError {
4877
5860
  }
4878
5861
  };
4879
5862
 
4880
- // src/core/beacon/fee-estimator.ts
4881
- var StaticFeeEstimator = class {
4882
- satsPerVbyte;
4883
- /**
4884
- * @param satsPerVbyte Fee rate in satoshis per virtual byte. Default: 5 sat/vB.
4885
- */
4886
- constructor(satsPerVbyte = 5) {
4887
- if (satsPerVbyte < 0 || !Number.isFinite(satsPerVbyte)) {
4888
- throw new Error(`Invalid satsPerVbyte: ${satsPerVbyte}`);
4889
- }
4890
- this.satsPerVbyte = satsPerVbyte;
4891
- }
4892
- async estimateFee(vsize) {
4893
- if (vsize < 0 || !Number.isFinite(vsize)) {
4894
- throw new Error(`Invalid vsize: ${vsize}`);
4895
- }
4896
- return BigInt(Math.ceil(vsize * this.satsPerVbyte));
4897
- }
4898
- };
4899
-
4900
5863
  // src/core/beacon/beacon.ts
4901
- var DEFAULT_FEE_ESTIMATOR = new StaticFeeEstimator(5);
4902
5864
  var P2TR_BEACON_TX_VSIZE = 160;
4903
5865
  var P2WPKH_BEACON_TX_VSIZE = 155;
4904
5866
  var P2PKH_BEACON_TX_VSIZE = 240;
@@ -4907,8 +5869,22 @@ var SINGLETON_BEACON_TX_VSIZE = {
4907
5869
  p2wpkh: P2WPKH_BEACON_TX_VSIZE,
4908
5870
  p2tr: P2TR_BEACON_TX_VSIZE
4909
5871
  };
5872
+ var CHANGE_OUTPUT_VBYTES = {
5873
+ p2pkh: 34,
5874
+ p2wpkh: 31,
5875
+ p2tr: 43
5876
+ };
5877
+ var DUST_LIMIT_SATS2 = {
5878
+ p2pkh: 546,
5879
+ p2wpkh: 294,
5880
+ p2tr: 330
5881
+ };
5882
+ function beaconTxVsize(beaconKind, changeKind) {
5883
+ const base2 = SINGLETON_BEACON_TX_VSIZE[beaconKind] - CHANGE_OUTPUT_VBYTES[beaconKind];
5884
+ return base2 + CHANGE_OUTPUT_VBYTES[changeKind];
5885
+ }
4910
5886
  function detectSingletonScriptKind(bitcoinAddress, network) {
4911
- const decoded = (0, import_btc_signer4.Address)(network).decode(bitcoinAddress);
5887
+ const decoded = (0, import_btc_signer7.Address)(network).decode(bitcoinAddress);
4912
5888
  if (decoded.type === "pkh") return "p2pkh";
4913
5889
  if (decoded.type === "wpkh") return "p2wpkh";
4914
5890
  if (decoded.type === "tr") return "p2tr";
@@ -4919,12 +5895,32 @@ function detectSingletonScriptKind(bitcoinAddress, network) {
4919
5895
  );
4920
5896
  }
4921
5897
  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;
5898
+ if (kind === "p2pkh") return (0, import_btc_signer7.p2pkh)(pubkey, network).address;
5899
+ if (kind === "p2wpkh") return (0, import_btc_signer7.p2wpkh)(pubkey, network).address;
5900
+ return (0, import_btc_signer7.p2tr)(pubkey.slice(1, 33), void 0, network).address;
5901
+ }
5902
+ function resolveChangeAddress(beaconAddress, network, changeAddress) {
5903
+ if (!changeAddress || changeAddress === beaconAddress) return beaconAddress;
5904
+ try {
5905
+ (0, import_btc_signer7.Address)(network).decode(changeAddress);
5906
+ } catch {
5907
+ throw new BeaconError(
5908
+ `Invalid change address "${changeAddress}" for network "${network}".`,
5909
+ "INVALID_CHANGE_ADDRESS",
5910
+ { changeAddress, network }
5911
+ );
5912
+ }
5913
+ return changeAddress;
5914
+ }
5915
+ function changeOutputKind(changeAddress, network) {
5916
+ try {
5917
+ return detectSingletonScriptKind(changeAddress, network);
5918
+ } catch {
5919
+ return "p2tr";
5920
+ }
4925
5921
  }
4926
5922
  function opReturnScript(signalBytes) {
4927
- return import_btc_signer4.Script.encode(["RETURN", signalBytes]);
5923
+ return import_btc_signer7.Script.encode(["RETURN", signalBytes]);
4928
5924
  }
4929
5925
  async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
4930
5926
  const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
@@ -4944,14 +5940,15 @@ async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
4944
5940
  );
4945
5941
  }
4946
5942
  const prevTxHex = await bitcoin.rest.transaction.getHex(utxo.txid);
4947
- return { utxo, prevTxBytes: (0, import_utils8.hexToBytes)(prevTxHex) };
5943
+ return { utxo, prevTxBytes: (0, import_utils10.hexToBytes)(prevTxHex) };
4948
5944
  }
4949
5945
  async function buildAggregationBeaconTx(opts) {
4950
5946
  const feeEstimator = opts.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
4951
5947
  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;
4954
- const feeSats = await feeEstimator.estimateFee(P2TR_BEACON_TX_VSIZE);
5948
+ const changeAddress = resolveChangeAddress(opts.beaconAddress, opts.network, opts.changeAddress);
5949
+ const witnessScript = import_btc_signer7.OutScript.encode((0, import_btc_signer7.Address)(opts.network).decode(opts.beaconAddress));
5950
+ const changeKind = changeOutputKind(changeAddress, opts.network);
5951
+ const feeSats = await feeEstimator.estimateFee(beaconTxVsize("p2tr", changeKind));
4955
5952
  if (BigInt(utxo.value) <= feeSats) {
4956
5953
  throw new BeaconError(
4957
5954
  `UTXO value (${utxo.value}) insufficient to cover fee (${feeSats}).`,
@@ -4959,7 +5956,7 @@ async function buildAggregationBeaconTx(opts) {
4959
5956
  { address: opts.beaconAddress, valueSats: utxo.value, feeSats }
4960
5957
  );
4961
5958
  }
4962
- const tx = new import_btc_signer4.Transaction({ allowUnknownOutputs: true });
5959
+ const tx = new import_btc_signer7.Transaction({ allowUnknownOutputs: true });
4963
5960
  tx.addInput({
4964
5961
  txid: utxo.txid,
4965
5962
  index: utxo.vout,
@@ -4967,13 +5964,17 @@ async function buildAggregationBeaconTx(opts) {
4967
5964
  witnessUtxo: { amount: BigInt(utxo.value), script: witnessScript },
4968
5965
  tapInternalKey: opts.internalPubkey
4969
5966
  });
4970
- tx.addOutputAddress(opts.beaconAddress, BigInt(utxo.value) - feeSats, opts.network);
5967
+ const changeValue = BigInt(utxo.value) - feeSats;
5968
+ if (changeValue >= BigInt(DUST_LIMIT_SATS2[changeKind])) {
5969
+ tx.addOutputAddress(changeAddress, changeValue, opts.network);
5970
+ }
4971
5971
  tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
4972
5972
  return {
4973
5973
  tx,
4974
5974
  prevOutScripts: [witnessScript],
4975
5975
  prevOutValues: [BigInt(utxo.value)],
4976
5976
  beaconAddress: opts.beaconAddress,
5977
+ changeAddress,
4977
5978
  utxo,
4978
5979
  feeSats,
4979
5980
  scriptKind: "p2tr"
@@ -4982,16 +5983,16 @@ async function buildAggregationBeaconTx(opts) {
4982
5983
  async function signSingletonInput(tx, inputIdx, kind, signer, prevOutScript, amount) {
4983
5984
  const pubkey = signer.publicKey;
4984
5985
  if (kind === "p2pkh") {
4985
- const sighashType = import_btc_signer4.SigHash.ALL;
5986
+ const sighashType = import_btc_signer7.SigHash.ALL;
4986
5987
  const sighash2 = tx.preimageLegacy(inputIdx, prevOutScript, sighashType);
4987
5988
  const sig2 = signer.sign(sighash2, "ecdsa");
4988
- const sigWithType = (0, import_utils8.concatBytes)(sig2, new Uint8Array([sighashType]));
5989
+ const sigWithType = (0, import_utils10.concatBytes)(sig2, new Uint8Array([sighashType]));
4989
5990
  tx.updateInput(inputIdx, { partialSig: [[pubkey, sigWithType]] }, true);
4990
5991
  tx.finalize();
4991
5992
  return tx.hex;
4992
5993
  }
4993
5994
  if (kind === "p2wpkh") {
4994
- const decoded = import_btc_signer4.OutScript.decode(prevOutScript);
5995
+ const decoded = import_btc_signer7.OutScript.decode(prevOutScript);
4995
5996
  if (decoded.type !== "wpkh") {
4996
5997
  throw new BeaconError(
4997
5998
  `Expected P2WPKH prev-output script, got "${decoded.type}".`,
@@ -4999,16 +6000,16 @@ async function signSingletonInput(tx, inputIdx, kind, signer, prevOutScript, amo
4999
6000
  { kind, observedScriptType: decoded.type }
5000
6001
  );
5001
6002
  }
5002
- const sighashScript = import_btc_signer4.OutScript.encode({ type: "pkh", hash: decoded.hash });
5003
- const sighashType = import_btc_signer4.SigHash.ALL;
6003
+ const sighashScript = import_btc_signer7.OutScript.encode({ type: "pkh", hash: decoded.hash });
6004
+ const sighashType = import_btc_signer7.SigHash.ALL;
5004
6005
  const sighash2 = tx.preimageWitnessV0(inputIdx, sighashScript, sighashType, amount);
5005
6006
  const sig2 = signer.sign(sighash2, "ecdsa");
5006
- const sigWithType = (0, import_utils8.concatBytes)(sig2, new Uint8Array([sighashType]));
6007
+ const sigWithType = (0, import_utils10.concatBytes)(sig2, new Uint8Array([sighashType]));
5007
6008
  tx.updateInput(inputIdx, { partialSig: [[pubkey, sigWithType]] }, true);
5008
6009
  tx.finalize();
5009
6010
  return tx.hex;
5010
6011
  }
5011
- const sighash = tx.preimageWitnessV1(inputIdx, [prevOutScript], import_btc_signer4.SigHash.DEFAULT, [amount]);
6012
+ const sighash = tx.preimageWitnessV1(inputIdx, [prevOutScript], import_btc_signer7.SigHash.DEFAULT, [amount]);
5012
6013
  const sig = signer.sign(sighash, "bip341");
5013
6014
  tx.updateInput(inputIdx, { tapKeySig: sig });
5014
6015
  tx.finalize();
@@ -5029,7 +6030,7 @@ var SinglePartyBeacon = class {
5029
6030
  *
5030
6031
  * Composed from the three extracted phases ({@link buildSinglePartyTx},
5031
6032
  * {@link signSinglePartyTx}, {@link broadcastRawTx}) so each piece can be exercised
5032
- * in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead
6033
+ * in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead:
5033
6034
  * the multi-party path can't share the signing phase, but the tx-construction
5034
6035
  * plumbing (UTXO fetch + OP_RETURN output + change output) is shared.
5035
6036
  *
@@ -5051,7 +6052,8 @@ var SinglePartyBeacon = class {
5051
6052
  prevTxBytes,
5052
6053
  signer,
5053
6054
  bitcoin,
5054
- feeEstimator
6055
+ feeEstimator,
6056
+ changeAddress: options?.changeAddress
5055
6057
  });
5056
6058
  const signedHex = await this.signSinglePartyTx(plan, signer);
5057
6059
  return this.broadcastRawTx(bitcoin, signedHex);
@@ -5061,15 +6063,17 @@ var SinglePartyBeacon = class {
5061
6063
  *
5062
6064
  * Detects the beacon address script kind (P2PKH / P2WPKH / P2TR) and configures
5063
6065
  * the input accordingly. Validates that the signer's pubkey produces the beacon
5064
- * address under that script kind without this check, a misconfigured caller
6066
+ * address under that script kind: without this check, a misconfigured caller
5065
6067
  * would burn a real UTXO on a tx that fails at broadcast. Fees are computed from
5066
- * the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant, avoiding any probe-sign
5067
- * round-trip.
6068
+ * the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant (via {@link beaconTxVsize}),
6069
+ * avoiding any probe-sign round-trip; a change address of a different kind re-sizes
6070
+ * the fee by the change output's size delta so it stays a valid upper bound.
5068
6071
  */
5069
6072
  async buildSinglePartyTx(opts) {
5070
6073
  const network = opts.bitcoin.data;
5071
6074
  const pubkey = opts.signer.publicKey;
5072
6075
  const kind = detectSingletonScriptKind(opts.beaconAddress, network);
6076
+ const changeAddress = resolveChangeAddress(opts.beaconAddress, network, opts.changeAddress);
5073
6077
  const derivedAddress = deriveSingletonAddress(kind, pubkey, network);
5074
6078
  if (derivedAddress !== opts.beaconAddress) {
5075
6079
  throw new BeaconError(
@@ -5078,7 +6082,8 @@ var SinglePartyBeacon = class {
5078
6082
  { kind, address: opts.beaconAddress, derivedAddress }
5079
6083
  );
5080
6084
  }
5081
- const feeSats = await opts.feeEstimator.estimateFee(SINGLETON_BEACON_TX_VSIZE[kind]);
6085
+ const changeKind = changeOutputKind(changeAddress, network);
6086
+ const feeSats = await opts.feeEstimator.estimateFee(beaconTxVsize(kind, changeKind));
5082
6087
  const amount = BigInt(opts.utxo.value);
5083
6088
  if (amount <= feeSats) {
5084
6089
  throw new BeaconError(
@@ -5087,17 +6092,17 @@ var SinglePartyBeacon = class {
5087
6092
  { address: opts.beaconAddress, valueSats: opts.utxo.value, feeSats }
5088
6093
  );
5089
6094
  }
5090
- const tx = new import_btc_signer4.Transaction({ allowUnknownOutputs: true });
6095
+ const tx = new import_btc_signer7.Transaction({ allowUnknownOutputs: true });
5091
6096
  let prevOutScript;
5092
6097
  if (kind === "p2pkh") {
5093
- prevOutScript = (0, import_btc_signer4.p2pkh)(pubkey, network).script;
6098
+ prevOutScript = (0, import_btc_signer7.p2pkh)(pubkey, network).script;
5094
6099
  tx.addInput({
5095
6100
  txid: opts.utxo.txid,
5096
6101
  index: opts.utxo.vout,
5097
6102
  nonWitnessUtxo: opts.prevTxBytes
5098
6103
  });
5099
6104
  } else if (kind === "p2wpkh") {
5100
- prevOutScript = (0, import_btc_signer4.p2wpkh)(pubkey, network).script;
6105
+ prevOutScript = (0, import_btc_signer7.p2wpkh)(pubkey, network).script;
5101
6106
  tx.addInput({
5102
6107
  txid: opts.utxo.txid,
5103
6108
  index: opts.utxo.vout,
@@ -5106,7 +6111,7 @@ var SinglePartyBeacon = class {
5106
6111
  });
5107
6112
  } else {
5108
6113
  const internalKey = pubkey.slice(1, 33);
5109
- prevOutScript = (0, import_btc_signer4.p2tr)(internalKey, void 0, network).script;
6114
+ prevOutScript = (0, import_btc_signer7.p2tr)(internalKey, void 0, network).script;
5110
6115
  tx.addInput({
5111
6116
  txid: opts.utxo.txid,
5112
6117
  index: opts.utxo.vout,
@@ -5115,13 +6120,17 @@ var SinglePartyBeacon = class {
5115
6120
  tapInternalKey: internalKey
5116
6121
  });
5117
6122
  }
5118
- tx.addOutputAddress(opts.beaconAddress, amount - feeSats, network);
6123
+ const changeValue = amount - feeSats;
6124
+ if (changeValue >= BigInt(DUST_LIMIT_SATS2[changeKind])) {
6125
+ tx.addOutputAddress(changeAddress, changeValue, network);
6126
+ }
5119
6127
  tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
5120
6128
  return {
5121
6129
  tx,
5122
6130
  prevOutScripts: [prevOutScript],
5123
6131
  prevOutValues: [amount],
5124
6132
  beaconAddress: opts.beaconAddress,
6133
+ changeAddress,
5125
6134
  utxo: opts.utxo,
5126
6135
  feeSats,
5127
6136
  scriptKind: kind
@@ -5295,7 +6304,7 @@ var SingletonBeacon = class extends SinglePartyBeacon {
5295
6304
  // src/core/beacon/smt-beacon.ts
5296
6305
  var import_common15 = require("@did-btcr2/common");
5297
6306
  var import_smt3 = require("@did-btcr2/smt");
5298
- var import_utils9 = require("@noble/hashes/utils");
6307
+ var import_utils11 = require("@noble/hashes/utils");
5299
6308
  var SMTBeacon = class extends SinglePartyBeacon {
5300
6309
  /**
5301
6310
  * Creates an instance of SMTBeacon.
@@ -5384,7 +6393,7 @@ var SMTBeacon = class extends SinglePartyBeacon {
5384
6393
  async broadcastSignal(signedUpdate, signer, bitcoin, options) {
5385
6394
  const did = this.service.id.split("#")[0];
5386
6395
  const canonicalBytes = new TextEncoder().encode((0, import_common15.canonicalize)(signedUpdate));
5387
- const nonce = (0, import_utils9.randomBytes)(32);
6396
+ const nonce = (0, import_utils11.randomBytes)(32);
5388
6397
  const tree = new import_smt3.BTCR2MerkleTree();
5389
6398
  tree.addEntries([{ did, nonce, signedUpdate: canonicalBytes }]);
5390
6399
  tree.finalize();
@@ -5415,13 +6424,13 @@ var BeaconFactory = class {
5415
6424
  };
5416
6425
 
5417
6426
  // src/core/beacon/signal-discovery.ts
5418
- var import_bitcoin3 = require("@did-btcr2/bitcoin");
6427
+ var import_bitcoin5 = require("@did-btcr2/bitcoin");
5419
6428
  var import_common18 = require("@did-btcr2/common");
5420
6429
 
5421
6430
  // src/core/beacon/utils.ts
5422
- var import_bitcoin2 = require("@did-btcr2/bitcoin");
6431
+ var import_bitcoin4 = require("@did-btcr2/bitcoin");
5423
6432
  var import_common17 = require("@did-btcr2/common");
5424
- var import_btc_signer5 = require("@scure/btc-signer");
6433
+ var import_btc_signer8 = require("@scure/btc-signer");
5425
6434
 
5426
6435
  // src/utils/appendix.ts
5427
6436
  var import_dids = require("@web5/dids");
@@ -6526,10 +7535,10 @@ var BeaconUtils = class {
6526
7535
  static createBeaconService(did, addressType, beaconType) {
6527
7536
  try {
6528
7537
  const components = Identifier.decode(did);
6529
- const network = (0, import_bitcoin2.getNetwork)(components.network);
7538
+ const network = (0, import_bitcoin4.getNetwork)(components.network);
6530
7539
  const pubkey = components.genesisBytes;
6531
7540
  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;
7541
+ 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
7542
  const serviceEndpoint = `bitcoin:${address}`;
6534
7543
  return { id, type: beaconType, serviceEndpoint };
6535
7544
  } catch (error) {
@@ -6548,9 +7557,9 @@ var BeaconUtils = class {
6548
7557
  */
6549
7558
  static generateBeaconServices({ id, publicKey, network, beaconType }) {
6550
7559
  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;
7560
+ const p2pkhAddr = (0, import_btc_signer8.p2pkh)(publicKey, network).address;
7561
+ const p2wpkhAddr = (0, import_btc_signer8.p2wpkh)(publicKey, network).address;
7562
+ const p2trAddr = (0, import_btc_signer8.p2tr)(publicKey.slice(1, 33), void 0, network).address;
6554
7563
  if (!p2pkhAddr || !p2wpkhAddr || !p2trAddr) {
6555
7564
  throw new import_common17.DidMethodError("Failed to generate bitcoin addresses");
6556
7565
  }
@@ -6677,14 +7686,14 @@ var BeaconSignalDiscovery = class {
6677
7686
  console.info(`Searching for beacon signals, please wait ...`);
6678
7687
  while (block.height <= targetHeight) {
6679
7688
  for (const tx of block.tx) {
6680
- if (tx.txid === import_bitcoin3.GENESIS_TX_ID) {
7689
+ if (tx.txid === import_bitcoin5.GENESIS_TX_ID) {
6681
7690
  continue;
6682
7691
  }
6683
7692
  for (const vin of tx.vin) {
6684
7693
  if (vin.coinbase) {
6685
7694
  continue;
6686
7695
  }
6687
- if (vin.txinwitness && vin.txinwitness.length === 1 && vin.txinwitness[0] === import_bitcoin3.TXIN_WITNESS_COINBASE) {
7696
+ if (vin.txinwitness && vin.txinwitness.length === 1 && vin.txinwitness[0] === import_bitcoin5.TXIN_WITNESS_COINBASE) {
6688
7697
  continue;
6689
7698
  }
6690
7699
  if (!vin.txid) {
@@ -6738,7 +7747,7 @@ var BeaconSignalDiscovery = class {
6738
7747
  };
6739
7748
 
6740
7749
  // src/core/resolver.ts
6741
- var import_bitcoin5 = require("@did-btcr2/bitcoin");
7750
+ var import_bitcoin7 = require("@did-btcr2/bitcoin");
6742
7751
  var import_common22 = require("@did-btcr2/common");
6743
7752
  var import_cryptosuite3 = require("@did-btcr2/cryptosuite");
6744
7753
  var import_keypair8 = require("@did-btcr2/keypair");
@@ -6752,11 +7761,11 @@ var import_common20 = require("@did-btcr2/common");
6752
7761
  var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
6753
7762
 
6754
7763
  // src/utils/did-document.ts
6755
- var import_bitcoin4 = require("@did-btcr2/bitcoin");
7764
+ var import_bitcoin6 = require("@did-btcr2/bitcoin");
6756
7765
  var import_common19 = require("@did-btcr2/common");
6757
7766
  var import_keypair7 = require("@did-btcr2/keypair");
6758
- var import_utils11 = require("@web5/dids/utils");
6759
- var import_btc_signer6 = require("@scure/btc-signer");
7767
+ var import_utils13 = require("@web5/dids/utils");
7768
+ var import_btc_signer9 = require("@scure/btc-signer");
6760
7769
  var BTCR2_DID_DOCUMENT_CONTEXT = [
6761
7770
  "https://www.w3.org/ns/did/v1.1",
6762
7771
  "https://btcr2.dev/context/v1"
@@ -6965,7 +7974,7 @@ var DidDocument = class _DidDocument {
6965
7974
  * @returns {boolean} True if the services are valid.
6966
7975
  */
6967
7976
  static isValidServices(service) {
6968
- return Array.isArray(service) && service.every(import_utils11.isDidService);
7977
+ return Array.isArray(service) && service.every(import_utils13.isDidService);
6969
7978
  }
6970
7979
  /**
6971
7980
  * Validates verification relationships (authentication, assertionMethod, capabilityInvocation, capabilityDelegation).
@@ -7078,7 +8087,7 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
7078
8087
  static fromPublicKey(publicKey, network) {
7079
8088
  const pk = new import_keypair7.CompressedSecp256k1PublicKey(publicKey);
7080
8089
  const id = ID_PLACEHOLDER_VALUE;
7081
- const address = (0, import_btc_signer6.p2pkh)(pk.compressed, (0, import_bitcoin4.getNetwork)(network)).address;
8090
+ const address = (0, import_btc_signer9.p2pkh)(pk.compressed, (0, import_bitcoin6.getNetwork)(network)).address;
7082
8091
  const services = [{
7083
8092
  id: `${id}#service-0`,
7084
8093
  serviceEndpoint: `bitcoin:${address}`,
@@ -7127,7 +8136,7 @@ var Updater = class _Updater {
7127
8136
  #verificationMethod;
7128
8137
  #beaconService;
7129
8138
  /**
7130
- * @internal Use {@link DidBtcr2.update} to create instances.
8139
+ * @internal Use {@link DidBtcr2.update} to create instances.
7131
8140
  */
7132
8141
  constructor(params) {
7133
8142
  this.#sourceDocument = params.sourceDocument;
@@ -7164,7 +8173,7 @@ var Updater = class _Updater {
7164
8173
  const targetDocument = import_common20.JSONPatch.apply(sourceDocument, patches);
7165
8174
  if (targetDocument.id !== sourceDocument.id) {
7166
8175
  throw new import_common20.UpdateError(
7167
- `Patches must not change the DID document id (source "${sourceDocument.id}" \u2192 target "${targetDocument.id}").`,
8176
+ `Patches must not change the DID document id (source "${sourceDocument.id}" to target "${targetDocument.id}").`,
7168
8177
  import_common20.INVALID_DID_UPDATE,
7169
8178
  { sourceId: sourceDocument.id, targetId: targetDocument.id }
7170
8179
  );
@@ -7248,8 +8257,8 @@ var Updater = class _Updater {
7248
8257
  }
7249
8258
  /**
7250
8259
  * 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
8260
+ * - `{ status: 'action-required', needs }` caller must provide data via {@link provide}
8261
+ * - `{ status: 'complete', result }` update is signed and broadcast
7253
8262
  */
7254
8263
  advance() {
7255
8264
  while (true) {
@@ -7262,7 +8271,7 @@ var Updater = class _Updater {
7262
8271
  continue;
7263
8272
  }
7264
8273
  // Phase: Sign
7265
- // Emit NeedSigningKey the caller supplies the secret key (or a KMS signature).
8274
+ // Emit NeedSigningKey: the caller supplies the secret key (or a KMS signature).
7266
8275
  case "Sign": {
7267
8276
  return {
7268
8277
  status: "action-required",
@@ -7445,8 +8454,8 @@ var DidBtcr2 = class {
7445
8454
  *
7446
8455
  * Factory method that validates the update parameters and returns a sans-I/O
7447
8456
  * {@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
8457
+ * phases (Construct -> Sign -> Broadcast -> Complete) by calling `advance()` and
8458
+ * `provide()`. The method package performs **zero I/O**: signing key retrieval
7450
8459
  * (or KMS delegation) and the on-chain broadcast are the caller's responsibility.
7451
8460
  *
7452
8461
  * For a fully-wired version with Bitcoin broadcast and key handling, see
@@ -7545,7 +8554,7 @@ var DidBtcr2 = class {
7545
8554
  };
7546
8555
 
7547
8556
  // src/core/resolver.ts
7548
- var import_utils13 = require("@noble/curves/utils.js");
8557
+ var import_utils15 = require("@noble/curves/utils.js");
7549
8558
  var Resolver = class _Resolver {
7550
8559
  // --- Immutable inputs ---
7551
8560
  #didComponents;
@@ -7564,7 +8573,7 @@ var Resolver = class _Resolver {
7564
8573
  #unsortedUpdates = [];
7565
8574
  #resolvedResponse = null;
7566
8575
  /**
7567
- * @internal Use {@link DidBtcr2.resolve} to create instances.
8576
+ * @internal Use {@link DidBtcr2.resolve} to create instances.
7568
8577
  */
7569
8578
  constructor(didComponents, sidecarData, currentDocument, options) {
7570
8579
  this.#didComponents = didComponents;
@@ -7589,7 +8598,7 @@ var Resolver = class _Resolver {
7589
8598
  const service = BeaconUtils.generateBeaconServices({
7590
8599
  id: did,
7591
8600
  publicKey: genesisBytes,
7592
- network: (0, import_bitcoin5.getNetwork)(didComponents.network),
8601
+ network: (0, import_bitcoin7.getNetwork)(didComponents.network),
7593
8602
  beaconType: "SingletonBeacon"
7594
8603
  });
7595
8604
  return new DidDocument({
@@ -7612,7 +8621,7 @@ var Resolver = class _Resolver {
7612
8621
  */
7613
8622
  static external(didComponents, genesisDocument) {
7614
8623
  const genesisDocumentHash = (0, import_common22.canonicalHashBytes)(genesisDocument);
7615
- if (!(0, import_utils13.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
8624
+ if (!(0, import_utils15.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
7616
8625
  throw new import_common22.ResolveError(
7617
8626
  `Initial document mismatch: genesisBytes !== genesisDocumentHash`,
7618
8627
  import_common22.INVALID_DID_DOCUMENT,
@@ -7689,7 +8698,7 @@ var Resolver = class _Resolver {
7689
8698
  this.confirmDuplicate(update, updateHashHistory);
7690
8699
  } else if (update.targetVersionId === currentVersionId + 1) {
7691
8700
  const sourceHashBytes = (0, import_common22.decode)(update.sourceHash, "base64urlnopad");
7692
- if (!(0, import_utils13.equalBytes)(sourceHashBytes, currentDocumentHash)) {
8701
+ if (!(0, import_utils15.equalBytes)(sourceHashBytes, currentDocumentHash)) {
7693
8702
  throw new import_common22.ResolveError(
7694
8703
  `Hash mismatch: update.sourceHash !== currentDocumentHash`,
7695
8704
  import_common22.INVALID_DID_UPDATE,
@@ -7736,7 +8745,7 @@ var Resolver = class _Resolver {
7736
8745
  const { proof: _, ...unsignedUpdate } = update;
7737
8746
  const unsignedUpdateHash = (0, import_common22.canonicalHashBytes)(unsignedUpdate);
7738
8747
  const historicalUpdateHash = updateHashHistory[update.targetVersionId - 2];
7739
- if (!(0, import_utils13.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
8748
+ if (!(0, import_utils15.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
7740
8749
  throw new import_common22.ResolveError(
7741
8750
  `Invalid duplicate: unsigned update hash does not match historical hash`,
7742
8751
  import_common22.LATE_PUBLISHING_ERROR,
@@ -7789,7 +8798,7 @@ var Resolver = class _Resolver {
7789
8798
  DidDocument.validate(updatedDocument);
7790
8799
  const currentDocumentHash = (0, import_common22.canonicalHashBytes)(updatedDocument);
7791
8800
  const updateTargetHash = (0, import_common22.decode)(update.targetHash);
7792
- if (!(0, import_utils13.equalBytes)(updateTargetHash, currentDocumentHash)) {
8801
+ if (!(0, import_utils15.equalBytes)(updateTargetHash, currentDocumentHash)) {
7793
8802
  throw new import_common22.ResolveError(
7794
8803
  `Invalid update: update.targetHash !== currentDocumentHash`,
7795
8804
  import_common22.INVALID_DID_UPDATE,
@@ -7800,8 +8809,8 @@ var Resolver = class _Resolver {
7800
8809
  }
7801
8810
  /**
7802
8811
  * Advance the state machine. Returns either:
7803
- * - `{ status: 'action-required', needs }` caller must provide data via {@link provide}
7804
- * - `{ status: 'resolved', result }` resolution complete
8812
+ * - `{ status: 'action-required', needs }` - caller must provide data via {@link provide}
8813
+ * - `{ status: 'resolved', result }` - resolution complete
7805
8814
  *
7806
8815
  * Analogous to Rust's `Resolver::resolve()`.
7807
8816
  */
@@ -8031,6 +9040,7 @@ var DidDocumentBuilder = class {
8031
9040
  BeaconUtils,
8032
9041
  CASBeacon,
8033
9042
  CASBeaconError,
9043
+ CHANGE_OUTPUT_VBYTES,
8034
9044
  COHORT_ADVERT,
8035
9045
  COHORT_OPT_IN,
8036
9046
  COHORT_OPT_IN_ACCEPT,
@@ -8039,17 +9049,23 @@ var DidDocumentBuilder = class {
8039
9049
  DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
8040
9050
  DEFAULT_BROADCAST_LOOKBACK_MS,
8041
9051
  DEFAULT_CLOCK_SKEW_SEC,
9052
+ DEFAULT_FEE_ESTIMATOR,
9053
+ DEFAULT_FUNDING_MODEL,
8042
9054
  DEFAULT_MAX_UPDATE_SIZE_BYTES,
8043
9055
  DEFAULT_NONCE_LEN_BYTES,
8044
9056
  DEFAULT_NOSTR_RELAYS,
9057
+ DEFAULT_RECOVERY_SEQUENCE,
8045
9058
  DID_REGEX,
8046
9059
  DISTRIBUTE_AGGREGATED_DATA,
9060
+ DUST_LIMIT_SATS,
8047
9061
  DidBtcr2,
8048
9062
  DidCommTransport,
8049
9063
  DidDocument,
8050
9064
  DidDocumentBuilder,
8051
9065
  DidVerificationMethod,
8052
9066
  Document,
9067
+ FALLBACK_AUTHORIZATION_REQUEST,
9068
+ FALLBACK_SIGNATURE,
8053
9069
  GenesisDocument,
8054
9070
  HTTP_ENVELOPE_VERSION,
8055
9071
  HTTP_ROUTE,
@@ -8063,7 +9079,9 @@ var DidDocumentBuilder = class {
8063
9079
  InMemoryTransport,
8064
9080
  InboxBuffer,
8065
9081
  KNOWN_BEACON_TYPES,
9082
+ KNOWN_FUNDING_MODELS,
8066
9083
  KeyPairAggregationSigner,
9084
+ MAX_RECOVERY_SEQUENCE,
8067
9085
  NONCE_CONTRIBUTION,
8068
9086
  NonceCache,
8069
9087
  NostrTransport,
@@ -8080,6 +9098,7 @@ var DidDocumentBuilder = class {
8080
9098
  SMTBeacon,
8081
9099
  SMTBeaconError,
8082
9100
  SSE_EVENT,
9101
+ SUBMIT_NONINCLUDED,
8083
9102
  SUBMIT_UPDATE,
8084
9103
  ServiceCohortPhase,
8085
9104
  SigningSessionError,
@@ -8088,13 +9107,20 @@ var DidDocumentBuilder = class {
8088
9107
  SingletonBeacon,
8089
9108
  SingletonBeaconError,
8090
9109
  StaticFeeEstimator,
9110
+ TAPROOT_LEAF_VERSION,
8091
9111
  TransportAdapterError,
8092
9112
  TransportError,
8093
9113
  TransportFactory,
8094
9114
  TypedEventEmitter,
8095
9115
  Updater,
8096
9116
  VALIDATION_ACK,
9117
+ beaconTxVsize,
8097
9118
  buildAggregationBeaconTx,
9119
+ buildFallbackLeaf,
9120
+ buildFallbackSpend,
9121
+ buildRecoveryLeaves,
9122
+ buildRecoveryScript,
9123
+ buildRecoverySpend,
8098
9124
  buildRequestAuth,
8099
9125
  createAggregatedNonceMessage,
8100
9126
  createAuthorizationRequestMessage,
@@ -8103,13 +9129,17 @@ var DidDocumentBuilder = class {
8103
9129
  createCohortOptInMessage,
8104
9130
  createCohortReadyMessage,
8105
9131
  createDistributeAggregatedDataMessage,
9132
+ createFallbackAuthorizationRequestMessage,
9133
+ createFallbackSignatureMessage,
8106
9134
  createNonceContributionMessage,
8107
9135
  createSignatureAuthorizationMessage,
9136
+ createSubmitNonIncludedMessage,
8108
9137
  createSubmitUpdateMessage,
8109
9138
  createValidationAckMessage,
8110
9139
  defaultReconnectBackoff,
8111
9140
  deriveSingletonAddress,
8112
9141
  detectSingletonScriptKind,
9142
+ fallbackSighash,
8113
9143
  formatSseComment,
8114
9144
  formatSseEvent,
8115
9145
  getBeaconStrategy,
@@ -8121,10 +9151,13 @@ var DidDocumentBuilder = class {
8121
9151
  isCohortOptInMessage,
8122
9152
  isCohortReadyMessage,
8123
9153
  isDistributeAggregatedDataMessage,
9154
+ isFallbackAuthorizationRequestMessage,
9155
+ isFallbackSignatureMessage,
8124
9156
  isKeygenMessageType,
8125
9157
  isNonceContributionMessage,
8126
9158
  isSignMessageType,
8127
9159
  isSignatureAuthorizationMessage,
9160
+ isSubmitNonIncludedMessage,
8128
9161
  isSubmitUpdateMessage,
8129
9162
  isUpdateMessageType,
8130
9163
  isValidationAckMessage,
@@ -8133,8 +9166,11 @@ var DidDocumentBuilder = class {
8133
9166
  parseRequestAuth,
8134
9167
  parseSseStream,
8135
9168
  registerBeaconStrategy,
9169
+ resolveChangeAddress,
9170
+ resolveFallbackThreshold,
8136
9171
  reviveFromWire,
8137
9172
  signEnvelope,
9173
+ tapLeafHash,
8138
9174
  validateCohortConditions,
8139
9175
  verifyEnvelope,
8140
9176
  verifyRequestAuth