@did-btcr2/method 0.36.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/browser.js +1371 -260
  3. package/dist/browser.mjs +1371 -260
  4. package/dist/cjs/index.js +1464 -343
  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 +218 -18
  23. package/dist/esm/core/aggregation/participant.js.map +1 -1
  24. package/dist/esm/core/aggregation/phases.js +6 -0
  25. package/dist/esm/core/aggregation/phases.js.map +1 -1
  26. package/dist/esm/core/aggregation/recovery-policy.js +156 -0
  27. package/dist/esm/core/aggregation/recovery-policy.js.map +1 -0
  28. package/dist/esm/core/aggregation/recovery-spend.js +106 -0
  29. package/dist/esm/core/aggregation/recovery-spend.js.map +1 -0
  30. package/dist/esm/core/aggregation/runner/aggregation-runner.js +11 -3
  31. package/dist/esm/core/aggregation/runner/aggregation-runner.js.map +1 -1
  32. package/dist/esm/core/aggregation/runner/participant-runner.js +106 -19
  33. package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -1
  34. package/dist/esm/core/aggregation/runner/service-runner.js +409 -195
  35. package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -1
  36. package/dist/esm/core/aggregation/service.js +221 -11
  37. package/dist/esm/core/aggregation/service.js.map +1 -1
  38. package/dist/esm/core/aggregation/signing-session.js +3 -3
  39. package/dist/esm/core/aggregation/signing-session.js.map +1 -1
  40. package/dist/esm/core/aggregation/transport/http/envelope.js +3 -3
  41. package/dist/esm/core/aggregation/transport/http/inbox-buffer.js +1 -1
  42. package/dist/esm/core/aggregation/transport/http/nonce-cache.js +1 -1
  43. package/dist/esm/core/aggregation/transport/http/rate-limiter.js +1 -1
  44. package/dist/esm/core/aggregation/transport/http/request-auth.js +1 -1
  45. package/dist/esm/core/aggregation/transport/http/server.js +1 -1
  46. package/dist/esm/core/aggregation/transport/http/sse-stream.js +1 -1
  47. package/dist/esm/core/aggregation/transport/http/sse-writer.js +1 -1
  48. package/dist/esm/core/aggregation/transport/in-memory.js +1 -1
  49. package/dist/esm/core/aggregation/transport/nostr.js +6 -6
  50. package/dist/esm/core/beacon/beacon.js +15 -10
  51. package/dist/esm/core/beacon/beacon.js.map +1 -1
  52. package/dist/esm/core/beacon/cas-beacon.js +6 -6
  53. package/dist/esm/core/beacon/cas-beacon.js.map +1 -1
  54. package/dist/esm/core/beacon/singleton-beacon.js +2 -2
  55. package/dist/esm/core/beacon/singleton-beacon.js.map +1 -1
  56. package/dist/esm/core/beacon/smt-beacon.js +3 -3
  57. package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
  58. package/dist/esm/core/resolver.js +8 -8
  59. package/dist/esm/core/resolver.js.map +1 -1
  60. package/dist/esm/core/updater.js +6 -6
  61. package/dist/esm/core/updater.js.map +1 -1
  62. package/dist/esm/did-btcr2.js +2 -2
  63. package/dist/esm/index.js +3 -0
  64. package/dist/esm/index.js.map +1 -1
  65. package/dist/types/core/aggregation/beacon-strategy.d.ts +8 -4
  66. package/dist/types/core/aggregation/beacon-strategy.d.ts.map +1 -1
  67. package/dist/types/core/aggregation/cohort.d.ts +88 -17
  68. package/dist/types/core/aggregation/cohort.d.ts.map +1 -1
  69. package/dist/types/core/aggregation/conditions.d.ts +28 -0
  70. package/dist/types/core/aggregation/conditions.d.ts.map +1 -1
  71. package/dist/types/core/aggregation/fallback-spend.d.ts +94 -0
  72. package/dist/types/core/aggregation/fallback-spend.d.ts.map +1 -0
  73. package/dist/types/core/aggregation/logger.d.ts +1 -1
  74. package/dist/types/core/aggregation/messages/base.d.ts +6 -0
  75. package/dist/types/core/aggregation/messages/base.d.ts.map +1 -1
  76. package/dist/types/core/aggregation/messages/bodies.d.ts +50 -2
  77. package/dist/types/core/aggregation/messages/bodies.d.ts.map +1 -1
  78. package/dist/types/core/aggregation/messages/constants.d.ts +8 -0
  79. package/dist/types/core/aggregation/messages/constants.d.ts.map +1 -1
  80. package/dist/types/core/aggregation/messages/factories.d.ts +40 -0
  81. package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -1
  82. package/dist/types/core/aggregation/messages/guards.d.ts.map +1 -1
  83. package/dist/types/core/aggregation/participant.d.ts +47 -4
  84. package/dist/types/core/aggregation/participant.d.ts.map +1 -1
  85. package/dist/types/core/aggregation/phases.d.ts +8 -2
  86. package/dist/types/core/aggregation/phases.d.ts.map +1 -1
  87. package/dist/types/core/aggregation/recovery-policy.d.ts +132 -0
  88. package/dist/types/core/aggregation/recovery-policy.d.ts.map +1 -0
  89. package/dist/types/core/aggregation/recovery-spend.d.ts +94 -0
  90. package/dist/types/core/aggregation/recovery-spend.d.ts.map +1 -0
  91. package/dist/types/core/aggregation/runner/aggregation-runner.d.ts +13 -3
  92. package/dist/types/core/aggregation/runner/aggregation-runner.d.ts.map +1 -1
  93. package/dist/types/core/aggregation/runner/events.d.ts +48 -16
  94. package/dist/types/core/aggregation/runner/events.d.ts.map +1 -1
  95. package/dist/types/core/aggregation/runner/participant-runner.d.ts +23 -14
  96. package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
  97. package/dist/types/core/aggregation/runner/service-runner.d.ts +102 -23
  98. package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -1
  99. package/dist/types/core/aggregation/service.d.ts +26 -2
  100. package/dist/types/core/aggregation/service.d.ts.map +1 -1
  101. package/dist/types/core/aggregation/signing-session.d.ts +2 -2
  102. package/dist/types/core/aggregation/signing-session.d.ts.map +1 -1
  103. package/dist/types/core/aggregation/transport/http/envelope.d.ts +3 -3
  104. package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts +1 -1
  105. package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts +1 -1
  106. package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts +1 -1
  107. package/dist/types/core/aggregation/transport/http/request-auth.d.ts +1 -1
  108. package/dist/types/core/aggregation/transport/http/server.d.ts +1 -1
  109. package/dist/types/core/aggregation/transport/http/sse-stream.d.ts +2 -2
  110. package/dist/types/core/aggregation/transport/http/sse-writer.d.ts +1 -1
  111. package/dist/types/core/aggregation/transport/in-memory.d.ts +1 -1
  112. package/dist/types/core/aggregation/transport/nostr.d.ts +4 -4
  113. package/dist/types/core/aggregation/transport/transport.d.ts +4 -4
  114. package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -1
  115. package/dist/types/core/beacon/beacon.d.ts +7 -7
  116. package/dist/types/core/beacon/beacon.d.ts.map +1 -1
  117. package/dist/types/core/beacon/cas-beacon.d.ts +2 -2
  118. package/dist/types/core/beacon/interfaces.d.ts +1 -1
  119. package/dist/types/core/resolver.d.ts +3 -3
  120. package/dist/types/core/updater.d.ts +4 -4
  121. package/dist/types/did-btcr2.d.ts +2 -2
  122. package/dist/types/index.d.ts +3 -0
  123. package/dist/types/index.d.ts.map +1 -1
  124. package/package.json +4 -4
  125. package/src/core/aggregation/beacon-strategy.ts +27 -9
  126. package/src/core/aggregation/cohort.ts +171 -35
  127. package/src/core/aggregation/conditions.ts +56 -0
  128. package/src/core/aggregation/fallback-spend.ts +223 -0
  129. package/src/core/aggregation/logger.ts +1 -1
  130. package/src/core/aggregation/messages/base.ts +6 -0
  131. package/src/core/aggregation/messages/bodies.ts +94 -1
  132. package/src/core/aggregation/messages/constants.ts +12 -0
  133. package/src/core/aggregation/messages/factories.ts +57 -0
  134. package/src/core/aggregation/messages/guards.ts +6 -0
  135. package/src/core/aggregation/participant.ts +264 -20
  136. package/src/core/aggregation/phases.ts +9 -0
  137. package/src/core/aggregation/recovery-policy.ts +237 -0
  138. package/src/core/aggregation/recovery-spend.ts +192 -0
  139. package/src/core/aggregation/runner/aggregation-runner.ts +27 -5
  140. package/src/core/aggregation/runner/events.ts +47 -19
  141. package/src/core/aggregation/runner/participant-runner.ts +113 -31
  142. package/src/core/aggregation/runner/service-runner.ts +484 -203
  143. package/src/core/aggregation/service.ts +260 -14
  144. package/src/core/aggregation/signing-session.ts +3 -3
  145. package/src/core/aggregation/transport/http/envelope.ts +3 -3
  146. package/src/core/aggregation/transport/http/inbox-buffer.ts +1 -1
  147. package/src/core/aggregation/transport/http/nonce-cache.ts +1 -1
  148. package/src/core/aggregation/transport/http/rate-limiter.ts +1 -1
  149. package/src/core/aggregation/transport/http/request-auth.ts +1 -1
  150. package/src/core/aggregation/transport/http/server.ts +1 -1
  151. package/src/core/aggregation/transport/http/sse-stream.ts +2 -2
  152. package/src/core/aggregation/transport/http/sse-writer.ts +1 -1
  153. package/src/core/aggregation/transport/in-memory.ts +1 -1
  154. package/src/core/aggregation/transport/nostr.ts +6 -6
  155. package/src/core/aggregation/transport/transport.ts +4 -4
  156. package/src/core/beacon/beacon.ts +16 -11
  157. package/src/core/beacon/cas-beacon.ts +7 -7
  158. package/src/core/beacon/interfaces.ts +1 -1
  159. package/src/core/beacon/singleton-beacon.ts +2 -2
  160. package/src/core/beacon/smt-beacon.ts +3 -3
  161. package/src/core/resolver.ts +8 -8
  162. package/src/core/updater.ts +6 -6
  163. package/src/did-btcr2.ts +2 -2
  164. package/src/index.ts +3 -0
package/dist/browser.mjs CHANGED
@@ -100123,7 +100123,7 @@ var SchnorrMultikey = class _SchnorrMultikey {
100123
100123
  * @readonly
100124
100124
  * Get signing ability of the Multikey: true if a local Secp256k1SecretKey
100125
100125
  * is present (note: the SchnorrKeyPair.secretKey getter throws when absent;
100126
- * that throw is the historical error contract see multikey tests) or if
100126
+ * that throw is the historical error contract - see multikey tests) or if
100127
100127
  * an external signer is available.
100128
100128
  */
100129
100129
  get signer() {
@@ -100228,6 +100228,7 @@ var SchnorrMultikey = class _SchnorrMultikey {
100228
100228
  init_shim();
100229
100229
 
100230
100230
  // src/core/aggregation/service.ts
100231
+ init_secp256k1();
100231
100232
  init_utils();
100232
100233
 
100233
100234
  // src/core/aggregation/beacon-strategy.ts
@@ -100528,9 +100529,12 @@ var CAS_STRATEGY = {
100528
100529
  getDistributePayload(cohort) {
100529
100530
  return { casAnnouncement: cohort.casAnnouncement };
100530
100531
  },
100531
- validateParticipantView({ participantDid, expectedHash, body }) {
100532
+ validateParticipantView({ participantDid, included, expectedHash, body }) {
100532
100533
  const casAnnouncement = body.casAnnouncement;
100533
100534
  if (!casAnnouncement) return { matches: false };
100535
+ if (!included) {
100536
+ return { matches: !(participantDid in casAnnouncement), casAnnouncement };
100537
+ }
100534
100538
  return {
100535
100539
  matches: casAnnouncement[participantDid] === expectedHash,
100536
100540
  casAnnouncement
@@ -100546,15 +100550,20 @@ var SMT_STRATEGY = {
100546
100550
  const proof = cohort.smtProofs?.get(participantDid);
100547
100551
  return { smtProof: proof };
100548
100552
  },
100549
- validateParticipantView({ participantDid, submittedUpdate, body }) {
100553
+ validateParticipantView({ participantDid, included, submittedUpdate, body }) {
100550
100554
  const smtProof = body.smtProof;
100551
- if (!smtProof?.updateId || !smtProof?.nonce) return { matches: false };
100555
+ const index = didToIndex(participantDid);
100556
+ if (!included) {
100557
+ if (!smtProof?.nonce || smtProof?.updateId) return { matches: false, smtProof };
100558
+ const candidateHash2 = blockHash(blockHash(base64UrlToHash(smtProof.nonce)));
100559
+ return { matches: verifySerializedProof(smtProof, index, candidateHash2), smtProof };
100560
+ }
100561
+ if (!smtProof?.updateId || !smtProof?.nonce || !submittedUpdate) return { matches: false, smtProof };
100552
100562
  const canonicalBytes = new TextEncoder().encode(canonicalize2(submittedUpdate));
100553
100563
  const expectedUpdateId = hashToBase64Url(blockHash(canonicalBytes));
100554
100564
  if (smtProof.updateId !== expectedUpdateId) {
100555
100565
  return { matches: false, smtProof };
100556
100566
  }
100557
- const index = didToIndex(participantDid);
100558
100567
  const candidateHash = blockHash(blockHash(base64UrlToHash(smtProof.nonce)), base64UrlToHash(smtProof.updateId));
100559
100568
  return {
100560
100569
  matches: verifySerializedProof(smtProof, index, candidateHash),
@@ -101553,6 +101562,15 @@ function checkScript(script, redeemScript, witnessScript) {
101553
101562
  checkWSH(r2, witnessScript);
101554
101563
  }
101555
101564
  }
101565
+ function uniqPubkey(pubkeys) {
101566
+ const map3 = {};
101567
+ for (const pub of pubkeys) {
101568
+ const key = hex.encode(pub);
101569
+ if (map3[key])
101570
+ throw new Error(`Multisig: non-uniq pubkey: ${pubkeys.map(hex.encode)}`);
101571
+ map3[key] = true;
101572
+ }
101573
+ }
101556
101574
  var p2pkh = (publicKey2, network = NETWORK) => {
101557
101575
  if (!isValidPubkey(publicKey2, PubT.ecdsa))
101558
101576
  throw new Error("P2PKH: invalid publicKey");
@@ -101725,6 +101743,14 @@ function p2tr(internalPubKey, tree2, network = NETWORK, allowUnknownOutputs = fa
101725
101743
  };
101726
101744
  }
101727
101745
  }
101746
+ function p2tr_ms(m2, pubkeys, allowSamePubkeys = false) {
101747
+ if (!allowSamePubkeys)
101748
+ uniqPubkey(pubkeys);
101749
+ return {
101750
+ type: "tr_ms",
101751
+ script: OutScript.encode({ type: "tr_ms", pubkeys, m: m2 })
101752
+ };
101753
+ }
101728
101754
  var base58check = createBase58check(sha256);
101729
101755
  function validateWitness(version3, data) {
101730
101756
  if (data.length < 2 || data.length > 40)
@@ -103200,6 +103226,104 @@ var SigningSessionError = class extends MethodError {
103200
103226
  }
103201
103227
  };
103202
103228
 
103229
+ // src/core/aggregation/recovery-policy.ts
103230
+ init_shim();
103231
+ init_secp256k1();
103232
+ init_utils();
103233
+ var DEFAULT_FUNDING_MODEL = "operator-funded";
103234
+ var DEFAULT_RECOVERY_SEQUENCE = 144;
103235
+ var MAX_RECOVERY_SEQUENCE = 65535;
103236
+ var TAPROOT_LEAF_VERSION = 192;
103237
+ function compactSize(n2) {
103238
+ if (n2 < 253) return new Uint8Array([n2]);
103239
+ if (n2 <= 65535) return new Uint8Array([253, n2 & 255, n2 >> 8 & 255]);
103240
+ return new Uint8Array([254, n2 & 255, n2 >> 8 & 255, n2 >> 16 & 255, n2 >> 24 & 255]);
103241
+ }
103242
+ function tapLeafHash2(script, leafVersion = TAPROOT_LEAF_VERSION) {
103243
+ return schnorr2.utils.taggedHash(
103244
+ "TapLeaf",
103245
+ concatBytes(new Uint8Array([leafVersion]), compactSize(script.length), script)
103246
+ );
103247
+ }
103248
+ function resolveFallbackThreshold(advertised, n2) {
103249
+ return advertised ?? Math.max(1, n2 - 1);
103250
+ }
103251
+ function assertFallbackParams({ cohortKeys, fallbackThreshold }) {
103252
+ const n2 = cohortKeys.length;
103253
+ if (n2 === 0) {
103254
+ throw new AggregationCohortError(
103255
+ "Cannot build fallback leaf: no cohort keys.",
103256
+ "NO_COHORT_KEYS"
103257
+ );
103258
+ }
103259
+ for (const key of cohortKeys) {
103260
+ if (key.length !== 33) {
103261
+ throw new AggregationCohortError(
103262
+ `Cohort key must be a 33-byte compressed public key, got ${key.length} bytes.`,
103263
+ "INVALID_COHORT_KEY",
103264
+ { length: key.length }
103265
+ );
103266
+ }
103267
+ }
103268
+ if (!Number.isInteger(fallbackThreshold) || fallbackThreshold < 1 || fallbackThreshold > n2) {
103269
+ throw new AggregationCohortError(
103270
+ `Fallback threshold must be an integer in [1, ${n2}] (k-of-n), got ${fallbackThreshold}.`,
103271
+ "INVALID_FALLBACK_THRESHOLD",
103272
+ { fallbackThreshold, n: n2 }
103273
+ );
103274
+ }
103275
+ }
103276
+ function buildFallbackLeaf(params) {
103277
+ assertFallbackParams(params);
103278
+ const xOnlyKeys = sortKeys(params.cohortKeys).map((k) => k.slice(1));
103279
+ return p2tr_ms(params.fallbackThreshold, xOnlyKeys).script;
103280
+ }
103281
+ function assertRecoveryParams({ recoveryKey, recoverySequence }) {
103282
+ if (recoveryKey.length !== 32) {
103283
+ throw new AggregationCohortError(
103284
+ `Recovery key must be a 32-byte x-only public key, got ${recoveryKey.length} bytes.`,
103285
+ "INVALID_RECOVERY_KEY"
103286
+ );
103287
+ }
103288
+ if (!Number.isInteger(recoverySequence) || recoverySequence < 1 || recoverySequence > MAX_RECOVERY_SEQUENCE) {
103289
+ throw new AggregationCohortError(
103290
+ `Recovery sequence must be a block-based BIP-68 relative timelock in [1, ${MAX_RECOVERY_SEQUENCE}], got ${recoverySequence}.`,
103291
+ "INVALID_RECOVERY_SEQUENCE"
103292
+ );
103293
+ }
103294
+ }
103295
+ function buildRecoveryScript(params) {
103296
+ assertRecoveryParams(params);
103297
+ return Script.encode([
103298
+ params.recoverySequence,
103299
+ "CHECKSEQUENCEVERIFY",
103300
+ "DROP",
103301
+ params.recoveryKey,
103302
+ "CHECKSIG"
103303
+ ]);
103304
+ }
103305
+ function buildRecoveryLeaves(fundingModel, params) {
103306
+ switch (fundingModel) {
103307
+ case "operator-funded":
103308
+ return [
103309
+ { script: buildFallbackLeaf(params) },
103310
+ { script: buildRecoveryScript(params) }
103311
+ ];
103312
+ case "participant-funded":
103313
+ throw new AggregationCohortError(
103314
+ "Funding model 'participant-funded' is reserved and not yet implemented.",
103315
+ "UNSUPPORTED_FUNDING_MODEL",
103316
+ { fundingModel }
103317
+ );
103318
+ default:
103319
+ throw new AggregationCohortError(
103320
+ `Unknown funding model: ${fundingModel}.`,
103321
+ "UNKNOWN_FUNDING_MODEL",
103322
+ { fundingModel }
103323
+ );
103324
+ }
103325
+ }
103326
+
103203
103327
  // src/core/aggregation/cohort.ts
103204
103328
  var AggregationCohort = class {
103205
103329
  /** Unique identifier for the cohort. */
@@ -103215,8 +103339,8 @@ var AggregationCohort = class {
103215
103339
  /** List of participant DIDs that have been accepted into the cohort. */
103216
103340
  participants = [];
103217
103341
  /**
103218
- * Mapping from participant DID their compressed secp256k1 public key.
103219
- * Distinct from {@link cohortKeys} (which is sorted per BIP-327) this lets
103342
+ * Mapping from participant DID to their compressed secp256k1 public key.
103343
+ * Distinct from {@link cohortKeys} (which is sorted per BIP-327): this lets
103220
103344
  * callers look up a participant's key without knowing their position in the
103221
103345
  * sorted array. Populated by the service at `acceptParticipant` time.
103222
103346
  */
@@ -103224,16 +103348,41 @@ var AggregationCohort = class {
103224
103348
  /** Sorted list of cohort participants' compressed public keys. */
103225
103349
  #cohortKeys = [];
103226
103350
  /**
103227
- * BIP-341 TapTweak `taggedHash("TapTweak", internalPubkey)` for a key-path-only
103228
- * Taproot output. Despite prior naming, this is NOT a Merkle root: key-path-only
103229
- * spends have no script tree.
103351
+ * BIP-341 TapTweak scalar: `taggedHash("TapTweak", internalPubkey || tapMerkleRoot)`.
103352
+ * The beacon output is an internal key (the MuSig2 aggregate) plus a script
103353
+ * tree (the recovery leaves), so the tweak commits to the tree's Merkle root.
103354
+ * The MuSig2 signing session applies this as an x-only tweak; a value that does
103355
+ * not match the root the funded address was derived from silently yields an
103356
+ * invalid key-path signature.
103230
103357
  */
103231
103358
  tapTweak = new Uint8Array();
103232
- /** The n-of-n MuSig2 Taproot beacon address. */
103359
+ /** The n-of-n MuSig2 aggregate internal key, x-only (32 bytes), set by computeBeaconAddress(). */
103360
+ internalKey = new Uint8Array();
103361
+ /** BIP-341 Taproot Merkle root of the recovery script tree, set by computeBeaconAddress(). */
103362
+ tapMerkleRoot = new Uint8Array();
103363
+ /** Operator recovery key, x-only (32 bytes). Used to build the CSV recovery leaf. */
103364
+ recoveryKey;
103365
+ /** Relative-timelock (BIP-68 nSequence) before the recovery leaf is spendable. */
103366
+ recoverySequence;
103367
+ /** Funding model governing the recovery leaves (default 'operator-funded'). */
103368
+ fundingModel;
103369
+ /**
103370
+ * Advertised k of the k-of-n fallback leaf, or undefined to default to n-1 at
103371
+ * address computation. Read the resolved value via {@link effectiveFallbackThreshold}.
103372
+ */
103373
+ fallbackThreshold;
103374
+ /** The Taproot beacon address: key path is the MuSig2 aggregate, script path is fallback + recovery. */
103233
103375
  beaconAddress = "";
103234
103376
  /** Pending DID updates submitted by participants, keyed by DID. */
103235
103377
  pendingUpdates = /* @__PURE__ */ new Map();
103236
- /** CAS Beacon Announcement Map (DID → updateHash), set by buildCASAnnouncement(). */
103378
+ /**
103379
+ * Participant DIDs that explicitly declined to submit an update this round
103380
+ * (cooperative non-inclusion). A decliner is absent from the CAS Announcement
103381
+ * Map and carries a non-inclusion leaf in the SMT, yet still signs. Kept
103382
+ * disjoint from {@link pendingUpdates} so CAS correctness holds by construction.
103383
+ */
103384
+ nonIncluded = /* @__PURE__ */ new Set();
103385
+ /** CAS Beacon Announcement Map (DID to updateHash), set by buildCASAnnouncement(). */
103237
103386
  casAnnouncement;
103238
103387
  /** Per-participant SMT proofs, set by buildSMTTree(). */
103239
103388
  smtProofs;
@@ -103243,12 +103392,16 @@ var AggregationCohort = class {
103243
103392
  validationAcks = /* @__PURE__ */ new Set();
103244
103393
  /** Set of participant DIDs that have rejected the aggregated data. */
103245
103394
  validationRejections = /* @__PURE__ */ new Set();
103246
- constructor({ id, minParticipants, serviceDid, network, beaconType }) {
103395
+ constructor({ id, minParticipants, serviceDid, network, beaconType, recoveryKey, recoverySequence, fundingModel, fallbackThreshold }) {
103247
103396
  this.id = id || crypto.randomUUID();
103248
103397
  this.minParticipants = minParticipants ?? 2;
103249
103398
  this.serviceDid = serviceDid || "";
103250
103399
  this.network = network;
103251
103400
  this.beaconType = beaconType || "CASBeacon";
103401
+ this.recoveryKey = recoveryKey;
103402
+ this.recoverySequence = recoverySequence;
103403
+ this.fundingModel = fundingModel ?? DEFAULT_FUNDING_MODEL;
103404
+ this.fallbackThreshold = fallbackThreshold;
103252
103405
  }
103253
103406
  /** Sorted cohort keys (sorted on assignment per BIP-327). */
103254
103407
  get cohortKeys() {
@@ -103258,8 +103411,26 @@ var AggregationCohort = class {
103258
103411
  this.#cohortKeys = sortKeys(keys);
103259
103412
  }
103260
103413
  /**
103261
- * Computes the n-of-n MuSig2 Taproot beacon address from cohort keys.
103262
- * Sets `tapTweak` to the BIP-341 key-path-only tweak.
103414
+ * The resolved k of the k-of-n fallback leaf: the advertised
103415
+ * {@link fallbackThreshold}, or n-1 (floored at 1) when unadvertised, where n is
103416
+ * the current cohort size. This is the value the beacon address commits to and
103417
+ * the spend builders must reproduce. Returns 0 before any cohort keys are set.
103418
+ */
103419
+ get effectiveFallbackThreshold() {
103420
+ if (this.#cohortKeys.length === 0) return 0;
103421
+ return resolveFallbackThreshold(this.fallbackThreshold, this.#cohortKeys.length);
103422
+ }
103423
+ /**
103424
+ * Computes the Taproot beacon address from the cohort keys and recovery params.
103425
+ *
103426
+ * The output's key path is the n-of-n MuSig2 aggregate of the cohort keys; its
103427
+ * script path is the recovery tree (a CSV recovery leaf so a missing signer
103428
+ * cannot permanently lock the UTXO). Sets `internalKey`, `tapMerkleRoot`, and
103429
+ * the `tapTweak` the MuSig2 session must apply for the key-path spend.
103430
+ *
103431
+ * The tweak is derived from the Merkle root the address was built with (read
103432
+ * back from the payment), never recomputed by hand, so the MuSig2 key-path
103433
+ * signature is guaranteed to validate against the funded address.
103263
103434
  */
103264
103435
  computeBeaconAddress() {
103265
103436
  if (this.#cohortKeys.length === 0) {
@@ -103269,10 +103440,25 @@ var AggregationCohort = class {
103269
103440
  { cohortId: this.id }
103270
103441
  );
103271
103442
  }
103443
+ if (!this.recoveryKey || this.recoveryKey.length === 0 || this.recoverySequence === void 0) {
103444
+ throw new AggregationCohortError(
103445
+ "Cannot compute beacon address: missing recovery key or sequence.",
103446
+ "NO_RECOVERY_PARAMS",
103447
+ { cohortId: this.id }
103448
+ );
103449
+ }
103272
103450
  const keyAggContext = keyAggregate(this.#cohortKeys);
103273
103451
  const aggPubkey = keyAggExport(keyAggContext);
103274
- const payment = p2tr(aggPubkey, void 0, getNetwork(this.network));
103275
- this.tapTweak = schnorr2.utils.taggedHash("TapTweak", aggPubkey);
103452
+ const leaves = buildRecoveryLeaves(this.fundingModel, {
103453
+ recoveryKey: this.recoveryKey,
103454
+ recoverySequence: this.recoverySequence,
103455
+ cohortKeys: this.#cohortKeys,
103456
+ fallbackThreshold: resolveFallbackThreshold(this.fallbackThreshold, this.#cohortKeys.length)
103457
+ });
103458
+ const payment = p2tr(aggPubkey, leaves, getNetwork(this.network), true);
103459
+ this.internalKey = aggPubkey;
103460
+ this.tapMerkleRoot = payment.tapMerkleRoot;
103461
+ this.tapTweak = schnorr2.utils.taggedHash("TapTweak", concatBytes(aggPubkey, payment.tapMerkleRoot));
103276
103462
  if (!payment.address) {
103277
103463
  throw new AggregationCohortError(
103278
103464
  "Failed to compute Taproot address",
@@ -103328,20 +103514,54 @@ var AggregationCohort = class {
103328
103514
  }
103329
103515
  this.pendingUpdates.set(participantDid, signedUpdate);
103330
103516
  }
103517
+ /**
103518
+ * Record that a participant declined to submit an update this round
103519
+ * (cooperative non-inclusion). The member stays in the cohort and still signs.
103520
+ */
103521
+ addNonInclusion(participantDid) {
103522
+ if (!this.participants.includes(participantDid)) {
103523
+ throw new AggregationCohortError(
103524
+ `Participant ${participantDid} is not in cohort ${this.id}.`,
103525
+ "UNKNOWN_PARTICIPANT",
103526
+ { cohortId: this.id, participantDid }
103527
+ );
103528
+ }
103529
+ if (this.pendingUpdates.has(participantDid)) {
103530
+ throw new AggregationCohortError(
103531
+ `Participant ${participantDid} already submitted an update; cannot also decline.`,
103532
+ "CONFLICTING_RESPONSE",
103533
+ { cohortId: this.id, participantDid }
103534
+ );
103535
+ }
103536
+ this.nonIncluded.add(participantDid);
103537
+ }
103331
103538
  hasAllUpdates() {
103332
103539
  return this.pendingUpdates.size === this.participants.length;
103333
103540
  }
103541
+ /**
103542
+ * True when every participant has responded for this round, either with an
103543
+ * update or with an explicit non-inclusion. This is the aggregation gate when
103544
+ * non-inclusion is in play; it generalizes {@link hasAllUpdates} the same way
103545
+ * {@link hasAllValidationResponses} generalizes a unanimous ack.
103546
+ */
103547
+ hasAllResponses() {
103548
+ return this.pendingUpdates.size + this.nonIncluded.size === this.participants.length;
103549
+ }
103334
103550
  /**
103335
103551
  * Builds a CAS Announcement Map from collected updates.
103336
- * Maps each participant DID base64url canonical hash of their signed update.
103552
+ * Maps each participant DID to base64url canonical hash of their signed update.
103337
103553
  * Computes signal bytes as SHA-256 of canonicalized announcement.
103554
+ *
103555
+ * Members who declined (cooperative non-inclusion) are naturally absent from
103556
+ * the map: the body iterates {@link pendingUpdates}, which never holds a
103557
+ * decliner. Absence from the map is exactly the CAS non-inclusion signal.
103338
103558
  */
103339
103559
  buildCASAnnouncement() {
103340
- if (!this.hasAllUpdates()) {
103560
+ if (!this.hasAllResponses()) {
103341
103561
  throw new AggregationCohortError(
103342
- "Cannot build CAS Announcement: not all updates collected.",
103343
- "INCOMPLETE_UPDATES",
103344
- { cohortId: this.id, collected: this.pendingUpdates.size, total: this.participants.length }
103562
+ "Cannot build CAS Announcement: not all participants have responded.",
103563
+ "INCOMPLETE_RESPONSES",
103564
+ { cohortId: this.id, updates: this.pendingUpdates.size, declined: this.nonIncluded.size, total: this.participants.length }
103345
103565
  );
103346
103566
  }
103347
103567
  const announcement = {};
@@ -103353,31 +103573,41 @@ var AggregationCohort = class {
103353
103573
  return announcement;
103354
103574
  }
103355
103575
  /**
103356
- * Builds an SMT tree from collected updates.
103357
- * Each entry uses a random 32-byte nonce + canonicalized signed update bytes.
103358
- * Stores per-participant proofs and the SMT root as signalBytes.
103576
+ * Builds an SMT tree with one leaf per participant.
103577
+ *
103578
+ * A member who submitted an update gets an inclusion leaf
103579
+ * (SHA-256(SHA-256(nonce) || SHA-256(update))); a member who declined gets a
103580
+ * non-inclusion leaf (SHA-256(SHA-256(nonce)), the `signedUpdate` entry field
103581
+ * omitted). The cohort mints each member's nonce and returns it inside that
103582
+ * member's serialized proof, so a decliner can self-validate its own
103583
+ * non-inclusion slot and the resolver can recompute the leaf. Stores
103584
+ * per-participant proofs and the SMT root as signalBytes.
103359
103585
  */
103360
103586
  buildSMTTree() {
103361
- if (!this.hasAllUpdates()) {
103587
+ if (!this.hasAllResponses()) {
103362
103588
  throw new AggregationCohortError(
103363
- "Cannot build SMT tree: not all updates collected.",
103364
- "INCOMPLETE_UPDATES",
103365
- { cohortId: this.id }
103589
+ "Cannot build SMT tree: not all participants have responded.",
103590
+ "INCOMPLETE_RESPONSES",
103591
+ { cohortId: this.id, updates: this.pendingUpdates.size, declined: this.nonIncluded.size, total: this.participants.length }
103366
103592
  );
103367
103593
  }
103368
103594
  const tree2 = new BTCR2MerkleTree();
103369
103595
  const entries = [];
103370
103596
  const encoder4 = new TextEncoder();
103371
- for (const [did, signedUpdate] of this.pendingUpdates) {
103372
- const canonicalBytes = encoder4.encode(canonicalize2(signedUpdate));
103597
+ for (const did of this.participants) {
103373
103598
  const nonce = randomBytes(32);
103374
- entries.push({ did, nonce, signedUpdate: canonicalBytes });
103599
+ const signedUpdate = this.pendingUpdates.get(did);
103600
+ if (signedUpdate) {
103601
+ entries.push({ did, nonce, signedUpdate: encoder4.encode(canonicalize2(signedUpdate)) });
103602
+ } else {
103603
+ entries.push({ did, nonce });
103604
+ }
103375
103605
  }
103376
103606
  tree2.addEntries(entries);
103377
103607
  tree2.finalize();
103378
103608
  this.signalBytes = tree2.rootHash;
103379
103609
  this.smtProofs = /* @__PURE__ */ new Map();
103380
- for (const [did] of this.pendingUpdates) {
103610
+ for (const did of this.participants) {
103381
103611
  this.smtProofs.set(did, tree2.proof(did));
103382
103612
  }
103383
103613
  return this.smtProofs;
@@ -103403,7 +103633,7 @@ var AggregationCohort = class {
103403
103633
  return this.validationAcks.size + this.validationRejections.size === this.participants.length;
103404
103634
  }
103405
103635
  /**
103406
- * True when all participants approved. Note: differs from {@link hasAllValidationResponses}
103636
+ * True when all participants approved. Note: differs from {@link hasAllValidationResponses},
103407
103637
  * this returns false if any participant rejected, even if all responses are in.
103408
103638
  */
103409
103639
  isFullyValidated() {
@@ -103414,6 +103644,7 @@ var AggregationCohort = class {
103414
103644
  // src/core/aggregation/conditions.ts
103415
103645
  init_shim();
103416
103646
  var KNOWN_BEACON_TYPES = ["CASBeacon", "SMTBeacon"];
103647
+ var KNOWN_FUNDING_MODELS = ["operator-funded", "participant-funded"];
103417
103648
  function checkPair(problems, label, min, max) {
103418
103649
  if (min !== void 0 && (!Number.isInteger(min) || min < 0)) {
103419
103650
  problems.push(`min${label} must be an integer >= 0`);
@@ -103457,11 +103688,128 @@ function validateCohortConditions(c2) {
103457
103688
  if (c2.pendingUpdateTrigger !== void 0 && (!Number.isInteger(c2.pendingUpdateTrigger) || c2.pendingUpdateTrigger < 1)) {
103458
103689
  problems.push("pendingUpdateTrigger must be an integer >= 1");
103459
103690
  }
103691
+ if (c2.fallbackThreshold !== void 0) {
103692
+ if (!Number.isInteger(c2.fallbackThreshold) || c2.fallbackThreshold < 1) {
103693
+ problems.push("fallbackThreshold must be an integer >= 1");
103694
+ } else if (c2.maxParticipants !== void 0 && Number.isInteger(c2.maxParticipants) && c2.fallbackThreshold > c2.maxParticipants) {
103695
+ problems.push("fallbackThreshold must be <= maxParticipants");
103696
+ }
103697
+ }
103460
103698
  checkCost(problems, "costOfEnrollment", c2.costOfEnrollment);
103461
103699
  checkCost(problems, "costPerAnnouncement", c2.costPerAnnouncement);
103700
+ if (typeof c2.recoveryKey !== "string" || !/^[0-9a-fA-F]{64}$/.test(c2.recoveryKey)) {
103701
+ problems.push("recoveryKey must be a 64-character hex string (x-only public key)");
103702
+ }
103703
+ if (!Number.isInteger(c2.recoverySequence) || c2.recoverySequence < 1 || c2.recoverySequence > MAX_RECOVERY_SEQUENCE) {
103704
+ problems.push(`recoverySequence must be a block-based BIP-68 relative timelock in [1, ${MAX_RECOVERY_SEQUENCE}]`);
103705
+ }
103706
+ if (c2.fundingModel !== void 0 && !KNOWN_FUNDING_MODELS.includes(c2.fundingModel)) {
103707
+ problems.push(`fundingModel must be one of ${KNOWN_FUNDING_MODELS.join(", ")}`);
103708
+ }
103462
103709
  return problems;
103463
103710
  }
103464
103711
 
103712
+ // src/core/aggregation/fallback-spend.ts
103713
+ init_shim();
103714
+ init_secp256k1();
103715
+ function bytesEqual(a2, b) {
103716
+ return a2.length === b.length && a2.every((x, i4) => x === b[i4]);
103717
+ }
103718
+ function fallbackSighash(tx, inputIndex, prevOutScript, prevOutValue, fallbackLeafScript) {
103719
+ return tx.preimageWitnessV1(
103720
+ inputIndex,
103721
+ [prevOutScript],
103722
+ SigHash.DEFAULT,
103723
+ [prevOutValue],
103724
+ void 0,
103725
+ fallbackLeafScript,
103726
+ TAPROOT_LEAF_VERSION
103727
+ );
103728
+ }
103729
+ function buildFallbackSpend(params) {
103730
+ const {
103731
+ pendingTx,
103732
+ cohortKeys,
103733
+ recoveryKey,
103734
+ recoverySequence,
103735
+ fundingModel,
103736
+ network,
103737
+ prevOutScript,
103738
+ prevOutValue,
103739
+ signatures
103740
+ } = params;
103741
+ const inputIndex = params.inputIndex ?? 0;
103742
+ if (cohortKeys.length === 0) {
103743
+ throw new AggregationCohortError(
103744
+ "Cannot build fallback spend: no cohort keys.",
103745
+ "NO_COHORT_KEYS"
103746
+ );
103747
+ }
103748
+ const fallbackThreshold = resolveFallbackThreshold(params.fallbackThreshold, cohortKeys.length);
103749
+ const internalKey = keyAggExport(keyAggregate(sortKeys(cohortKeys)));
103750
+ const leaves = buildRecoveryLeaves(fundingModel ?? DEFAULT_FUNDING_MODEL, {
103751
+ recoveryKey,
103752
+ recoverySequence,
103753
+ cohortKeys,
103754
+ fallbackThreshold
103755
+ });
103756
+ const net = getNetwork(network);
103757
+ const payment = p2tr(internalKey, leaves, net, true);
103758
+ if (!bytesEqual(payment.script, prevOutScript)) {
103759
+ throw new AggregationCohortError(
103760
+ "Reconstructed beacon output script does not match the spent prevout script.",
103761
+ "PREVOUT_SCRIPT_MISMATCH"
103762
+ );
103763
+ }
103764
+ const fallbackLeaf = buildFallbackLeaf({ cohortKeys, fallbackThreshold });
103765
+ const leafEntry = payment.tapLeafScript?.find(([, scriptVer]) => {
103766
+ const script = scriptVer.slice(0, scriptVer.length - 1);
103767
+ return bytesEqual(script, fallbackLeaf);
103768
+ });
103769
+ if (!leafEntry) {
103770
+ throw new AggregationCohortError(
103771
+ "Could not locate the fallback leaf in the reconstructed beacon output.",
103772
+ "FALLBACK_LEAF_NOT_FOUND"
103773
+ );
103774
+ }
103775
+ const cohortXOnly = sortKeys(cohortKeys).map((k) => k.slice(1));
103776
+ const tx = pendingTx;
103777
+ const sighash = fallbackSighash(tx, inputIndex, prevOutScript, prevOutValue, fallbackLeaf);
103778
+ const leafHash = tapLeafHash2(fallbackLeaf);
103779
+ const accepted = /* @__PURE__ */ new Map();
103780
+ for (const { pubKey, signature } of signatures) {
103781
+ if (pubKey.length !== 32 || signature.length !== 64) continue;
103782
+ const isCohortKey = cohortXOnly.some((k) => bytesEqual(k, pubKey));
103783
+ if (!isCohortKey) continue;
103784
+ const hex2 = Array.from(pubKey, (b) => b.toString(16).padStart(2, "0")).join("");
103785
+ if (accepted.has(hex2)) continue;
103786
+ let ok = false;
103787
+ try {
103788
+ ok = schnorr2.verify(signature, sighash, pubKey);
103789
+ } catch {
103790
+ ok = false;
103791
+ }
103792
+ if (ok) accepted.set(hex2, signature);
103793
+ }
103794
+ if (accepted.size < fallbackThreshold) {
103795
+ throw new AggregationCohortError(
103796
+ `Not enough valid fallback signatures: have ${accepted.size}, need ${fallbackThreshold}.`,
103797
+ "NOT_ENOUGH_FALLBACK_SIGNATURES",
103798
+ { have: accepted.size, need: fallbackThreshold }
103799
+ );
103800
+ }
103801
+ const chosen = cohortXOnly.filter((k) => accepted.has(Array.from(k, (b) => b.toString(16).padStart(2, "0")).join(""))).slice(0, fallbackThreshold);
103802
+ tx.updateInput(inputIndex, {
103803
+ tapLeafScript: [leafEntry],
103804
+ tapScriptSig: chosen.map((pubKey) => {
103805
+ const hex2 = Array.from(pubKey, (b) => b.toString(16).padStart(2, "0")).join("");
103806
+ return [{ pubKey, leafHash }, accepted.get(hex2)];
103807
+ })
103808
+ });
103809
+ tx.finalize();
103810
+ return tx;
103811
+ }
103812
+
103465
103813
  // src/core/aggregation/messages/base.ts
103466
103814
  init_shim();
103467
103815
  var AGGREGATION_WIRE_VERSION = 1;
@@ -103501,12 +103849,15 @@ var COHORT_OPT_IN = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in`;
103501
103849
  var COHORT_OPT_IN_ACCEPT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in_accept`;
103502
103850
  var COHORT_READY = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_ready`;
103503
103851
  var SUBMIT_UPDATE = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_update`;
103852
+ var SUBMIT_NONINCLUDED = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_nonincluded`;
103504
103853
  var DISTRIBUTE_AGGREGATED_DATA = `${AGGREGATION_MESSAGE_PREFIX}/update/distribute_aggregated_data`;
103505
103854
  var VALIDATION_ACK = `${AGGREGATION_MESSAGE_PREFIX}/update/validation_ack`;
103506
103855
  var AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/authorization_request`;
103507
103856
  var NONCE_CONTRIBUTION = `${AGGREGATION_MESSAGE_PREFIX}/sign/nonce_contribution`;
103508
103857
  var AGGREGATED_NONCE = `${AGGREGATION_MESSAGE_PREFIX}/sign/aggregated_nonce`;
103509
103858
  var SIGNATURE_AUTHORIZATION = `${AGGREGATION_MESSAGE_PREFIX}/sign/signature_authorization`;
103859
+ var FALLBACK_AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/fallback_authorization_request`;
103860
+ var FALLBACK_SIGNATURE = `${AGGREGATION_MESSAGE_PREFIX}/sign/fallback_signature`;
103510
103861
 
103511
103862
  // src/core/aggregation/messages/factories.ts
103512
103863
  init_shim();
@@ -103530,6 +103881,10 @@ function createSubmitUpdateMessage(fields) {
103530
103881
  const { from: from8, to, ...body } = fields;
103531
103882
  return new BaseMessage({ type: SUBMIT_UPDATE, from: from8, to, body });
103532
103883
  }
103884
+ function createSubmitNonIncludedMessage(fields) {
103885
+ const { from: from8, to, ...body } = fields;
103886
+ return new BaseMessage({ type: SUBMIT_NONINCLUDED, from: from8, to, body });
103887
+ }
103533
103888
  function createDistributeAggregatedDataMessage(fields) {
103534
103889
  const { from: from8, to, ...body } = fields;
103535
103890
  return new BaseMessage({ type: DISTRIBUTE_AGGREGATED_DATA, from: from8, to, body });
@@ -103554,6 +103909,14 @@ function createSignatureAuthorizationMessage(fields) {
103554
103909
  const { from: from8, to, ...body } = fields;
103555
103910
  return new BaseMessage({ type: SIGNATURE_AUTHORIZATION, from: from8, to, body });
103556
103911
  }
103912
+ function createFallbackAuthorizationRequestMessage(fields) {
103913
+ const { from: from8, to, ...body } = fields;
103914
+ return new BaseMessage({ type: FALLBACK_AUTHORIZATION_REQUEST, from: from8, to, body });
103915
+ }
103916
+ function createFallbackSignatureMessage(fields) {
103917
+ const { from: from8, to, ...body } = fields;
103918
+ return new BaseMessage({ type: FALLBACK_SIGNATURE, from: from8, to, body });
103919
+ }
103557
103920
 
103558
103921
  // src/core/aggregation/phases.ts
103559
103922
  init_shim();
@@ -103568,6 +103931,7 @@ var ServiceCohortPhase = /* @__PURE__ */ ((ServiceCohortPhase2) => {
103568
103931
  ServiceCohortPhase2["SigningStarted"] = "SigningStarted";
103569
103932
  ServiceCohortPhase2["NoncesCollected"] = "NoncesCollected";
103570
103933
  ServiceCohortPhase2["AwaitingPartialSigs"] = "AwaitingPartialSigs";
103934
+ ServiceCohortPhase2["FallbackRequested"] = "FallbackRequested";
103571
103935
  ServiceCohortPhase2["Complete"] = "Complete";
103572
103936
  ServiceCohortPhase2["Failed"] = "Failed";
103573
103937
  return ServiceCohortPhase2;
@@ -103577,11 +103941,13 @@ var ParticipantCohortPhase = /* @__PURE__ */ ((ParticipantCohortPhase2) => {
103577
103941
  ParticipantCohortPhase2["OptedIn"] = "OptedIn";
103578
103942
  ParticipantCohortPhase2["CohortReady"] = "CohortReady";
103579
103943
  ParticipantCohortPhase2["UpdateSubmitted"] = "UpdateSubmitted";
103944
+ ParticipantCohortPhase2["NonIncluded"] = "NonIncluded";
103580
103945
  ParticipantCohortPhase2["AwaitingValidation"] = "AwaitingValidation";
103581
103946
  ParticipantCohortPhase2["ValidationSent"] = "ValidationSent";
103582
103947
  ParticipantCohortPhase2["AwaitingSigning"] = "AwaitingSigning";
103583
103948
  ParticipantCohortPhase2["NonceSent"] = "NonceSent";
103584
103949
  ParticipantCohortPhase2["AwaitingPartialSig"] = "AwaitingPartialSig";
103950
+ ParticipantCohortPhase2["AwaitingFallbackSig"] = "AwaitingFallbackSig";
103585
103951
  ParticipantCohortPhase2["Complete"] = "Complete";
103586
103952
  ParticipantCohortPhase2["Failed"] = "Failed";
103587
103953
  return ParticipantCohortPhase2;
@@ -103609,11 +103975,11 @@ var BeaconSigningSession = class {
103609
103975
  prevOutScripts;
103610
103976
  /** Previous output values for Taproot sighash computation. */
103611
103977
  prevOutValues;
103612
- /** Map of participant publicKey-hex public nonce contribution. */
103978
+ /** Map of participant publicKey-hex to public nonce contribution. */
103613
103979
  nonceContributions = /* @__PURE__ */ new Map();
103614
103980
  /** Aggregated MuSig2 nonce (66 bytes). */
103615
103981
  aggregatedNonce;
103616
- /** Map of participant DID partial signature. */
103982
+ /** Map of participant DID to partial signature. */
103617
103983
  partialSignatures = /* @__PURE__ */ new Map();
103618
103984
  /** Final 64-byte Schnorr signature. */
103619
103985
  signature;
@@ -103796,7 +104162,7 @@ var BeaconSigningSession = class {
103796
104162
  throw new SigningSessionError("Aggregated nonce not available.", "MISSING_AGGREGATED_NONCE");
103797
104163
  }
103798
104164
  if (!this.#secretNonce) {
103799
- throw new SigningSessionError("Secret nonce not available \u2014 generateNonceContribution() must be called first.", "MISSING_SECRET_NONCE");
104165
+ throw new SigningSessionError("Secret nonce not available - generateNonceContribution() must be called first.", "MISSING_SECRET_NONCE");
103800
104166
  }
103801
104167
  const session = new Session(
103802
104168
  this.aggregatedNonce,
@@ -103866,6 +104232,9 @@ var AggregationService = class {
103866
104232
  case SUBMIT_UPDATE:
103867
104233
  this.#handleSubmitUpdate(message2);
103868
104234
  break;
104235
+ case SUBMIT_NONINCLUDED:
104236
+ this.#handleSubmitNonInclusion(message2);
104237
+ break;
103869
104238
  case VALIDATION_ACK:
103870
104239
  this.#handleValidationAck(message2);
103871
104240
  break;
@@ -103875,6 +104244,9 @@ var AggregationService = class {
103875
104244
  case SIGNATURE_AUTHORIZATION:
103876
104245
  this.#handleSignatureAuthorization(message2);
103877
104246
  break;
104247
+ case FALLBACK_SIGNATURE:
104248
+ this.#handleFallbackSignature(message2);
104249
+ break;
103878
104250
  default:
103879
104251
  break;
103880
104252
  }
@@ -103893,7 +104265,7 @@ var AggregationService = class {
103893
104265
  }
103894
104266
  /**
103895
104267
  * Create a new cohort with the given config. Returns the cohort ID.
103896
- * Cohort starts in `Created` phase call `advertise()` to broadcast.
104268
+ * Cohort starts in `Created` phase - call `advertise()` to broadcast.
103897
104269
  */
103898
104270
  createCohort(config) {
103899
104271
  const problems = validateCohortConditions(config);
@@ -103908,7 +104280,11 @@ var AggregationService = class {
103908
104280
  serviceDid: this.did,
103909
104281
  minParticipants: config.minParticipants,
103910
104282
  network: config.network,
103911
- beaconType: config.beaconType
104283
+ beaconType: config.beaconType,
104284
+ recoveryKey: hexToBytes(config.recoveryKey),
104285
+ recoverySequence: config.recoverySequence,
104286
+ fundingModel: config.fundingModel,
104287
+ fallbackThreshold: config.fallbackThreshold
103912
104288
  });
103913
104289
  this.#cohortStates.set(cohort.id, {
103914
104290
  phase: "Created" /* Created */,
@@ -104109,11 +104485,64 @@ var AggregationService = class {
104109
104485
  });
104110
104486
  return;
104111
104487
  }
104488
+ if (state.cohort.nonIncluded.has(message2.from)) {
104489
+ state.rejections.push({
104490
+ from: message2.from,
104491
+ code: "UPDATE_MALFORMED",
104492
+ reason: "Participant already declined this round; cannot also submit an update"
104493
+ });
104494
+ return;
104495
+ }
104496
+ if (state.cohort.pendingUpdates.has(message2.from)) {
104497
+ state.rejections.push({
104498
+ from: message2.from,
104499
+ code: "UPDATE_MALFORMED",
104500
+ reason: "Participant already submitted an update this round; cannot resubmit"
104501
+ });
104502
+ return;
104503
+ }
104112
104504
  state.cohort.addUpdate(message2.from, signedUpdate);
104113
104505
  if (state.phase === "CohortSet" /* CohortSet */) {
104114
104506
  state.phase = "CollectingUpdates" /* CollectingUpdates */;
104115
104507
  }
104116
- if (state.cohort.hasAllUpdates()) {
104508
+ if (state.cohort.hasAllResponses()) {
104509
+ state.phase = "UpdatesCollected" /* UpdatesCollected */;
104510
+ }
104511
+ }
104512
+ /**
104513
+ * Handle an incoming SUBMIT_NONINCLUDED message: a member declares it has no
104514
+ * update this round (cooperative non-inclusion). Membership is proven by the
104515
+ * signed transport envelope, so the body carries only the cohortId. The member
104516
+ * stays in the cohort and still signs; it is absent from the CAS map and
104517
+ * carries a non-inclusion leaf in the SMT.
104518
+ */
104519
+ #handleSubmitNonInclusion(message2) {
104520
+ const cohortId = message2.body?.cohortId;
104521
+ if (!cohortId) return;
104522
+ const state = this.#cohortStates.get(cohortId);
104523
+ if (!state) return;
104524
+ if (state.phase !== "CohortSet" /* CohortSet */ && state.phase !== "CollectingUpdates" /* CollectingUpdates */) return;
104525
+ if (!state.cohort.participants.includes(message2.from)) {
104526
+ state.rejections.push({
104527
+ from: message2.from,
104528
+ code: "UPDATE_MALFORMED",
104529
+ reason: "Sender is not a member of this cohort"
104530
+ });
104531
+ return;
104532
+ }
104533
+ if (state.cohort.pendingUpdates.has(message2.from) || state.cohort.nonIncluded.has(message2.from)) {
104534
+ state.rejections.push({
104535
+ from: message2.from,
104536
+ code: "UPDATE_MALFORMED",
104537
+ reason: "Participant already responded this round"
104538
+ });
104539
+ return;
104540
+ }
104541
+ state.cohort.addNonInclusion(message2.from);
104542
+ if (state.phase === "CohortSet" /* CohortSet */) {
104543
+ state.phase = "CollectingUpdates" /* CollectingUpdates */;
104544
+ }
104545
+ if (state.cohort.hasAllResponses()) {
104117
104546
  state.phase = "UpdatesCollected" /* UpdatesCollected */;
104118
104547
  }
104119
104548
  }
@@ -104223,7 +104652,7 @@ var AggregationService = class {
104223
104652
  }
104224
104653
  /**
104225
104654
  * Start a signing session by creating auth requests for all participants.
104226
- * The caller provides the transaction data typically built via
104655
+ * The caller provides the transaction data - typically built via
104227
104656
  * `buildBeaconTransaction()` against a Bitcoin connection.
104228
104657
  */
104229
104658
  startSigning(cohortId, txData) {
@@ -104330,11 +104759,144 @@ var AggregationService = class {
104330
104759
  state.result = {
104331
104760
  cohortId,
104332
104761
  signature,
104333
- signedTx: state.signingSession.pendingTx
104762
+ signedTx: state.signingSession.pendingTx,
104763
+ path: "key-path"
104334
104764
  };
104335
104765
  state.phase = "Complete" /* Complete */;
104336
104766
  }
104337
104767
  }
104768
+ /**
104769
+ * Abandon the optimistic n-of-n key path and ask members to authorize the
104770
+ * k-of-n fallback (script-path) spend of the SAME beacon transaction (graceful
104771
+ * liveness, ADR 042). Reuses the in-flight signing session's transaction and
104772
+ * spent output, so the announcement and its outputs are unchanged: only the
104773
+ * witness path differs. Returns one FALLBACK_AUTHORIZATION_REQUEST per
104774
+ * participant.
104775
+ *
104776
+ * Callable once optimistic signing has started (the session and its tx exist)
104777
+ * and before it completes. A cohort can take exactly one of the two paths: the
104778
+ * caller (runner) must commit to fallback and stop driving the optimistic path.
104779
+ */
104780
+ startFallbackSigning(cohortId) {
104781
+ const state = this.#cohortStates.get(cohortId);
104782
+ if (!state) {
104783
+ throw new AggregationServiceError(`Cohort ${cohortId} not found.`, "COHORT_NOT_FOUND", { cohortId });
104784
+ }
104785
+ if (!state.signingSession) {
104786
+ throw new AggregationServiceError(
104787
+ `Cannot start fallback for cohort ${cohortId}: no signing session.`,
104788
+ "NO_SIGNING_SESSION",
104789
+ { cohortId }
104790
+ );
104791
+ }
104792
+ const signingPhases = [
104793
+ "SigningStarted" /* SigningStarted */,
104794
+ "NoncesCollected" /* NoncesCollected */,
104795
+ "AwaitingPartialSigs" /* AwaitingPartialSigs */
104796
+ ];
104797
+ if (!signingPhases.includes(state.phase)) {
104798
+ throw new AggregationServiceError(
104799
+ `Cannot start fallback for cohort ${cohortId}: phase is ${state.phase}.`,
104800
+ "INVALID_PHASE",
104801
+ { cohortId, phase: state.phase }
104802
+ );
104803
+ }
104804
+ const session = state.signingSession;
104805
+ const prevOutScript = session.prevOutScripts[0];
104806
+ const prevOutValue = session.prevOutValues[0];
104807
+ if (!prevOutScript || prevOutValue === void 0) {
104808
+ throw new AggregationServiceError(
104809
+ `Cannot start fallback for cohort ${cohortId}: signing session missing prevout data.`,
104810
+ "MISSING_PREV_OUT",
104811
+ { cohortId }
104812
+ );
104813
+ }
104814
+ const fallbackLeaf = buildFallbackLeaf({
104815
+ cohortKeys: state.cohort.cohortKeys,
104816
+ fallbackThreshold: state.cohort.effectiveFallbackThreshold
104817
+ });
104818
+ state.fallbackSignatures = /* @__PURE__ */ new Map();
104819
+ state.phase = "FallbackRequested" /* FallbackRequested */;
104820
+ const messages2 = [];
104821
+ for (const participantDid of state.cohort.participants) {
104822
+ messages2.push(createFallbackAuthorizationRequestMessage({
104823
+ from: this.did,
104824
+ to: participantDid,
104825
+ cohortId,
104826
+ sessionId: session.id,
104827
+ pendingTx: session.pendingTx.hex,
104828
+ prevOutScriptHex: bytesToHex(prevOutScript),
104829
+ prevOutValue: prevOutValue.toString(),
104830
+ fallbackLeafScriptHex: bytesToHex(fallbackLeaf)
104831
+ }));
104832
+ }
104833
+ return messages2;
104834
+ }
104835
+ /**
104836
+ * Handle an incoming FALLBACK_SIGNATURE: a member's standalone BIP-340
104837
+ * signature over the fallback script-path sighash. The signature is
104838
+ * authenticated to the sender (its `signerPk` must be the sender's own cohort
104839
+ * key and the signature must verify against the sighash) and collected. Once k
104840
+ * valid signatures are in, the k-of-n fallback spend is assembled and the
104841
+ * cohort completes via the script path.
104842
+ */
104843
+ #handleFallbackSignature(message2) {
104844
+ const cohortId = message2.body?.cohortId;
104845
+ if (!cohortId) return;
104846
+ const state = this.#cohortStates.get(cohortId);
104847
+ if (!state || !state.signingSession || !state.fallbackSignatures) return;
104848
+ if (state.phase !== "FallbackRequested" /* FallbackRequested */) return;
104849
+ const sessionId = message2.body?.sessionId;
104850
+ if (sessionId !== state.signingSession.id) return;
104851
+ const signerPk = message2.body?.signerPk;
104852
+ const fallbackSignature = message2.body?.fallbackSignature;
104853
+ if (!signerPk || !fallbackSignature) return;
104854
+ const prevOutScript = state.signingSession.prevOutScripts[0];
104855
+ const prevOutValue = state.signingSession.prevOutValues[0];
104856
+ if (!prevOutScript || prevOutValue === void 0) return;
104857
+ const memberKey = state.cohort.participantKeys.get(message2.from);
104858
+ if (!memberKey) {
104859
+ state.rejections.push({ from: message2.from, code: "UPDATE_MALFORMED", reason: "Fallback signature from a non-member" });
104860
+ return;
104861
+ }
104862
+ const memberXOnly = memberKey.slice(1);
104863
+ if (signerPk.length !== 32 || !memberXOnly.every((b, i4) => b === signerPk[i4])) {
104864
+ state.rejections.push({ from: message2.from, code: "UPDATE_MALFORMED", reason: "Fallback signerPk does not match the sender cohort key" });
104865
+ return;
104866
+ }
104867
+ const fallbackLeaf = buildFallbackLeaf({
104868
+ cohortKeys: state.cohort.cohortKeys,
104869
+ fallbackThreshold: state.cohort.effectiveFallbackThreshold
104870
+ });
104871
+ const sighash = fallbackSighash(state.signingSession.pendingTx, 0, prevOutScript, prevOutValue, fallbackLeaf);
104872
+ let valid = false;
104873
+ try {
104874
+ valid = fallbackSignature.length === 64 && schnorr2.verify(fallbackSignature, sighash, signerPk);
104875
+ } catch {
104876
+ valid = false;
104877
+ }
104878
+ if (!valid) {
104879
+ state.rejections.push({ from: message2.from, code: "UPDATE_VERIFICATION_FAILED", reason: "Fallback signature failed verification" });
104880
+ return;
104881
+ }
104882
+ state.fallbackSignatures.set(message2.from, { pubKey: signerPk, signature: fallbackSignature });
104883
+ if (state.fallbackSignatures.size >= state.cohort.effectiveFallbackThreshold) {
104884
+ const signedTx = buildFallbackSpend({
104885
+ pendingTx: state.signingSession.pendingTx,
104886
+ cohortKeys: state.cohort.cohortKeys,
104887
+ fallbackThreshold: state.cohort.effectiveFallbackThreshold,
104888
+ recoveryKey: state.cohort.recoveryKey,
104889
+ recoverySequence: state.cohort.recoverySequence,
104890
+ fundingModel: state.cohort.fundingModel,
104891
+ network: state.cohort.network,
104892
+ prevOutScript,
104893
+ prevOutValue,
104894
+ signatures: [...state.fallbackSignatures.values()]
104895
+ });
104896
+ state.result = { cohortId, signature: new Uint8Array(), signedTx, path: "script-path" };
104897
+ state.phase = "Complete" /* Complete */;
104898
+ }
104899
+ }
104338
104900
  getResult(cohortId) {
104339
104901
  return this.#cohortStates.get(cohortId)?.result;
104340
104902
  }
@@ -104366,6 +104928,7 @@ var AggregationService = class {
104366
104928
 
104367
104929
  // src/core/aggregation/participant.ts
104368
104930
  init_shim();
104931
+ init_secp256k1();
104369
104932
  init_utils();
104370
104933
 
104371
104934
  // src/core/aggregation/messages/bodies.ts
@@ -104379,6 +104942,14 @@ var optIntMin = (b, k, min) => {
104379
104942
  const v = b ? b[k] : void 0;
104380
104943
  return v === void 0 || typeof v === "number" && Number.isInteger(v) && v >= min;
104381
104944
  };
104945
+ var intInRange = (b, k, min, max) => {
104946
+ const v = b ? b[k] : void 0;
104947
+ return typeof v === "number" && Number.isInteger(v) && v >= min && v <= max;
104948
+ };
104949
+ var optStrOneOf = (b, k, allowed) => {
104950
+ const v = b ? b[k] : void 0;
104951
+ return v === void 0 || typeof v === "string" && allowed.includes(v);
104952
+ };
104382
104953
  var hasBool = (b, k) => !!b && typeof b[k] === "boolean";
104383
104954
  var hasBytes = (b, k) => !!b && b[k] instanceof Uint8Array;
104384
104955
  var hasBytesArray = (b, k) => {
@@ -104386,7 +104957,7 @@ var hasBytesArray = (b, k) => {
104386
104957
  return Array.isArray(v) && v.every((x) => x instanceof Uint8Array);
104387
104958
  };
104388
104959
  function isCohortAdvertMessage(m2) {
104389
- return m2.type === COHORT_ADVERT && hasStr(m2.body, "cohortId") && hasIntMin(m2.body, "minParticipants", 1) && optIntMin(m2.body, "maxParticipants", 1) && hasStr(m2.body, "beaconType") && hasStr(m2.body, "network") && hasBytes(m2.body, "communicationPk");
104960
+ return m2.type === COHORT_ADVERT && hasStr(m2.body, "cohortId") && hasIntMin(m2.body, "minParticipants", 1) && optIntMin(m2.body, "maxParticipants", 1) && hasStr(m2.body, "beaconType") && hasStr(m2.body, "network") && hasStr(m2.body, "recoveryKey") && intInRange(m2.body, "recoverySequence", 1, MAX_RECOVERY_SEQUENCE) && optStrOneOf(m2.body, "fundingModel", KNOWN_FUNDING_MODELS) && optIntMin(m2.body, "fallbackThreshold", 1) && hasBytes(m2.body, "communicationPk");
104390
104961
  }
104391
104962
  function isCohortOptInMessage(m2) {
104392
104963
  return m2.type === COHORT_OPT_IN && hasStr(m2.body, "cohortId") && hasBytes(m2.body, "participantPk") && hasBytes(m2.body, "communicationPk");
@@ -104400,6 +104971,9 @@ function isCohortReadyMessage(m2) {
104400
104971
  function isSubmitUpdateMessage(m2) {
104401
104972
  return m2.type === SUBMIT_UPDATE && hasStr(m2.body, "cohortId") && !!m2.body && typeof m2.body.signedUpdate === "object";
104402
104973
  }
104974
+ function isSubmitNonIncludedMessage(m2) {
104975
+ return m2.type === SUBMIT_NONINCLUDED && hasStr(m2.body, "cohortId");
104976
+ }
104403
104977
  function isDistributeAggregatedDataMessage(m2) {
104404
104978
  return m2.type === DISTRIBUTE_AGGREGATED_DATA && hasStr(m2.body, "cohortId") && hasStr(m2.body, "beaconType") && hasStr(m2.body, "signalBytesHex");
104405
104979
  }
@@ -104418,8 +104992,38 @@ function isAggregatedNonceMessage(m2) {
104418
104992
  function isSignatureAuthorizationMessage(m2) {
104419
104993
  return m2.type === SIGNATURE_AUTHORIZATION && hasStr(m2.body, "cohortId") && hasStr(m2.body, "sessionId") && hasBytes(m2.body, "partialSignature");
104420
104994
  }
104995
+ function isFallbackAuthorizationRequestMessage(m2) {
104996
+ return m2.type === FALLBACK_AUTHORIZATION_REQUEST && hasStr(m2.body, "cohortId") && hasStr(m2.body, "sessionId") && hasStr(m2.body, "pendingTx") && hasStr(m2.body, "prevOutScriptHex") && hasStr(m2.body, "prevOutValue") && hasStr(m2.body, "fallbackLeafScriptHex");
104997
+ }
104998
+ function isFallbackSignatureMessage(m2) {
104999
+ return m2.type === FALLBACK_SIGNATURE && hasStr(m2.body, "cohortId") && hasStr(m2.body, "sessionId") && hasBytes(m2.body, "signerPk") && hasBytes(m2.body, "fallbackSignature");
105000
+ }
104421
105001
 
104422
105002
  // src/core/aggregation/participant.ts
105003
+ function txEmbedsSignal(tx, signalHex) {
105004
+ let expected;
105005
+ try {
105006
+ expected = hexToBytes(signalHex);
105007
+ } catch {
105008
+ return false;
105009
+ }
105010
+ if (expected.length === 0) return false;
105011
+ for (let i4 = 0; i4 < tx.outputsLength; i4++) {
105012
+ const script = tx.getOutput(i4)?.script;
105013
+ if (!script) continue;
105014
+ let decoded;
105015
+ try {
105016
+ decoded = Script.decode(script);
105017
+ } catch {
105018
+ continue;
105019
+ }
105020
+ if (decoded.length === 2 && decoded[0] === "RETURN" && decoded[1] instanceof Uint8Array) {
105021
+ const payload = decoded[1];
105022
+ if (payload.length === expected.length && payload.every((b, j) => b === expected[j])) return true;
105023
+ }
105024
+ }
105025
+ return false;
105026
+ }
104423
105027
  var AggregationParticipant = class {
104424
105028
  did;
104425
105029
  /** MuSig2 signing capability. The raw secret never lives as a field here. */
@@ -104436,7 +105040,7 @@ var AggregationParticipant = class {
104436
105040
  }
104437
105041
  /**
104438
105042
  * Process an incoming message. Updates internal state but never produces
104439
- * outgoing messages those come exclusively from action methods.
105043
+ * outgoing messages: those come exclusively from action methods.
104440
105044
  */
104441
105045
  receive(message2) {
104442
105046
  if (message2.version === void 0 || message2.version !== AGGREGATION_WIRE_VERSION) {
@@ -104462,6 +105066,9 @@ var AggregationParticipant = class {
104462
105066
  case AGGREGATED_NONCE:
104463
105067
  this.#handleAggregatedNonce(message2);
104464
105068
  break;
105069
+ case FALLBACK_AUTHORIZATION_REQUEST:
105070
+ this.#handleFallbackAuthorizationRequest(message2);
105071
+ break;
104465
105072
  default:
104466
105073
  break;
104467
105074
  }
@@ -104507,12 +105114,17 @@ var AggregationParticipant = class {
104507
105114
  { cohortId, phase: state?.phase }
104508
105115
  );
104509
105116
  }
105117
+ const advert = state.advert;
104510
105118
  const cohort = new AggregationCohort({
104511
105119
  id: cohortId,
104512
105120
  serviceDid: state.serviceDid,
104513
- minParticipants: state.advert.minParticipants,
104514
- network: state.advert.network,
104515
- beaconType: state.advert.beaconType
105121
+ minParticipants: advert.minParticipants,
105122
+ network: advert.network,
105123
+ beaconType: advert.beaconType,
105124
+ recoveryKey: advert.recoveryKey ? hexToBytes(advert.recoveryKey) : void 0,
105125
+ recoverySequence: advert.recoverySequence,
105126
+ fundingModel: advert.fundingModel,
105127
+ fallbackThreshold: advert.fallbackThreshold
104516
105128
  });
104517
105129
  state.cohort = cohort;
104518
105130
  state.phase = "OptedIn" /* OptedIn */;
@@ -104527,7 +105139,7 @@ var AggregationParticipant = class {
104527
105139
  }
104528
105140
  #handleOptInAccept(message2) {
104529
105141
  }
104530
- /** Cohorts that have been finalized beacon address available. */
105142
+ /** Cohorts that have been finalized: beacon address available. */
104531
105143
  get joinedCohorts() {
104532
105144
  const map3 = /* @__PURE__ */ new Map();
104533
105145
  for (const [id, state] of this.#cohortStates) {
@@ -104570,6 +105182,7 @@ var AggregationParticipant = class {
104570
105182
  );
104571
105183
  }
104572
105184
  state.submittedUpdate = signedUpdate;
105185
+ state.included = true;
104573
105186
  state.phase = "UpdateSubmitted" /* UpdateSubmitted */;
104574
105187
  const message2 = createSubmitUpdateMessage({
104575
105188
  from: this.did,
@@ -104579,6 +105192,30 @@ var AggregationParticipant = class {
104579
105192
  });
104580
105193
  return [message2];
104581
105194
  }
105195
+ /**
105196
+ * User action: decline to submit an update this round (cooperative
105197
+ * non-inclusion). The member stays in the cohort and still signs; it will be
105198
+ * absent from the CAS Announcement Map, or carry a non-inclusion leaf in the
105199
+ * SMT. Returns the SUBMIT_NONINCLUDED message to send.
105200
+ */
105201
+ declineUpdate(cohortId) {
105202
+ const state = this.#cohortStates.get(cohortId);
105203
+ if (!state || state.phase !== "CohortReady" /* CohortReady */) {
105204
+ throw new AggregationParticipantError(
105205
+ `Cannot decline in cohort ${cohortId}: not in CohortReady phase.`,
105206
+ "INVALID_PHASE",
105207
+ { cohortId, phase: state?.phase }
105208
+ );
105209
+ }
105210
+ state.included = false;
105211
+ state.phase = "NonIncluded" /* NonIncluded */;
105212
+ const message2 = createSubmitNonIncludedMessage({
105213
+ from: this.did,
105214
+ to: state.serviceDid,
105215
+ cohortId
105216
+ });
105217
+ return [message2];
105218
+ }
104582
105219
  /** Aggregated data awaiting user validation. */
104583
105220
  get pendingValidations() {
104584
105221
  const map3 = /* @__PURE__ */ new Map();
@@ -104589,22 +105226,35 @@ var AggregationParticipant = class {
104589
105226
  }
104590
105227
  return map3;
104591
105228
  }
105229
+ /**
105230
+ * The validated aggregated data retained for a cohort, regardless of phase.
105231
+ * Unlike {@link pendingValidations} (which lists only cohorts still awaiting
105232
+ * the validate decision), this returns the stored validation, including the
105233
+ * participant's sidecar (the CAS Announcement map or its SMT inclusion proof),
105234
+ * so it is still readable once the cohort reaches Complete. Returns
105235
+ * undefined before aggregated data has been received.
105236
+ */
105237
+ getValidation(cohortId) {
105238
+ return this.#cohortStates.get(cohortId)?.validation;
105239
+ }
104592
105240
  #handleDistributeAggregatedData(message2) {
104593
105241
  const cohortId = message2.body?.cohortId;
104594
105242
  if (!cohortId) return;
104595
105243
  const state = this.#cohortStates.get(cohortId);
104596
- if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */) return;
104597
- if (!state.submittedUpdate) return;
105244
+ if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */ && state.phase !== "NonIncluded" /* NonIncluded */) return;
105245
+ const declined = state.included === false;
105246
+ if (!declined && !state.submittedUpdate) return;
104598
105247
  const beaconType = message2.body?.beaconType;
104599
105248
  if (!beaconType) return;
104600
105249
  const strategy = getBeaconStrategy(beaconType);
104601
105250
  if (!strategy) return;
104602
105251
  const signalBytesHex = message2.body?.signalBytesHex ?? "";
104603
- const expectedHash = canonicalHash(state.submittedUpdate);
105252
+ const expectedHash = declined ? "" : canonicalHash(state.submittedUpdate);
104604
105253
  const result = strategy.validateParticipantView({
104605
105254
  participantDid: this.did,
104606
- submittedUpdate: state.submittedUpdate,
104607
- expectedHash,
105255
+ included: !declined,
105256
+ submittedUpdate: declined ? void 0 : state.submittedUpdate,
105257
+ expectedHash: declined ? void 0 : expectedHash,
104608
105258
  body: message2.body
104609
105259
  });
104610
105260
  state.validation = {
@@ -104614,7 +105264,8 @@ var AggregationParticipant = class {
104614
105264
  expectedHash,
104615
105265
  matches: result.matches,
104616
105266
  casAnnouncement: result.casAnnouncement,
104617
- smtProof: result.smtProof
105267
+ smtProof: result.smtProof,
105268
+ included: !declined
104618
105269
  };
104619
105270
  state.phase = "AwaitingValidation" /* AwaitingValidation */;
104620
105271
  }
@@ -104688,6 +105339,31 @@ var AggregationParticipant = class {
104688
105339
  };
104689
105340
  state.phase = "AwaitingSigning" /* AwaitingSigning */;
104690
105341
  }
105342
+ /**
105343
+ * Bind a signing approval to the announcement the member validated: a beacon
105344
+ * transaction MUST carry an OP_RETURN with the exact 32-byte signal stored when
105345
+ * the aggregated data was distributed. Both the optimistic nonce approval and
105346
+ * the fallback approval sign with SIGHASH_DEFAULT (committing to every output)
105347
+ * while the coordinator drives output selection, so without this check a
105348
+ * coordinator could anchor a different signal under the member's signature.
105349
+ */
105350
+ #assertTxAnchorsValidatedSignal(cohortId, state, tx) {
105351
+ const signalHex = state.validation?.signalBytesHex;
105352
+ if (!signalHex) {
105353
+ throw new AggregationParticipantError(
105354
+ `Cohort ${cohortId} has no validated signal to bind the signature to.`,
105355
+ "MISSING_STATE",
105356
+ { cohortId }
105357
+ );
105358
+ }
105359
+ if (!txEmbedsSignal(tx, signalHex)) {
105360
+ throw new AggregationParticipantError(
105361
+ `Transaction for cohort ${cohortId} does not anchor the validated signal.`,
105362
+ "SIGNAL_MISMATCH",
105363
+ { cohortId }
105364
+ );
105365
+ }
105366
+ }
104691
105367
  /**
104692
105368
  * User action: approve signing and generate nonce contribution.
104693
105369
  */
@@ -104707,7 +105383,8 @@ var AggregationParticipant = class {
104707
105383
  { cohortId }
104708
105384
  );
104709
105385
  }
104710
- const tx = Transaction.fromRaw(hexToBytes(state.signingRequest.pendingTxHex));
105386
+ const tx = Transaction.fromRaw(hexToBytes(state.signingRequest.pendingTxHex), { allowUnknownOutputs: true });
105387
+ this.#assertTxAnchorsValidatedSignal(cohortId, state, tx);
104711
105388
  const prevOutScripts = [hexToBytes(state.signingRequest.prevOutScriptHex)];
104712
105389
  const prevOutValues = [BigInt(state.signingRequest.prevOutValue)];
104713
105390
  const session = new BeaconSigningSession({
@@ -104743,7 +105420,7 @@ var AggregationParticipant = class {
104743
105420
  }
104744
105421
  /**
104745
105422
  * User action: generate and return the partial signature.
104746
- * In most UIs this is automatic after AwaitingPartialSig but exposing it
105423
+ * In most UIs this is automatic after AwaitingPartialSig, but exposing it
104747
105424
  * as an explicit action lets the client UI confirm before signing if desired.
104748
105425
  */
104749
105426
  generatePartialSignature(cohortId) {
@@ -104775,6 +105452,83 @@ var AggregationParticipant = class {
104775
105452
  partialSignature: partialSig
104776
105453
  })];
104777
105454
  }
105455
+ /** Fallback signing requests awaiting user approval (UI: review the fallback spend). */
105456
+ get pendingFallbackRequests() {
105457
+ const map3 = /* @__PURE__ */ new Map();
105458
+ for (const [id, state] of this.#cohortStates) {
105459
+ if (state.phase === "AwaitingFallbackSig" /* AwaitingFallbackSig */ && state.fallbackRequest) {
105460
+ map3.set(id, state.fallbackRequest);
105461
+ }
105462
+ }
105463
+ return map3;
105464
+ }
105465
+ #handleFallbackAuthorizationRequest(message2) {
105466
+ const cohortId = message2.body?.cohortId;
105467
+ if (!cohortId) return;
105468
+ const state = this.#cohortStates.get(cohortId);
105469
+ if (!state || !state.cohort) return;
105470
+ const acceptFrom = [
105471
+ "ValidationSent" /* ValidationSent */,
105472
+ "AwaitingSigning" /* AwaitingSigning */,
105473
+ "NonceSent" /* NonceSent */,
105474
+ "AwaitingPartialSig" /* AwaitingPartialSig */,
105475
+ "Complete" /* Complete */
105476
+ ];
105477
+ if (!acceptFrom.includes(state.phase)) return;
105478
+ const sessionId = message2.body?.sessionId;
105479
+ const pendingTxHex = message2.body?.pendingTx;
105480
+ const prevOutScriptHex = message2.body?.prevOutScriptHex;
105481
+ const prevOutValue = message2.body?.prevOutValue;
105482
+ const fallbackLeafScriptHex = message2.body?.fallbackLeafScriptHex;
105483
+ if (!sessionId || !pendingTxHex || !prevOutScriptHex || !prevOutValue || !fallbackLeafScriptHex) return;
105484
+ state.fallbackRequest = { cohortId, sessionId, pendingTxHex, prevOutScriptHex, prevOutValue, fallbackLeafScriptHex };
105485
+ state.signingSession?.clearSecrets();
105486
+ state.phase = "AwaitingFallbackSig" /* AwaitingFallbackSig */;
105487
+ }
105488
+ /**
105489
+ * User action: authorize the fallback spend. Recomputes the k-of-n fallback
105490
+ * leaf from the member's OWN cohort state (not the service-provided script),
105491
+ * computes the BIP-341 script-path sighash over the requested transaction, and
105492
+ * returns a standalone BIP-340 signature (no nonce round). The member completes
105493
+ * once it has contributed; the service needs only k of these.
105494
+ */
105495
+ approveFallback(cohortId) {
105496
+ const state = this.#cohortStates.get(cohortId);
105497
+ if (!state || state.phase !== "AwaitingFallbackSig" /* AwaitingFallbackSig */) {
105498
+ throw new AggregationParticipantError(
105499
+ `Cannot approve fallback for cohort ${cohortId}: not in AwaitingFallbackSig phase.`,
105500
+ "INVALID_PHASE",
105501
+ { cohortId, phase: state?.phase }
105502
+ );
105503
+ }
105504
+ if (!state.fallbackRequest || !state.cohort) {
105505
+ throw new AggregationParticipantError(
105506
+ `Cohort ${cohortId} missing fallback request or cohort state.`,
105507
+ "MISSING_STATE",
105508
+ { cohortId }
105509
+ );
105510
+ }
105511
+ const req = state.fallbackRequest;
105512
+ const tx = Transaction.fromRaw(hexToBytes(req.pendingTxHex), { allowUnknownOutputs: true });
105513
+ const prevOutScript = hexToBytes(req.prevOutScriptHex);
105514
+ const prevOutValue = BigInt(req.prevOutValue);
105515
+ this.#assertTxAnchorsValidatedSignal(cohortId, state, tx);
105516
+ const fallbackLeaf = buildFallbackLeaf({
105517
+ cohortKeys: state.cohort.cohortKeys,
105518
+ fallbackThreshold: state.cohort.effectiveFallbackThreshold
105519
+ });
105520
+ const sighash = fallbackSighash(tx, 0, prevOutScript, prevOutValue, fallbackLeaf);
105521
+ const signature = this.#signer.withSecret((secretKey) => schnorr2.sign(sighash, secretKey));
105522
+ state.phase = "Complete" /* Complete */;
105523
+ return [createFallbackSignatureMessage({
105524
+ from: this.did,
105525
+ to: state.serviceDid,
105526
+ cohortId,
105527
+ sessionId: req.sessionId,
105528
+ signerPk: this.publicKey.slice(1),
105529
+ fallbackSignature: signature
105530
+ })];
105531
+ }
104778
105532
  getCohortPhase(cohortId) {
104779
105533
  return this.#cohortStates.get(cohortId)?.phase;
104780
105534
  }
@@ -104811,6 +105565,85 @@ var KeyPairAggregationSigner = class {
104811
105565
  }
104812
105566
  };
104813
105567
 
105568
+ // src/core/aggregation/recovery-spend.ts
105569
+ init_shim();
105570
+ init_secp256k1();
105571
+ var DUST_LIMIT_SATS = 546n;
105572
+ function bytesEqual2(a2, b) {
105573
+ return a2.length === b.length && a2.every((x, i4) => x === b[i4]);
105574
+ }
105575
+ function buildRecoverySpend(params) {
105576
+ const {
105577
+ cohortKeys,
105578
+ recoverySecretKey,
105579
+ recoveryKey,
105580
+ recoverySequence,
105581
+ fallbackThreshold,
105582
+ fundingModel,
105583
+ network,
105584
+ utxo,
105585
+ destinationAddress,
105586
+ fee,
105587
+ beaconAddress
105588
+ } = params;
105589
+ if (cohortKeys.length === 0) {
105590
+ throw new AggregationCohortError(
105591
+ "Cannot build recovery spend: no cohort keys.",
105592
+ "NO_COHORT_KEYS"
105593
+ );
105594
+ }
105595
+ const derivedPub = schnorr2.getPublicKey(recoverySecretKey);
105596
+ if (!bytesEqual2(derivedPub, recoveryKey)) {
105597
+ throw new AggregationCohortError(
105598
+ "Recovery secret key does not correspond to the committed recovery key.",
105599
+ "RECOVERY_KEY_MISMATCH"
105600
+ );
105601
+ }
105602
+ const out = utxo.value - fee;
105603
+ if (out <= 0n) {
105604
+ throw new AggregationCohortError(
105605
+ `Recovery fee ${fee} exceeds UTXO value ${utxo.value}.`,
105606
+ "FEE_EXCEEDS_VALUE",
105607
+ { fee: fee.toString(), value: utxo.value.toString() }
105608
+ );
105609
+ }
105610
+ if (out < DUST_LIMIT_SATS) {
105611
+ throw new AggregationCohortError(
105612
+ `Recovered output ${out} is below the dust limit ${DUST_LIMIT_SATS}; the spend would not relay.`,
105613
+ "DUST_OUTPUT",
105614
+ { output: out.toString(), dustLimit: DUST_LIMIT_SATS.toString() }
105615
+ );
105616
+ }
105617
+ const internalKey = keyAggExport(keyAggregate(sortKeys(cohortKeys)));
105618
+ const leaves = buildRecoveryLeaves(fundingModel ?? DEFAULT_FUNDING_MODEL, {
105619
+ recoveryKey,
105620
+ recoverySequence,
105621
+ cohortKeys,
105622
+ fallbackThreshold: resolveFallbackThreshold(fallbackThreshold, cohortKeys.length)
105623
+ });
105624
+ const net = getNetwork(network);
105625
+ const payment = p2tr(internalKey, leaves, net, true);
105626
+ if (beaconAddress !== void 0 && payment.address !== beaconAddress) {
105627
+ throw new AggregationCohortError(
105628
+ `Reconstructed beacon address ${payment.address} does not match the cohort's funded address ${beaconAddress}.`,
105629
+ "BEACON_ADDRESS_MISMATCH",
105630
+ { reconstructed: payment.address, expected: beaconAddress }
105631
+ );
105632
+ }
105633
+ const tx = new Transaction({ version: 2, allowUnknownInputs: true });
105634
+ tx.addInput({
105635
+ txid: utxo.txid,
105636
+ index: utxo.vout,
105637
+ witnessUtxo: { script: payment.script, amount: utxo.value },
105638
+ tapLeafScript: payment.tapLeafScript,
105639
+ sequence: recoverySequence
105640
+ });
105641
+ tx.addOutputAddress(destinationAddress, out, net);
105642
+ tx.signIdx(recoverySecretKey, 0);
105643
+ tx.finalize();
105644
+ return tx;
105645
+ }
105646
+
104814
105647
  // src/core/aggregation/logger.ts
104815
105648
  init_shim();
104816
105649
  var CONSOLE_LOGGER = {
@@ -104843,6 +105676,7 @@ var KEYGEN_VALUES = /* @__PURE__ */ new Set([
104843
105676
  ]);
104844
105677
  var UPDATE_VALUES = /* @__PURE__ */ new Set([
104845
105678
  SUBMIT_UPDATE,
105679
+ SUBMIT_NONINCLUDED,
104846
105680
  DISTRIBUTE_AGGREGATED_DATA,
104847
105681
  VALIDATION_ACK
104848
105682
  ]);
@@ -104850,7 +105684,9 @@ var SIGN_VALUES = /* @__PURE__ */ new Set([
104850
105684
  AUTHORIZATION_REQUEST,
104851
105685
  NONCE_CONTRIBUTION,
104852
105686
  AGGREGATED_NONCE,
104853
- SIGNATURE_AUTHORIZATION
105687
+ SIGNATURE_AUTHORIZATION,
105688
+ FALLBACK_AUTHORIZATION_REQUEST,
105689
+ FALLBACK_SIGNATURE
104854
105690
  ]);
104855
105691
  function isAggregationMessageType(type) {
104856
105692
  return KEYGEN_VALUES.has(type) || UPDATE_VALUES.has(type) || SIGN_VALUES.has(type);
@@ -111011,7 +111847,7 @@ var NostrTransport = class _NostrTransport {
111011
111847
  * backfill historical events to late subscribers: republishing gives late
111012
111848
  * joiners a window to discover the message without requiring protocol
111013
111849
  * changes. Relay rate-limit / publish failures inside the interval are
111014
- * caught and logged rather than propagated the caller should stop the
111850
+ * caught and logged rather than propagated - the caller should stop the
111015
111851
  * repeater once the protocol condition is satisfied.
111016
111852
  */
111017
111853
  publishRepeating(message2, sender, intervalMs, recipient) {
@@ -111465,35 +112301,29 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
111465
112301
  session;
111466
112302
  #transport;
111467
112303
  #did;
111468
- #config;
112304
+ #defaultConfig;
111469
112305
  #onOptInReceived;
111470
112306
  #onReadyToFinalize;
111471
112307
  #onProvideTxData;
111472
112308
  #cohortTtlMs;
111473
112309
  #phaseTimeoutMs;
111474
112310
  #advertRepeatIntervalMs;
111475
- #cohortId;
112311
+ #autoFallbackOnStall;
112312
+ /** Phases during which a stall can be salvaged by the k-of-n fallback (ADR 042). */
112313
+ static #SIGNING_PHASES = [
112314
+ "SigningStarted" /* SigningStarted */,
112315
+ "NoncesCollected" /* NoncesCollected */,
112316
+ "AwaitingPartialSigs" /* AwaitingPartialSigs */
112317
+ ];
112318
+ /** Per-cohort run state, keyed by cohortId. */
112319
+ #contexts = /* @__PURE__ */ new Map();
111476
112320
  #handlersRegistered = false;
111477
112321
  #stopped = false;
111478
- /**
111479
- * Guard against the async race where two concurrent #handleOptIn invocations
111480
- * both pass the `participants.length >= minParticipants` check before either
111481
- * mutates the cohort phase. Set synchronously before any `await` so subsequent
111482
- * handlers observe it on their next resumption.
111483
- */
111484
- #finalizing = false;
111485
- #resolveRun;
111486
- #rejectRun;
111487
- #cohortTtlTimer;
111488
- #phaseTimer;
111489
- #lastObservedPhase;
111490
- /** Stop handle for the repeating COHORT_ADVERT publish loop. */
111491
- #stopAdvertRepeat;
111492
112322
  constructor(options2) {
111493
112323
  super();
111494
112324
  this.#transport = options2.transport;
111495
112325
  this.#did = options2.did;
111496
- this.#config = options2.config;
112326
+ this.#defaultConfig = options2.config;
111497
112327
  this.#onOptInReceived = options2.onOptInReceived ?? (async () => ({ accepted: true }));
111498
112328
  this.#onReadyToFinalize = options2.onReadyToFinalize ?? (async ({ acceptedCount, minRequired }) => ({
111499
112329
  finalize: acceptedCount >= minRequired
@@ -111502,6 +112332,7 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
111502
112332
  this.#cohortTtlMs = options2.cohortTtlMs;
111503
112333
  this.#phaseTimeoutMs = options2.phaseTimeoutMs;
111504
112334
  this.#advertRepeatIntervalMs = options2.advertRepeatIntervalMs ?? DEFAULT_ADVERT_REPEAT_INTERVAL_MS;
112335
+ this.#autoFallbackOnStall = options2.autoFallbackOnStall ?? false;
111505
112336
  this.session = new AggregationService({
111506
112337
  // The coordinator never signs, so the state machine receives only the
111507
112338
  // public half of the operator's keypair (see ADR 038). The full keypair
@@ -111511,55 +112342,126 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
111511
112342
  maxUpdateSizeBytes: options2.maxUpdateSizeBytes
111512
112343
  });
111513
112344
  }
112345
+ /** Resolve the {@link RunContext} an inbound message belongs to, by cohortId. */
112346
+ #contextFor(msg) {
112347
+ const cohortId = msg.body?.cohortId;
112348
+ if (!cohortId) return void 0;
112349
+ return this.#contexts.get(cohortId);
112350
+ }
111514
112351
  /**
111515
- * Drain any silent rejections the state machine recorded during the most
111516
- * recent receive() and surface them as `message-rejected` events. Safe to
111517
- * call even before a cohortId is assigned.
112352
+ * Drain any silent rejections the state machine recorded for a cohort during
112353
+ * the most recent receive() and surface them as `message-rejected` events.
111518
112354
  */
111519
- #drainRejections() {
111520
- if (!this.#cohortId) return;
111521
- for (const r2 of this.session.drainRejections(this.#cohortId)) {
111522
- this.emit("message-rejected", { cohortId: this.#cohortId, ...r2 });
112355
+ #drainRejections(ctx) {
112356
+ for (const r2 of this.session.drainRejections(ctx.cohortId)) {
112357
+ this.emit("message-rejected", { cohortId: ctx.cohortId, ...r2 });
111523
112358
  }
111524
112359
  }
111525
112360
  /**
111526
- * Run the protocol to completion. Resolves with the final aggregation result
111527
- * (signature + signed transaction) once signing is complete.
112361
+ * Advertise a new cohort and begin driving it to completion. Callable many
112362
+ * times on one runner; each cohort runs concurrently and independently.
112363
+ *
112364
+ * @param config Per-cohort conditions + network (see {@link CohortConfig}).
112365
+ * @returns The new cohort's id and a `completion` promise that resolves with
112366
+ * that cohort's {@link AggregationResult} (or rejects if it fails/stalls).
112367
+ * @throws If the runner has been stopped, or the config is invalid
112368
+ * (fail-fast via `createCohort`).
112369
+ */
112370
+ advertiseCohort(config) {
112371
+ if (this.#stopped) {
112372
+ throw new AggregationServiceError("Cannot advertise on a stopped runner.", "RUNNER_STOPPED", {});
112373
+ }
112374
+ this.#registerHandlers();
112375
+ const cohortId = this.session.createCohort(config);
112376
+ let resolve;
112377
+ let reject;
112378
+ const completion = new Promise((res, rej) => {
112379
+ resolve = res;
112380
+ reject = rej;
112381
+ });
112382
+ const ctx = {
112383
+ cohortId,
112384
+ config,
112385
+ resolve,
112386
+ reject,
112387
+ completion,
112388
+ finalizing: false,
112389
+ settled: false
112390
+ };
112391
+ this.#contexts.set(cohortId, ctx);
112392
+ try {
112393
+ this.#startTimers(ctx);
112394
+ const advertMsgs = this.session.advertise(cohortId);
112395
+ this.#onPhaseMaybeChanged(ctx);
112396
+ this.emit("cohort-advertised", { cohortId });
112397
+ if (this.#advertRepeatIntervalMs > 0) {
112398
+ this.#startAdvertRepeat(ctx, advertMsgs);
112399
+ } else {
112400
+ this.#sendAll(advertMsgs).catch((err) => this.#failCohort(ctx, err));
112401
+ }
112402
+ } catch (err) {
112403
+ this.#failCohort(ctx, err);
112404
+ }
112405
+ return { cohortId, completion };
112406
+ }
112407
+ /**
112408
+ * Run a single cohort to completion using the `config` supplied in the
112409
+ * runner options. Thin convenience over {@link advertiseCohort} for the
112410
+ * single-cohort case (and the path {@link AggregationRunner.solo} rides).
111528
112411
  *
111529
112412
  * @returns {Promise<AggregationResult>} The final result with signature and signed tx.
111530
112413
  */
111531
112414
  run() {
111532
- return new Promise((resolve, reject) => {
111533
- this.#resolveRun = resolve;
111534
- this.#rejectRun = reject;
111535
- try {
111536
- this.#registerHandlers();
111537
- this.#cohortId = this.session.createCohort(this.#config);
111538
- this.#startTimers();
111539
- const advertMsgs = this.session.advertise(this.#cohortId);
111540
- this.#onPhaseMaybeChanged();
111541
- this.emit("cohort-advertised", { cohortId: this.#cohortId });
111542
- if (this.#advertRepeatIntervalMs > 0) {
111543
- this.#startAdvertRepeat(advertMsgs);
111544
- } else {
111545
- this.#sendAll(advertMsgs).catch((err) => this.#fail(err));
111546
- }
111547
- } catch (err) {
111548
- this.#fail(err);
112415
+ if (!this.#defaultConfig) {
112416
+ return Promise.reject(new AggregationServiceError(
112417
+ "run() requires `config` in the runner options; use advertiseCohort(config) to drive cohorts explicitly.",
112418
+ "MISSING_COHORT_CONFIG",
112419
+ {}
112420
+ ));
112421
+ }
112422
+ try {
112423
+ return this.advertiseCohort(this.#defaultConfig).completion;
112424
+ } catch (err) {
112425
+ return Promise.reject(err);
112426
+ }
112427
+ }
112428
+ /**
112429
+ * Wait for every currently-outstanding cohort to settle and return the
112430
+ * successful results. Dynamic drain: cohorts advertised while this is pending
112431
+ * are included, and it resolves only once no cohorts remain. Failed cohorts
112432
+ * are surfaced via `error` / `cohort-failed` events and their rejected
112433
+ * `completion` promises; they are omitted from the returned array (this
112434
+ * method does not throw). Bound long-running cohorts with `cohortTtlMs` /
112435
+ * `phaseTimeoutMs` or this may never resolve.
112436
+ *
112437
+ * @returns {Promise<AggregationResult[]>} Results of the cohorts that completed.
112438
+ */
112439
+ async runAll() {
112440
+ const collected = /* @__PURE__ */ new Map();
112441
+ const onComplete = (result) => {
112442
+ collected.set(result.cohortId, result);
112443
+ };
112444
+ this.on("signing-complete", onComplete);
112445
+ try {
112446
+ while (this.#contexts.size > 0) {
112447
+ await Promise.allSettled([...this.#contexts.values()].map((c2) => c2.completion));
111549
112448
  }
111550
- });
112449
+ } finally {
112450
+ this.off("signing-complete", onComplete);
112451
+ }
112452
+ return [...collected.values()];
111551
112453
  }
111552
112454
  /**
111553
- * Begin publishing the cohort advert immediately and on a repeating interval
111554
- * until {@link #stopAdvertRepeating} is called. Each advert is broadcast
111555
- * (no recipient) via the transport's `publishRepeating` primitive.
112455
+ * Begin publishing a cohort's advert immediately and on a repeating interval
112456
+ * until the cohort's advert loop is stopped. Each advert is broadcast (no
112457
+ * recipient) via the transport's `publishRepeating` primitive.
111556
112458
  */
111557
- #startAdvertRepeat(advertMsgs) {
112459
+ #startAdvertRepeat(ctx, advertMsgs) {
111558
112460
  const stops = [];
111559
112461
  for (const msg of advertMsgs) {
111560
112462
  stops.push(this.#transport.publishRepeating(msg, this.#did, this.#advertRepeatIntervalMs));
111561
112463
  }
111562
- this.#stopAdvertRepeat = () => {
112464
+ ctx.stopAdvertRepeat = () => {
111563
112465
  for (const stop2 of stops) {
111564
112466
  try {
111565
112467
  stop2();
@@ -111568,81 +112470,176 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
111568
112470
  }
111569
112471
  };
111570
112472
  }
111571
- /** Stop the advert republish loop. Idempotent. */
111572
- #stopAdvertRepeating() {
111573
- if (!this.#stopAdvertRepeat) return;
111574
- const stop2 = this.#stopAdvertRepeat;
111575
- this.#stopAdvertRepeat = void 0;
112473
+ /** Stop a cohort's advert republish loop. Idempotent. */
112474
+ #stopAdvertRepeating(ctx) {
112475
+ if (!ctx.stopAdvertRepeat) return;
112476
+ const stop2 = ctx.stopAdvertRepeat;
112477
+ ctx.stopAdvertRepeat = void 0;
111576
112478
  stop2();
111577
112479
  }
111578
- /** Schedule cohort TTL + phase timeout at the start of a run. */
111579
- #startTimers() {
112480
+ /** Schedule a cohort's TTL + phase timeout when it is advertised. */
112481
+ #startTimers(ctx) {
111580
112482
  if (this.#cohortTtlMs !== void 0) {
111581
- this.#cohortTtlTimer = setTimeout(() => {
111582
- const reason = `Cohort ${this.#cohortId ?? ""} exceeded TTL of ${this.#cohortTtlMs}ms`;
111583
- this.emit("cohort-failed", { cohortId: this.#cohortId ?? "", reason });
111584
- this.#fail(new Error(reason));
112483
+ ctx.cohortTtlTimer = setTimeout(() => {
112484
+ const reason = `Cohort ${ctx.cohortId} exceeded TTL of ${this.#cohortTtlMs}ms`;
112485
+ this.emit("cohort-failed", { cohortId: ctx.cohortId, reason });
112486
+ this.#failCohort(ctx, new Error(reason));
111585
112487
  }, this.#cohortTtlMs);
111586
112488
  }
111587
- this.#resetPhaseTimer();
112489
+ this.#resetPhaseTimer(ctx);
111588
112490
  }
111589
- /** Reset the per-phase stall timer. Called when a phase transition is observed. */
111590
- #resetPhaseTimer() {
111591
- if (this.#phaseTimer) clearTimeout(this.#phaseTimer);
111592
- this.#phaseTimer = void 0;
112491
+ /** Reset a cohort's per-phase stall timer. Called when a phase transition is observed. */
112492
+ #resetPhaseTimer(ctx) {
112493
+ if (ctx.phaseTimer) clearTimeout(ctx.phaseTimer);
112494
+ ctx.phaseTimer = void 0;
111593
112495
  if (this.#phaseTimeoutMs === void 0) return;
111594
- this.#phaseTimer = setTimeout(() => {
111595
- const reason = `Cohort ${this.#cohortId ?? ""} stalled in phase ${this.#lastObservedPhase ?? "?"} for ${this.#phaseTimeoutMs}ms`;
111596
- this.emit("cohort-failed", { cohortId: this.#cohortId ?? "", reason });
111597
- this.#fail(new Error(reason));
112496
+ ctx.phaseTimer = setTimeout(() => {
112497
+ const phase = this.session.getCohortPhase(ctx.cohortId);
112498
+ const inSigning = phase !== void 0 && _AggregationServiceRunner.#SIGNING_PHASES.includes(phase);
112499
+ if (this.#autoFallbackOnStall && inSigning && !ctx.committedPath && !ctx.settled) {
112500
+ this.triggerFallback(ctx.cohortId).catch((err) => this.#failCohort(ctx, err));
112501
+ return;
112502
+ }
112503
+ const reason = `Cohort ${ctx.cohortId} stalled in phase ${ctx.lastObservedPhase ?? "?"} for ${this.#phaseTimeoutMs}ms`;
112504
+ this.emit("cohort-failed", { cohortId: ctx.cohortId, reason });
112505
+ this.#failCohort(ctx, new Error(reason));
111598
112506
  }, this.#phaseTimeoutMs);
111599
112507
  }
111600
- /** Detect a phase change since the last observation and reset the phase timer. */
111601
- #onPhaseMaybeChanged() {
111602
- if (!this.#cohortId) return;
111603
- const phase = this.session.getCohortPhase(this.#cohortId);
111604
- if (phase !== this.#lastObservedPhase) {
111605
- this.#lastObservedPhase = phase;
111606
- this.#resetPhaseTimer();
111607
- }
112508
+ /**
112509
+ * Abandon the optimistic n-of-n key path for a cohort and collect k-of-n
112510
+ * fallback (script-path) signatures instead (ADR 042). Idempotent and safe
112511
+ * against the optimistic completion: it commits the cohort to the fallback
112512
+ * path synchronously (the `committedPath` latch) before sending anything, so a
112513
+ * late optimistic signature can no longer complete-and-broadcast a competing
112514
+ * spend of the same UTXO. No-op if the cohort is unknown, already settled, or
112515
+ * already committed to a path.
112516
+ *
112517
+ * Wired automatically to the phase-stall timer when `autoFallbackOnStall` is
112518
+ * set; otherwise call it from an operator decision (a UI "fall back now"
112519
+ * action). Throws only if the underlying state machine rejects the transition
112520
+ * (e.g. signing has not started).
112521
+ */
112522
+ async triggerFallback(cohortId) {
112523
+ const ctx = this.#contexts.get(cohortId);
112524
+ if (!ctx || ctx.settled || ctx.committedPath) return;
112525
+ const messages2 = this.session.startFallbackSigning(cohortId);
112526
+ ctx.committedPath = "fallback";
112527
+ this.#stopAdvertRepeating(ctx);
112528
+ this.#onPhaseMaybeChanged(ctx);
112529
+ const sessionId = this.session.getSigningSessionId(cohortId) ?? "";
112530
+ this.emit("fallback-started", { cohortId, sessionId });
112531
+ await this.#sendAll(messages2);
112532
+ }
112533
+ /** Detect a phase change for a cohort since the last observation and reset its phase timer. */
112534
+ #onPhaseMaybeChanged(ctx) {
112535
+ const phase = this.session.getCohortPhase(ctx.cohortId);
112536
+ if (phase !== ctx.lastObservedPhase) {
112537
+ ctx.lastObservedPhase = phase;
112538
+ this.#resetPhaseTimer(ctx);
112539
+ }
112540
+ }
112541
+ /** Clear a cohort's timers. Called on completion, stop, and failure. */
112542
+ #clearTimers(ctx) {
112543
+ if (ctx.cohortTtlTimer) clearTimeout(ctx.cohortTtlTimer);
112544
+ if (ctx.phaseTimer) clearTimeout(ctx.phaseTimer);
112545
+ ctx.cohortTtlTimer = void 0;
112546
+ ctx.phaseTimer = void 0;
111608
112547
  }
111609
- /** Clear both timers. Called on successful completion, stop(), and #fail. */
111610
- #clearTimers() {
111611
- if (this.#cohortTtlTimer) clearTimeout(this.#cohortTtlTimer);
111612
- if (this.#phaseTimer) clearTimeout(this.#phaseTimer);
111613
- this.#cohortTtlTimer = void 0;
111614
- this.#phaseTimer = void 0;
112548
+ /**
112549
+ * Reclaim one cohort's runner-layer bookkeeping: stop its advert loop, clear
112550
+ * its timers, and drop its {@link RunContext}. Does NOT touch sibling cohorts
112551
+ * and does NOT detach the shared transport handlers. Leaves the cohort in the
112552
+ * state machine; whether that cohort's `session` state is also removed is the
112553
+ * caller's choice (see {@link #completeCohort} vs {@link #failCohort}).
112554
+ */
112555
+ #disposeCohort(ctx) {
112556
+ this.#stopAdvertRepeating(ctx);
112557
+ this.#clearTimers(ctx);
112558
+ this.#contexts.delete(ctx.cohortId);
112559
+ }
112560
+ /**
112561
+ * Settle one cohort successfully. Reclaims the runner context but leaves the
112562
+ * completed cohort in `session` so callers can read its beaconAddress / cohort
112563
+ * via `session.getCohort(result.cohortId)`; reclaim it with
112564
+ * `session.removeCohort(cohortId)` when done. Idempotent via `ctx.settled`.
112565
+ */
112566
+ #completeCohort(ctx, result) {
112567
+ if (ctx.settled) return;
112568
+ ctx.settled = true;
112569
+ this.#disposeCohort(ctx);
112570
+ this.emit("signing-complete", result);
112571
+ ctx.resolve(result);
112572
+ }
112573
+ /**
112574
+ * Fail one cohort. Reclaims its runner context, drops its now-dead state from
112575
+ * the state machine, and rejects only its completion; siblings keep running
112576
+ * and the shared transport handlers stay registered. Idempotent via
112577
+ * `ctx.settled`.
112578
+ */
112579
+ #failCohort(ctx, err) {
112580
+ if (ctx.settled) return;
112581
+ ctx.settled = true;
112582
+ this.#disposeCohort(ctx);
112583
+ this.session.removeCohort(ctx.cohortId);
112584
+ this.emit("error", err);
112585
+ ctx.reject(err);
112586
+ }
112587
+ /**
112588
+ * Stop a single cohort early without affecting the rest of the runner. Drops
112589
+ * the cohort's state machine state; its `completion` promise rejects with a
112590
+ * stopped error.
112591
+ */
112592
+ stopCohort(cohortId) {
112593
+ const ctx = this.#contexts.get(cohortId);
112594
+ if (!ctx || ctx.settled) return;
112595
+ ctx.settled = true;
112596
+ this.#disposeCohort(ctx);
112597
+ this.session.removeCohort(cohortId);
112598
+ ctx.reject(new AggregationServiceError(`Cohort ${cohortId} stopped.`, "COHORT_STOPPED", { cohortId }));
111615
112599
  }
111616
112600
  /**
111617
- * Stop the runner early. Marks the runner stopped and detaches transport
111618
- * handlers so a restart or a new runner doesn't inherit stale dispatch.
112601
+ * Stop the whole runner. Fails every outstanding cohort, then detaches the
112602
+ * shared transport handlers so a restart or a new runner doesn't inherit
112603
+ * stale dispatch. Safe to call repeatedly.
111619
112604
  */
111620
112605
  stop() {
111621
112606
  this.#stopped = true;
111622
- this.#stopAdvertRepeating();
111623
- this.#clearTimers();
112607
+ for (const ctx of [...this.#contexts.values()]) {
112608
+ if (ctx.settled) continue;
112609
+ ctx.settled = true;
112610
+ this.#disposeCohort(ctx);
112611
+ this.session.removeCohort(ctx.cohortId);
112612
+ ctx.reject(new AggregationServiceError("Service runner stopped.", "RUNNER_STOPPED", { cohortId: ctx.cohortId }));
112613
+ }
112614
+ this.#contexts.clear();
111624
112615
  this.#unregisterHandlers();
111625
- if (this.#cohortId) this.session.removeCohort(this.#cohortId);
111626
112616
  }
111627
112617
  /** Message types this runner listens for on the transport. */
111628
112618
  static #HANDLED_MESSAGE_TYPES = [
111629
112619
  COHORT_OPT_IN,
111630
112620
  SUBMIT_UPDATE,
112621
+ SUBMIT_NONINCLUDED,
111631
112622
  VALIDATION_ACK,
111632
112623
  NONCE_CONTRIBUTION,
111633
- SIGNATURE_AUTHORIZATION
112624
+ SIGNATURE_AUTHORIZATION,
112625
+ FALLBACK_SIGNATURE
111634
112626
  ];
111635
112627
  /**
111636
- * Internal: handler registration with the transport. Idempotent.
112628
+ * Internal: handler registration with the transport. Idempotent. Handlers
112629
+ * are DID-scoped and cohort-agnostic - one registration serves every cohort
112630
+ * this runner drives; demux to the right {@link RunContext} happens in each
112631
+ * handler via the inbound message's cohortId.
111637
112632
  */
111638
112633
  #registerHandlers() {
111639
112634
  if (this.#handlersRegistered) return;
111640
112635
  this.#handlersRegistered = true;
111641
112636
  this.#transport.registerMessageHandler(this.#did, COHORT_OPT_IN, this.#handleOptIn.bind(this));
111642
112637
  this.#transport.registerMessageHandler(this.#did, SUBMIT_UPDATE, this.#handleSubmitUpdate.bind(this));
112638
+ this.#transport.registerMessageHandler(this.#did, SUBMIT_NONINCLUDED, this.#handleSubmitUpdate.bind(this));
111643
112639
  this.#transport.registerMessageHandler(this.#did, VALIDATION_ACK, this.#handleValidationAck.bind(this));
111644
112640
  this.#transport.registerMessageHandler(this.#did, NONCE_CONTRIBUTION, this.#handleNonceContribution.bind(this));
111645
112641
  this.#transport.registerMessageHandler(this.#did, SIGNATURE_AUTHORIZATION, this.#handleSignatureAuthorization.bind(this));
112642
+ this.#transport.registerMessageHandler(this.#did, FALLBACK_SIGNATURE, this.#handleFallbackSignature.bind(this));
111646
112643
  }
111647
112644
  /** Internal: detach from the transport. Safe to call repeatedly. */
111648
112645
  #unregisterHandlers() {
@@ -111654,23 +112651,25 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
111654
112651
  }
111655
112652
  /**
111656
112653
  * Internal: message handlers for each protocol step. Each handler:
111657
- * 1) feeds the message into the state machine via session.receive()
111658
- * 2) emits a high-level event for external observers
111659
- * 3) checks if the new state triggers any automatic next steps, and if so:
112654
+ * 1) resolves the cohort the message belongs to (by cohortId); ignores it if unknown
112655
+ * 2) feeds the message into the state machine via session.receive()
112656
+ * 3) emits a high-level event (carrying cohortId) for external observers
112657
+ * 4) checks if the new state triggers any automatic next steps, and if so:
111660
112658
  * a) calls the appropriate decision callback(s)
111661
112659
  * b) sends any resulting messages from the state machine
112660
+ * Errors fail only the owning cohort. A stopped runner ignores messages.
111662
112661
  * @param {BaseMessage} msg - The incoming message to handle.
111663
112662
  * @returns {Promise<void>} Resolves when handling is complete.
111664
- * @throws {Error} If any step of handling fails, the error is emitted and the run promise is rejected.
111665
- * Note: if the runner has been stopped, handlers will ignore incoming messages.
111666
112663
  */
111667
112664
  async #handleOptIn(msg) {
111668
112665
  if (this.#stopped) return;
112666
+ const ctx = this.#contextFor(msg);
112667
+ if (!ctx) return;
111669
112668
  try {
111670
112669
  this.session.receive(msg);
111671
- this.#drainRejections();
111672
- this.#onPhaseMaybeChanged();
111673
- const optIn = this.session.pendingOptIns(this.#cohortId).get(msg.from);
112670
+ this.#drainRejections(ctx);
112671
+ this.#onPhaseMaybeChanged(ctx);
112672
+ const optIn = this.session.pendingOptIns(ctx.cohortId).get(msg.from);
111674
112673
  if (!optIn) return;
111675
112674
  this.emit("opt-in-received", optIn);
111676
112675
  if (optIn.communicationPk) {
@@ -111678,34 +112677,34 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
111678
112677
  }
111679
112678
  const decision = await this.#onOptInReceived(optIn);
111680
112679
  if (!decision.accepted) return;
111681
- const maxParticipants = this.#config.maxParticipants;
111682
- const cohortNow = this.session.getCohort(this.#cohortId);
112680
+ const maxParticipants = ctx.config.maxParticipants;
112681
+ const cohortNow = this.session.getCohort(ctx.cohortId);
111683
112682
  if (maxParticipants !== void 0 && cohortNow && cohortNow.participants.length >= maxParticipants) {
111684
112683
  return;
111685
112684
  }
111686
- await this.#sendAll(this.session.acceptParticipant(this.#cohortId, msg.from));
111687
- this.emit("participant-accepted", { participantDid: msg.from });
111688
- const cohort = this.session.getCohort(this.#cohortId);
111689
- if (cohort.participants.length >= this.#config.minParticipants && !this.#finalizing) {
111690
- this.#finalizing = true;
112685
+ await this.#sendAll(this.session.acceptParticipant(ctx.cohortId, msg.from));
112686
+ this.emit("participant-accepted", { cohortId: ctx.cohortId, participantDid: msg.from });
112687
+ const cohort = this.session.getCohort(ctx.cohortId);
112688
+ if (cohort.participants.length >= ctx.config.minParticipants && !ctx.finalizing) {
112689
+ ctx.finalizing = true;
111691
112690
  const finalizeDecision = await this.#onReadyToFinalize({
111692
112691
  acceptedCount: cohort.participants.length,
111693
- minRequired: this.#config.minParticipants
112692
+ minRequired: ctx.config.minParticipants
111694
112693
  });
111695
112694
  if (!finalizeDecision.finalize) {
111696
- this.#finalizing = false;
112695
+ ctx.finalizing = false;
111697
112696
  return;
111698
112697
  }
111699
- const readyMsgs = this.session.finalizeKeygen(this.#cohortId);
111700
- this.#stopAdvertRepeating();
112698
+ const readyMsgs = this.session.finalizeKeygen(ctx.cohortId);
112699
+ this.#stopAdvertRepeating(ctx);
111701
112700
  this.emit("keygen-complete", {
111702
- cohortId: this.#cohortId,
112701
+ cohortId: ctx.cohortId,
111703
112702
  beaconAddress: cohort.beaconAddress
111704
112703
  });
111705
112704
  await this.#sendAll(readyMsgs);
111706
112705
  }
111707
112706
  } catch (err) {
111708
- this.#fail(err);
112707
+ this.#failCohort(ctx, err);
111709
112708
  }
111710
112709
  }
111711
112710
  /**
@@ -111713,23 +112712,23 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
111713
112712
  * and distributes the data for validation.
111714
112713
  * @param {BaseMessage} msg - The incoming message to handle.
111715
112714
  * @returns {Promise<void>} Resolves when handling is complete.
111716
- * @throws {Error} If any step of handling fails, the error is emitted and the run promise is rejected.
111717
- * Note: if the runner has been stopped, handlers will ignore incoming messages.
111718
112715
  */
111719
112716
  async #handleSubmitUpdate(msg) {
111720
112717
  if (this.#stopped) return;
112718
+ const ctx = this.#contextFor(msg);
112719
+ if (!ctx) return;
111721
112720
  try {
111722
112721
  this.session.receive(msg);
111723
- this.#drainRejections();
111724
- this.#onPhaseMaybeChanged();
111725
- this.emit("update-received", { participantDid: msg.from });
111726
- if (this.session.getCohortPhase(this.#cohortId) === "UpdatesCollected" /* UpdatesCollected */) {
111727
- const distributeMsgs = this.session.buildAndDistribute(this.#cohortId);
111728
- this.emit("data-distributed", { cohortId: this.#cohortId });
112722
+ this.#drainRejections(ctx);
112723
+ this.#onPhaseMaybeChanged(ctx);
112724
+ this.emit("update-received", { cohortId: ctx.cohortId, participantDid: msg.from });
112725
+ if (this.session.getCohortPhase(ctx.cohortId) === "UpdatesCollected" /* UpdatesCollected */) {
112726
+ const distributeMsgs = this.session.buildAndDistribute(ctx.cohortId);
112727
+ this.emit("data-distributed", { cohortId: ctx.cohortId });
111729
112728
  await this.#sendAll(distributeMsgs);
111730
112729
  }
111731
112730
  } catch (err) {
111732
- this.#fail(err);
112731
+ this.#failCohort(ctx, err);
111733
112732
  }
111734
112733
  }
111735
112734
  /**
@@ -111737,111 +112736,121 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
111737
112736
  * automatically requests tx data and starts signing.
111738
112737
  * @param {BaseMessage} msg - The incoming message to handle.
111739
112738
  * @returns {Promise<void>} Resolves when handling is complete.
111740
- * @throws {Error} If any step of handling fails, the error is emitted and the run promise is rejected.
111741
- * Note: if the runner has been stopped, handlers will ignore incoming messages.
111742
112739
  */
111743
112740
  async #handleValidationAck(msg) {
111744
112741
  if (this.#stopped) return;
112742
+ const ctx = this.#contextFor(msg);
112743
+ if (!ctx) return;
111745
112744
  try {
111746
112745
  this.session.receive(msg);
111747
- this.#drainRejections();
111748
- this.#onPhaseMaybeChanged();
112746
+ this.#drainRejections(ctx);
112747
+ this.#onPhaseMaybeChanged(ctx);
111749
112748
  const approved = !!msg.body?.approved;
111750
- this.emit("validation-received", { participantDid: msg.from, approved });
111751
- const phase = this.session.getCohortPhase(this.#cohortId);
112749
+ this.emit("validation-received", { cohortId: ctx.cohortId, participantDid: msg.from, approved });
112750
+ const phase = this.session.getCohortPhase(ctx.cohortId);
111752
112751
  if (phase === "Failed" /* Failed */) {
111753
112752
  const reason = `Validation rejected by participant ${msg.from}`;
111754
- this.emit("cohort-failed", { cohortId: this.#cohortId, reason });
111755
- this.#fail(new Error(reason));
112753
+ this.emit("cohort-failed", { cohortId: ctx.cohortId, reason });
112754
+ this.#failCohort(ctx, new Error(reason));
111756
112755
  return;
111757
112756
  }
111758
112757
  if (phase === "Validated" /* Validated */) {
111759
- const cohort = this.session.getCohort(this.#cohortId);
112758
+ const cohort = this.session.getCohort(ctx.cohortId);
111760
112759
  const txData = await this.#onProvideTxData({
111761
- cohortId: this.#cohortId,
112760
+ cohortId: ctx.cohortId,
111762
112761
  beaconAddress: cohort.beaconAddress,
111763
112762
  signalBytes: cohort.signalBytes
111764
112763
  });
111765
- const authMsgs = this.session.startSigning(this.#cohortId, txData);
111766
- const sessionId = this.session.getSigningSessionId(this.#cohortId) ?? "";
111767
- this.emit("signing-started", { sessionId });
112764
+ const authMsgs = this.session.startSigning(ctx.cohortId, txData);
112765
+ const sessionId = this.session.getSigningSessionId(ctx.cohortId) ?? "";
112766
+ this.emit("signing-started", { cohortId: ctx.cohortId, sessionId });
111768
112767
  await this.#sendAll(authMsgs);
111769
112768
  }
111770
112769
  } catch (err) {
111771
- this.#fail(err);
112770
+ this.#failCohort(ctx, err);
111772
112771
  }
111773
112772
  }
111774
112773
  /**
111775
- * Handler for receiving nonce contributions and signature authorizations. When all nonces or
111776
- * signatures are received,
112774
+ * Handler for receiving nonce contributions. When all nonces are received, sends the aggregated
112775
+ * nonce back to the cohort.
111777
112776
  * @param {BaseMessage} msg - The incoming message to handle.
111778
112777
  * @returns {Promise<void>} Resolves when handling is complete.
111779
- * @throws {Error} If any step of handling fails, the error is emitted and the run promise is rejected.
111780
- * Note: if the runner has been stopped, handlers will ignore incoming messages.
111781
112778
  */
111782
112779
  async #handleNonceContribution(msg) {
111783
112780
  if (this.#stopped) return;
112781
+ const ctx = this.#contextFor(msg);
112782
+ if (!ctx) return;
111784
112783
  try {
111785
112784
  this.session.receive(msg);
111786
- this.#drainRejections();
111787
- this.#onPhaseMaybeChanged();
111788
- this.emit("nonce-received", { participantDid: msg.from });
111789
- if (this.session.getCohortPhase(this.#cohortId) === "NoncesCollected" /* NoncesCollected */) {
111790
- await this.#sendAll(this.session.sendAggregatedNonce(this.#cohortId));
112785
+ this.#drainRejections(ctx);
112786
+ this.#onPhaseMaybeChanged(ctx);
112787
+ this.emit("nonce-received", { cohortId: ctx.cohortId, participantDid: msg.from });
112788
+ if (this.session.getCohortPhase(ctx.cohortId) === "NoncesCollected" /* NoncesCollected */) {
112789
+ await this.#sendAll(this.session.sendAggregatedNonce(ctx.cohortId));
111791
112790
  }
111792
112791
  } catch (err) {
111793
- this.#fail(err);
112792
+ this.#failCohort(ctx, err);
111794
112793
  }
111795
112794
  }
111796
112795
  /**
111797
112796
  * Handler for receiving signature authorizations. When all partial signatures are received, the
111798
- * session automatically completes and the final result is emitted and the run() promise is resolved.
112797
+ * session automatically completes; the final result is emitted and the cohort's completion
112798
+ * promise resolves.
111799
112799
  * @param {BaseMessage} msg - The incoming message to handle.
111800
112800
  * @returns {Promise<void>} Resolves when handling is complete.
111801
- * @throws {Error} If any step of handling fails, the error is emitted and the run promise is rejected.
111802
- * Note: if the runner has been stopped, handlers will ignore incoming messages.
111803
112801
  */
111804
112802
  async #handleSignatureAuthorization(msg) {
111805
112803
  if (this.#stopped) return;
112804
+ const ctx = this.#contextFor(msg);
112805
+ if (!ctx) return;
112806
+ try {
112807
+ this.session.receive(msg);
112808
+ this.#drainRejections(ctx);
112809
+ this.#onPhaseMaybeChanged(ctx);
112810
+ if (ctx.committedPath === "fallback") return;
112811
+ const result = this.session.getResult(ctx.cohortId);
112812
+ if (result) {
112813
+ ctx.committedPath = "optimistic";
112814
+ this.#completeCohort(ctx, result);
112815
+ }
112816
+ } catch (err) {
112817
+ this.#failCohort(ctx, err);
112818
+ }
112819
+ }
112820
+ /**
112821
+ * Handler for receiving fallback (k-of-n script-path) signatures. The state
112822
+ * machine assembles and finalizes the fallback spend once k valid signatures
112823
+ * are in; the result is then emitted and the cohort's completion resolves. The
112824
+ * cohort is already committed to the fallback path (via {@link triggerFallback}).
112825
+ * @param {BaseMessage} msg - The incoming FALLBACK_SIGNATURE message.
112826
+ * @returns {Promise<void>} Resolves when handling is complete.
112827
+ */
112828
+ async #handleFallbackSignature(msg) {
112829
+ if (this.#stopped) return;
112830
+ const ctx = this.#contextFor(msg);
112831
+ if (!ctx) return;
111806
112832
  try {
111807
112833
  this.session.receive(msg);
111808
- this.#drainRejections();
111809
- this.#onPhaseMaybeChanged();
111810
- const result = this.session.getResult(this.#cohortId);
112834
+ this.#drainRejections(ctx);
112835
+ this.#onPhaseMaybeChanged(ctx);
112836
+ const result = this.session.getResult(ctx.cohortId);
111811
112837
  if (result) {
111812
- this.#clearTimers();
111813
- this.#unregisterHandlers();
111814
- this.emit("signing-complete", result);
111815
- this.#resolveRun?.(result);
112838
+ this.#completeCohort(ctx, result);
111816
112839
  }
111817
112840
  } catch (err) {
111818
- this.#fail(err);
112841
+ this.#failCohort(ctx, err);
111819
112842
  }
111820
112843
  }
111821
112844
  /**
111822
112845
  * Internal: helper to send all messages sequentially. Catches and propagates errors.
111823
112846
  * @param {BaseMessage[]} msgs - The messages to send.
111824
112847
  * @returns {Promise<void>} Resolves when all messages have been sent.
111825
- * @throws {Error} If sending any message fails, the error is emitted and the run promise is
111826
- * rejected.
111827
112848
  */
111828
112849
  async #sendAll(msgs) {
111829
112850
  for (const m2 of msgs) {
111830
112851
  await this.#transport.sendMessage(m2, this.#did, m2.to);
111831
112852
  }
111832
112853
  }
111833
- /**
111834
- * Internal: helper to handle errors. Emits an 'error' event and rejects the run promise.
111835
- * @param {Error} err - The error to handle.
111836
- */
111837
- #fail(err) {
111838
- this.#stopAdvertRepeating();
111839
- this.#clearTimers();
111840
- this.#unregisterHandlers();
111841
- if (this.#cohortId) this.session.removeCohort(this.#cohortId);
111842
- this.emit("error", err);
111843
- this.#rejectRun?.(err);
111844
- }
111845
112854
  };
111846
112855
 
111847
112856
  // src/core/aggregation/runner/participant-runner.ts
@@ -111890,7 +112899,8 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
111890
112899
  COHORT_READY,
111891
112900
  DISTRIBUTE_AGGREGATED_DATA,
111892
112901
  AUTHORIZATION_REQUEST,
111893
- AGGREGATED_NONCE
112902
+ AGGREGATED_NONCE,
112903
+ FALLBACK_AUTHORIZATION_REQUEST
111894
112904
  ];
111895
112905
  /** Internal: detach from the transport. Safe to call repeatedly. */
111896
112906
  #unregisterHandlers() {
@@ -111902,7 +112912,8 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
111902
112912
  }
111903
112913
  /**
111904
112914
  * Single-shot helper: start, join the first cohort that passes `shouldJoin`,
111905
- * drive it to completion, and resolve. Convenient for tests and demos.
112915
+ * drive it to completion, and resolve. Convenient for tests and demos. The
112916
+ * single-cohort special case of {@link joinMatching} (count = 1).
111906
112917
  */
111907
112918
  static async joinFirst(options2) {
111908
112919
  return new Promise((resolve, reject) => {
@@ -111915,6 +112926,37 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
111915
112926
  runner.start().catch(reject);
111916
112927
  });
111917
112928
  }
112929
+ /**
112930
+ * Multi-cohort helper: start, join EVERY cohort whose advert passes
112931
+ * `shouldJoin`, drive each to completion in parallel, and resolve once
112932
+ * `count` cohorts have completed (the runner stops at that point). The
112933
+ * N-cohort generalization of {@link joinFirst}, for a participant that joins
112934
+ * several cohorts advertised by one service.
112935
+ *
112936
+ * For an open-ended, long-lived subscriber (no fixed count), construct an
112937
+ * {@link AggregationParticipantRunner} directly, set `shouldJoin`, call
112938
+ * `start()`, and listen for `cohort-complete` - the runner already drives
112939
+ * any number of cohorts concurrently.
112940
+ *
112941
+ * @param options Participant runner options (set `shouldJoin` to select cohorts).
112942
+ * @param count Number of completed cohorts to collect before resolving.
112943
+ * @returns The {@link CohortCompleteInfo} for each completed cohort, in completion order.
112944
+ */
112945
+ static async joinMatching(options2, count) {
112946
+ return new Promise((resolve, reject) => {
112947
+ const runner = new _AggregationParticipantRunner(options2);
112948
+ const completed = [];
112949
+ runner.on("cohort-complete", (info) => {
112950
+ completed.push(info);
112951
+ if (completed.length >= count) {
112952
+ runner.stop();
112953
+ resolve(completed);
112954
+ }
112955
+ });
112956
+ runner.on("error", reject);
112957
+ runner.start().catch(reject);
112958
+ });
112959
+ }
111918
112960
  /**
111919
112961
  * Internal: handler registration with the transport. Idempotent and safe to call multiple times,
111920
112962
  * but only registers handlers once.
@@ -111928,6 +112970,7 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
111928
112970
  this.#transport.registerMessageHandler(this.#did, DISTRIBUTE_AGGREGATED_DATA, this.#handleDistributeData.bind(this));
111929
112971
  this.#transport.registerMessageHandler(this.#did, AUTHORIZATION_REQUEST, this.#handleAuthorizationRequest.bind(this));
111930
112972
  this.#transport.registerMessageHandler(this.#did, AGGREGATED_NONCE, this.#handleAggregatedNonce.bind(this));
112973
+ this.#transport.registerMessageHandler(this.#did, FALLBACK_AUTHORIZATION_REQUEST, this.#handleFallbackAuthorizationRequest.bind(this));
111931
112974
  }
111932
112975
  /**
111933
112976
  * Internal: handler for cohort adverts. Applies the `shouldJoin` filter and joins if approved.
@@ -111983,8 +113026,13 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
111983
113026
  cohortId,
111984
113027
  beaconAddress: info.beaconAddress
111985
113028
  });
111986
- await this.#sendAll(this.session.submitUpdate(cohortId, signedUpdate));
111987
- this.emit("update-submitted", { cohortId });
113029
+ if (signedUpdate === null) {
113030
+ await this.#sendAll(this.session.declineUpdate(cohortId));
113031
+ this.emit("update-declined", { cohortId });
113032
+ } else {
113033
+ await this.#sendAll(this.session.submitUpdate(cohortId, signedUpdate));
113034
+ this.emit("update-submitted", { cohortId });
113035
+ }
111988
113036
  } catch (err) {
111989
113037
  this.emit("error", err);
111990
113038
  }
@@ -112060,23 +113108,62 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
112060
113108
  const cohortId = msg.body?.cohortId;
112061
113109
  if (!cohortId) return;
112062
113110
  await this.#sendAll(this.session.generatePartialSignature(cohortId));
112063
- if (this.session.getCohortPhase(cohortId) === "Complete" /* Complete */) {
112064
- const info = this.session.joinedCohorts.get(cohortId);
112065
- if (info) {
112066
- const validation = this.session.pendingValidations.get(cohortId);
112067
- this.emit("cohort-complete", {
112068
- cohortId,
112069
- beaconAddress: info.beaconAddress,
112070
- beaconType: validation?.beaconType ?? "",
112071
- casAnnouncement: validation?.casAnnouncement,
112072
- smtProof: validation?.smtProof
112073
- });
112074
- }
113111
+ this.#emitCohortCompleteIfDone(cohortId);
113112
+ } catch (err) {
113113
+ this.emit("error", err);
113114
+ }
113115
+ }
113116
+ /**
113117
+ * Internal: handler for fallback authorization requests. The service abandoned
113118
+ * the optimistic key path; the member authorizes the k-of-n script-path spend
113119
+ * of the same beacon transaction (ADR 042). Reuses `onApproveSigning` to gate
113120
+ * the decision, signs the fallback, and completes once it has contributed (the
113121
+ * service needs only k members).
113122
+ * @param {BaseMessage} msg - The received fallback authorization request message.
113123
+ * @returns {Promise<void>} Resolves when processing is complete.
113124
+ */
113125
+ async #handleFallbackAuthorizationRequest(msg) {
113126
+ if (this.#stopped) return;
113127
+ try {
113128
+ this.session.receive(msg);
113129
+ const cohortId = msg.body?.cohortId;
113130
+ if (!cohortId) return;
113131
+ const req = this.session.pendingFallbackRequests.get(cohortId);
113132
+ if (!req) return;
113133
+ this.emit("fallback-requested", req);
113134
+ const decision = await this.#onApproveSigning(req);
113135
+ if (!decision.approved) {
113136
+ this.emit("cohort-failed", { cohortId, reason: "Fallback signing rejected by participant" });
113137
+ return;
112075
113138
  }
113139
+ await this.#sendAll(this.session.approveFallback(cohortId));
113140
+ this.#emitCohortCompleteIfDone(cohortId);
112076
113141
  } catch (err) {
112077
113142
  this.emit("error", err);
112078
113143
  }
112079
113144
  }
113145
+ /**
113146
+ * Internal: emit `cohort-complete` with the participant's retained sidecar once
113147
+ * the cohort has reached the Complete phase. Surfaces the CAS Announcement map
113148
+ * (CAS beacons) or the SMT proof (SMT beacons) the participant keeps for future
113149
+ * DID resolution. Read via getValidation (not pendingValidations, which lists
113150
+ * only the AwaitingValidation phase) so the sidecar is still available at
113151
+ * Complete. Shared by the optimistic and fallback completion paths.
113152
+ */
113153
+ #emitCohortCompleteIfDone(cohortId) {
113154
+ if (this.session.getCohortPhase(cohortId) !== "Complete" /* Complete */) return;
113155
+ const info = this.session.joinedCohorts.get(cohortId);
113156
+ if (!info) return;
113157
+ const validation = this.session.getValidation(cohortId);
113158
+ this.emit("cohort-complete", {
113159
+ cohortId,
113160
+ beaconAddress: info.beaconAddress,
113161
+ beaconType: validation?.beaconType ?? "",
113162
+ included: validation?.included ?? true,
113163
+ casAnnouncement: validation?.casAnnouncement,
113164
+ smtProof: validation?.smtProof
113165
+ });
113166
+ }
112080
113167
  /**
112081
113168
  * Internal: send helper to ensure messages are sent sequentially. This is important for protocol
112082
113169
  * correctness, as some transports may not guarantee message order if sent in parallel.
@@ -112093,6 +113180,7 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
112093
113180
 
112094
113181
  // src/core/aggregation/runner/aggregation-runner.ts
112095
113182
  init_shim();
113183
+ init_utils();
112096
113184
  var AggregationRunner = class {
112097
113185
  /**
112098
113186
  * Run a cohort of ONE participant entirely in-process and return the
@@ -112100,8 +113188,8 @@ var AggregationRunner = class {
112100
113188
  *
112101
113189
  * One party plays both the coordinating service and the lone participant,
112102
113190
  * connected over an {@link InMemoryTransport} (no relay or HTTP server). This
112103
- * makes the single-participant aggregate-beacon path the N=1 corner of the
112104
- * two-axis beacon matrix (see ADR 037) first-class, useful for generating
113191
+ * makes the single-participant aggregate-beacon path (the N=1 corner of the
113192
+ * two-axis beacon matrix, see ADR 037) first-class, useful for generating
112105
113193
  * and reproducing single-participant aggregate test vectors.
112106
113194
  *
112107
113195
  * The service advertises a cohort with `minParticipants: 1`; the participant
@@ -112119,11 +113207,13 @@ var AggregationRunner = class {
112119
113207
  transport.registerPeer(options2.participant.did, options2.participant.keys.publicKey.compressed);
112120
113208
  transport.registerPeer(options2.service.did, options2.service.keys.publicKey.compressed);
112121
113209
  transport.start();
113210
+ const recoveryKey = options2.config.recoveryKey ?? bytesToHex(options2.service.keys.publicKey.compressed.slice(1));
113211
+ const recoverySequence = options2.config.recoverySequence ?? DEFAULT_RECOVERY_SEQUENCE;
112122
113212
  const service = new AggregationServiceRunner({
112123
113213
  transport,
112124
113214
  did: options2.service.did,
112125
113215
  keys: options2.service.keys,
112126
- config: { minParticipants: 1, network: options2.config.network, beaconType: options2.config.beaconType },
113216
+ config: { minParticipants: 1, network: options2.config.network, beaconType: options2.config.beaconType, recoveryKey, recoverySequence, fundingModel: DEFAULT_FUNDING_MODEL },
112127
113217
  onProvideTxData: options2.onProvideTxData,
112128
113218
  cohortTtlMs: options2.cohortTtlMs,
112129
113219
  phaseTimeoutMs: options2.phaseTimeoutMs,
@@ -112253,8 +113343,7 @@ async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
112253
113343
  async function buildAggregationBeaconTx(opts) {
112254
113344
  const feeEstimator = opts.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
112255
113345
  const { utxo, prevTxBytes } = await fetchSpendableUtxo(opts.beaconAddress, opts.bitcoin);
112256
- const tapOut = p2tr(opts.internalPubkey, void 0, opts.network);
112257
- const witnessScript = tapOut.script;
113346
+ const witnessScript = OutScript.encode(Address(opts.network).decode(opts.beaconAddress));
112258
113347
  const feeSats = await feeEstimator.estimateFee(P2TR_BEACON_TX_VSIZE);
112259
113348
  if (BigInt(utxo.value) <= feeSats) {
112260
113349
  throw new BeaconError(
@@ -112333,7 +113422,7 @@ var SinglePartyBeacon = class {
112333
113422
  *
112334
113423
  * Composed from the three extracted phases ({@link buildSinglePartyTx},
112335
113424
  * {@link signSinglePartyTx}, {@link broadcastRawTx}) so each piece can be exercised
112336
- * in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead
113425
+ * in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead:
112337
113426
  * the multi-party path can't share the signing phase, but the tx-construction
112338
113427
  * plumbing (UTXO fetch + OP_RETURN output + change output) is shared.
112339
113428
  *
@@ -112365,7 +113454,7 @@ var SinglePartyBeacon = class {
112365
113454
  *
112366
113455
  * Detects the beacon address script kind (P2PKH / P2WPKH / P2TR) and configures
112367
113456
  * the input accordingly. Validates that the signer's pubkey produces the beacon
112368
- * address under that script kind without this check, a misconfigured caller
113457
+ * address under that script kind: without this check, a misconfigured caller
112369
113458
  * would burn a real UTXO on a tx that fails at broadcast. Fees are computed from
112370
113459
  * the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant, avoiding any probe-sign
112371
113460
  * round-trip.
@@ -128252,7 +129341,7 @@ var Updater = class _Updater {
128252
129341
  #verificationMethod;
128253
129342
  #beaconService;
128254
129343
  /**
128255
- * @internal Use {@link DidBtcr2.update} to create instances.
129344
+ * @internal Use {@link DidBtcr2.update} to create instances.
128256
129345
  */
128257
129346
  constructor(params) {
128258
129347
  this.#sourceDocument = params.sourceDocument;
@@ -128289,7 +129378,7 @@ var Updater = class _Updater {
128289
129378
  const targetDocument = JSONPatch.apply(sourceDocument, patches);
128290
129379
  if (targetDocument.id !== sourceDocument.id) {
128291
129380
  throw new UpdateError(
128292
- `Patches must not change the DID document id (source "${sourceDocument.id}" \u2192 target "${targetDocument.id}").`,
129381
+ `Patches must not change the DID document id (source "${sourceDocument.id}" to target "${targetDocument.id}").`,
128293
129382
  INVALID_DID_UPDATE,
128294
129383
  { sourceId: sourceDocument.id, targetId: targetDocument.id }
128295
129384
  );
@@ -128373,8 +129462,8 @@ var Updater = class _Updater {
128373
129462
  }
128374
129463
  /**
128375
129464
  * Advance the state machine. Returns either:
128376
- * - `{ status: 'action-required', needs }` caller must provide data via {@link provide}
128377
- * - `{ status: 'complete', result }` update is signed and broadcast
129465
+ * - `{ status: 'action-required', needs }` caller must provide data via {@link provide}
129466
+ * - `{ status: 'complete', result }` update is signed and broadcast
128378
129467
  */
128379
129468
  advance() {
128380
129469
  while (true) {
@@ -128387,7 +129476,7 @@ var Updater = class _Updater {
128387
129476
  continue;
128388
129477
  }
128389
129478
  // Phase: Sign
128390
- // Emit NeedSigningKey the caller supplies the secret key (or a KMS signature).
129479
+ // Emit NeedSigningKey: the caller supplies the secret key (or a KMS signature).
128391
129480
  case "Sign": {
128392
129481
  return {
128393
129482
  status: "action-required",
@@ -128570,8 +129659,8 @@ var DidBtcr2 = class {
128570
129659
  *
128571
129660
  * Factory method that validates the update parameters and returns a sans-I/O
128572
129661
  * {@link Updater} state machine. The caller drives the updater through its
128573
- * phases (Construct Sign Broadcast Complete) by calling `advance()` and
128574
- * `provide()`. The method package performs **zero I/O** signing key retrieval
129662
+ * phases (Construct -> Sign -> Broadcast -> Complete) by calling `advance()` and
129663
+ * `provide()`. The method package performs **zero I/O**: signing key retrieval
128575
129664
  * (or KMS delegation) and the on-chain broadcast are the caller's responsibility.
128576
129665
  *
128577
129666
  * For a fully-wired version with Bitcoin broadcast and key handling, see
@@ -128689,7 +129778,7 @@ var Resolver = class _Resolver {
128689
129778
  #unsortedUpdates = [];
128690
129779
  #resolvedResponse = null;
128691
129780
  /**
128692
- * @internal Use {@link DidBtcr2.resolve} to create instances.
129781
+ * @internal Use {@link DidBtcr2.resolve} to create instances.
128693
129782
  */
128694
129783
  constructor(didComponents, sidecarData, currentDocument, options2) {
128695
129784
  this.#didComponents = didComponents;
@@ -128925,8 +130014,8 @@ var Resolver = class _Resolver {
128925
130014
  }
128926
130015
  /**
128927
130016
  * Advance the state machine. Returns either:
128928
- * - `{ status: 'action-required', needs }` caller must provide data via {@link provide}
128929
- * - `{ status: 'resolved', result }` resolution complete
130017
+ * - `{ status: 'action-required', needs }` - caller must provide data via {@link provide}
130018
+ * - `{ status: 'resolved', result }` - resolution complete
128930
130019
  *
128931
130020
  * Analogous to Rust's `Resolver::resolve()`.
128932
130021
  */
@@ -129166,9 +130255,11 @@ export {
129166
130255
  DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
129167
130256
  DEFAULT_BROADCAST_LOOKBACK_MS,
129168
130257
  DEFAULT_CLOCK_SKEW_SEC,
130258
+ DEFAULT_FUNDING_MODEL,
129169
130259
  DEFAULT_MAX_UPDATE_SIZE_BYTES,
129170
130260
  DEFAULT_NONCE_LEN_BYTES,
129171
130261
  DEFAULT_NOSTR_RELAYS,
130262
+ DEFAULT_RECOVERY_SEQUENCE,
129172
130263
  DID_REGEX,
129173
130264
  DISTRIBUTE_AGGREGATED_DATA,
129174
130265
  DidBtcr2,
@@ -129177,6 +130268,8 @@ export {
129177
130268
  DidDocumentBuilder,
129178
130269
  DidVerificationMethod,
129179
130270
  Document,
130271
+ FALLBACK_AUTHORIZATION_REQUEST,
130272
+ FALLBACK_SIGNATURE,
129180
130273
  GenesisDocument,
129181
130274
  HTTP_ENVELOPE_VERSION,
129182
130275
  HTTP_ROUTE,
@@ -129190,7 +130283,9 @@ export {
129190
130283
  InMemoryTransport,
129191
130284
  InboxBuffer,
129192
130285
  KNOWN_BEACON_TYPES,
130286
+ KNOWN_FUNDING_MODELS,
129193
130287
  KeyPairAggregationSigner,
130288
+ MAX_RECOVERY_SEQUENCE,
129194
130289
  NONCE_CONTRIBUTION,
129195
130290
  NonceCache,
129196
130291
  NostrTransport,
@@ -129207,6 +130302,7 @@ export {
129207
130302
  SMTBeacon,
129208
130303
  SMTBeaconError,
129209
130304
  SSE_EVENT,
130305
+ SUBMIT_NONINCLUDED,
129210
130306
  SUBMIT_UPDATE,
129211
130307
  ServiceCohortPhase,
129212
130308
  SigningSessionError,
@@ -129215,6 +130311,7 @@ export {
129215
130311
  SingletonBeacon,
129216
130312
  SingletonBeaconError,
129217
130313
  StaticFeeEstimator,
130314
+ TAPROOT_LEAF_VERSION,
129218
130315
  TransportAdapterError,
129219
130316
  TransportError,
129220
130317
  TransportFactory,
@@ -129222,6 +130319,11 @@ export {
129222
130319
  Updater,
129223
130320
  VALIDATION_ACK,
129224
130321
  buildAggregationBeaconTx,
130322
+ buildFallbackLeaf,
130323
+ buildFallbackSpend,
130324
+ buildRecoveryLeaves,
130325
+ buildRecoveryScript,
130326
+ buildRecoverySpend,
129225
130327
  buildRequestAuth,
129226
130328
  createAggregatedNonceMessage,
129227
130329
  createAuthorizationRequestMessage,
@@ -129230,13 +130332,17 @@ export {
129230
130332
  createCohortOptInMessage,
129231
130333
  createCohortReadyMessage,
129232
130334
  createDistributeAggregatedDataMessage,
130335
+ createFallbackAuthorizationRequestMessage,
130336
+ createFallbackSignatureMessage,
129233
130337
  createNonceContributionMessage,
129234
130338
  createSignatureAuthorizationMessage,
130339
+ createSubmitNonIncludedMessage,
129235
130340
  createSubmitUpdateMessage,
129236
130341
  createValidationAckMessage,
129237
130342
  defaultReconnectBackoff,
129238
130343
  deriveSingletonAddress,
129239
130344
  detectSingletonScriptKind,
130345
+ fallbackSighash,
129240
130346
  formatSseComment,
129241
130347
  formatSseEvent,
129242
130348
  getBeaconStrategy,
@@ -129248,10 +130354,13 @@ export {
129248
130354
  isCohortOptInMessage,
129249
130355
  isCohortReadyMessage,
129250
130356
  isDistributeAggregatedDataMessage,
130357
+ isFallbackAuthorizationRequestMessage,
130358
+ isFallbackSignatureMessage,
129251
130359
  isKeygenMessageType,
129252
130360
  isNonceContributionMessage,
129253
130361
  isSignMessageType,
129254
130362
  isSignatureAuthorizationMessage,
130363
+ isSubmitNonIncludedMessage,
129255
130364
  isSubmitUpdateMessage,
129256
130365
  isUpdateMessageType,
129257
130366
  isValidationAckMessage,
@@ -129260,8 +130369,10 @@ export {
129260
130369
  parseRequestAuth,
129261
130370
  parseSseStream,
129262
130371
  registerBeaconStrategy,
130372
+ resolveFallbackThreshold,
129263
130373
  reviveFromWire,
129264
130374
  signEnvelope,
130375
+ tapLeafHash2 as tapLeafHash,
129265
130376
  validateCohortConditions,
129266
130377
  verifyEnvelope,
129267
130378
  verifyRequestAuth