@did-btcr2/method 0.34.0 → 0.36.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 (64) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/browser.js +335 -167
  3. package/dist/browser.mjs +335 -167
  4. package/dist/cjs/index.js +275 -114
  5. package/dist/esm/core/aggregation/cohort.js +8 -2
  6. package/dist/esm/core/aggregation/cohort.js.map +1 -1
  7. package/dist/esm/core/aggregation/conditions.js +75 -0
  8. package/dist/esm/core/aggregation/conditions.js.map +1 -0
  9. package/dist/esm/core/aggregation/messages/base.js.map +1 -1
  10. package/dist/esm/core/aggregation/messages/bodies.js +16 -2
  11. package/dist/esm/core/aggregation/messages/bodies.js.map +1 -1
  12. package/dist/esm/core/aggregation/messages/factories.js.map +1 -1
  13. package/dist/esm/core/aggregation/participant.js +34 -15
  14. package/dist/esm/core/aggregation/participant.js.map +1 -1
  15. package/dist/esm/core/aggregation/runner/participant-runner.js +9 -1
  16. package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -1
  17. package/dist/esm/core/aggregation/runner/service-runner.js +12 -1
  18. package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -1
  19. package/dist/esm/core/aggregation/service.js +27 -7
  20. package/dist/esm/core/aggregation/service.js.map +1 -1
  21. package/dist/esm/core/aggregation/signer.js +33 -0
  22. package/dist/esm/core/aggregation/signer.js.map +1 -0
  23. package/dist/esm/core/aggregation/signing-session.js +34 -11
  24. package/dist/esm/core/aggregation/signing-session.js.map +1 -1
  25. package/dist/esm/core/aggregation/transport/in-memory.js +5 -2
  26. package/dist/esm/core/aggregation/transport/in-memory.js.map +1 -1
  27. package/dist/esm/index.js +2 -0
  28. package/dist/esm/index.js.map +1 -1
  29. package/dist/types/core/aggregation/cohort.d.ts.map +1 -1
  30. package/dist/types/core/aggregation/conditions.d.ts +58 -0
  31. package/dist/types/core/aggregation/conditions.d.ts.map +1 -0
  32. package/dist/types/core/aggregation/messages/base.d.ts +2 -3
  33. package/dist/types/core/aggregation/messages/base.d.ts.map +1 -1
  34. package/dist/types/core/aggregation/messages/bodies.d.ts +2 -3
  35. package/dist/types/core/aggregation/messages/bodies.d.ts.map +1 -1
  36. package/dist/types/core/aggregation/messages/factories.d.ts +2 -3
  37. package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -1
  38. package/dist/types/core/aggregation/participant.d.ts +24 -8
  39. package/dist/types/core/aggregation/participant.d.ts.map +1 -1
  40. package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
  41. package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -1
  42. package/dist/types/core/aggregation/service.d.ts +18 -8
  43. package/dist/types/core/aggregation/service.d.ts.map +1 -1
  44. package/dist/types/core/aggregation/signer.d.ts +50 -0
  45. package/dist/types/core/aggregation/signer.d.ts.map +1 -0
  46. package/dist/types/core/aggregation/signing-session.d.ts +13 -5
  47. package/dist/types/core/aggregation/signing-session.d.ts.map +1 -1
  48. package/dist/types/core/aggregation/transport/in-memory.d.ts.map +1 -1
  49. package/dist/types/index.d.ts +2 -0
  50. package/dist/types/index.d.ts.map +1 -1
  51. package/package.json +4 -4
  52. package/src/core/aggregation/cohort.ts +8 -2
  53. package/src/core/aggregation/conditions.ts +116 -0
  54. package/src/core/aggregation/messages/base.ts +6 -3
  55. package/src/core/aggregation/messages/bodies.ts +18 -6
  56. package/src/core/aggregation/messages/factories.ts +2 -3
  57. package/src/core/aggregation/participant.ts +54 -23
  58. package/src/core/aggregation/runner/participant-runner.ts +9 -1
  59. package/src/core/aggregation/runner/service-runner.ts +13 -1
  60. package/src/core/aggregation/service.ts +51 -13
  61. package/src/core/aggregation/signer.ts +67 -0
  62. package/src/core/aggregation/signing-session.ts +34 -11
  63. package/src/core/aggregation/transport/in-memory.ts +7 -3
  64. package/src/index.ts +2 -0
package/dist/browser.js CHANGED
@@ -93203,6 +93203,8 @@ a=end-of-candidates
93203
93203
  InMemoryRateLimitStore: () => InMemoryRateLimitStore,
93204
93204
  InMemoryTransport: () => InMemoryTransport,
93205
93205
  InboxBuffer: () => InboxBuffer,
93206
+ KNOWN_BEACON_TYPES: () => KNOWN_BEACON_TYPES,
93207
+ KeyPairAggregationSigner: () => KeyPairAggregationSigner,
93206
93208
  NONCE_CONTRIBUTION: () => NONCE_CONTRIBUTION,
93207
93209
  NonceCache: () => NonceCache,
93208
93210
  NostrTransport: () => NostrTransport,
@@ -93274,6 +93276,7 @@ a=end-of-candidates
93274
93276
  registerBeaconStrategy: () => registerBeaconStrategy,
93275
93277
  reviveFromWire: () => reviveFromWire,
93276
93278
  signEnvelope: () => signEnvelope,
93279
+ validateCohortConditions: () => validateCohortConditions,
93277
93280
  verifyEnvelope: () => verifyEnvelope,
93278
93281
  verifyRequestAuth: () => verifyRequestAuth
93279
93282
  });
@@ -100117,6 +100120,13 @@ a=end-of-candidates
100117
100120
  // ../keypair/dist/esm/types.js
100118
100121
  init_shim();
100119
100122
 
100123
+ // ../keypair/dist/esm/utils.js
100124
+ init_shim();
100125
+ function wipe(bytes3) {
100126
+ if (bytes3)
100127
+ bytes3.fill(0);
100128
+ }
100129
+
100120
100130
  // ../cryptosuite/dist/esm/multikey/index.js
100121
100131
  init_secp256k1();
100122
100132
  init_utils();
@@ -100703,8 +100713,48 @@ a=end-of-candidates
100703
100713
 
100704
100714
  // src/core/aggregation/cohort.ts
100705
100715
  init_shim();
100706
- init_secp256k1();
100707
- init_utils();
100716
+
100717
+ // ../bitcoin/dist/esm/index.js
100718
+ init_shim();
100719
+
100720
+ // ../bitcoin/dist/esm/connection.js
100721
+ init_shim();
100722
+
100723
+ // ../bitcoin/dist/esm/client/rest/index.js
100724
+ init_shim();
100725
+
100726
+ // ../bitcoin/dist/esm/client/rest/address.js
100727
+ init_shim();
100728
+
100729
+ // ../bitcoin/dist/esm/client/rest/block.js
100730
+ init_shim();
100731
+
100732
+ // ../bitcoin/dist/esm/errors.js
100733
+ init_shim();
100734
+
100735
+ // ../bitcoin/dist/esm/client/rest/transaction.js
100736
+ init_shim();
100737
+
100738
+ // ../bitcoin/dist/esm/client/rest/protocol.js
100739
+ init_shim();
100740
+
100741
+ // ../bitcoin/dist/esm/client/http.js
100742
+ init_shim();
100743
+
100744
+ // ../bitcoin/dist/esm/client/rpc/index.js
100745
+ init_shim();
100746
+
100747
+ // ../bitcoin/dist/esm/client/rpc/json-rpc.js
100748
+ init_shim();
100749
+
100750
+ // ../bitcoin/dist/esm/client/utils.js
100751
+ init_shim();
100752
+
100753
+ // ../bitcoin/dist/esm/client/rpc/protocol.js
100754
+ init_shim();
100755
+
100756
+ // ../bitcoin/dist/esm/network.js
100757
+ init_shim();
100708
100758
 
100709
100759
  // ../../node_modules/.pnpm/@scure+btc-signer@1.8.1/node_modules/@scure/btc-signer/esm/index.js
100710
100760
  init_shim();
@@ -102903,6 +102953,48 @@ a=end-of-candidates
102903
102953
  }
102904
102954
  };
102905
102955
 
102956
+ // ../bitcoin/dist/esm/network.js
102957
+ var REGTEST_NETWORK = {
102958
+ bech32: "bcrt",
102959
+ pubKeyHash: 111,
102960
+ scriptHash: 196,
102961
+ wif: 239
102962
+ };
102963
+ function getNetwork(network) {
102964
+ switch (network) {
102965
+ case "bitcoin":
102966
+ return NETWORK;
102967
+ case "testnet3":
102968
+ case "testnet4":
102969
+ case "signet":
102970
+ case "mutinynet":
102971
+ return TEST_NETWORK;
102972
+ case "regtest":
102973
+ return REGTEST_NETWORK;
102974
+ default:
102975
+ throw new Error(`Unknown network "${network}"`);
102976
+ }
102977
+ }
102978
+
102979
+ // ../bitcoin/dist/esm/constants.js
102980
+ init_shim();
102981
+ var TXIN_WITNESS_COINBASE = "0000000000000000000000000000000000000000000000000000000000000000";
102982
+ var GENESIS_TX_ID = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b";
102983
+
102984
+ // ../bitcoin/dist/esm/types.js
102985
+ init_shim();
102986
+ var VerbosityLevel;
102987
+ (function(VerbosityLevel2) {
102988
+ VerbosityLevel2[VerbosityLevel2["hex"] = 0] = "hex";
102989
+ VerbosityLevel2[VerbosityLevel2["json"] = 1] = "json";
102990
+ VerbosityLevel2[VerbosityLevel2["jsonext"] = 2] = "jsonext";
102991
+ VerbosityLevel2[VerbosityLevel2["jsonextprev"] = 3] = "jsonextprev";
102992
+ })(VerbosityLevel || (VerbosityLevel = {}));
102993
+
102994
+ // src/core/aggregation/cohort.ts
102995
+ init_secp256k1();
102996
+ init_utils();
102997
+
102906
102998
  // ../../node_modules/.pnpm/@scure+btc-signer@1.8.1/node_modules/@scure/btc-signer/esm/musig2.js
102907
102999
  init_shim();
102908
103000
  init_modular();
@@ -103291,7 +103383,7 @@ a=end-of-candidates
103291
103383
  validationRejections = /* @__PURE__ */ new Set();
103292
103384
  constructor({ id, minParticipants, serviceDid, network, beaconType }) {
103293
103385
  this.id = id || crypto.randomUUID();
103294
- this.minParticipants = minParticipants || 2;
103386
+ this.minParticipants = minParticipants ?? 2;
103295
103387
  this.serviceDid = serviceDid || "";
103296
103388
  this.network = network;
103297
103389
  this.beaconType = beaconType || "CASBeacon";
@@ -103317,7 +103409,7 @@ a=end-of-candidates
103317
103409
  }
103318
103410
  const keyAggContext = keyAggregate(this.#cohortKeys);
103319
103411
  const aggPubkey = keyAggExport(keyAggContext);
103320
- const payment = p2tr(aggPubkey);
103412
+ const payment = p2tr(aggPubkey, void 0, getNetwork(this.network));
103321
103413
  this.tapTweak = schnorr2.utils.taggedHash("TapTweak", aggPubkey);
103322
103414
  if (!payment.address) {
103323
103415
  throw new AggregationCohortError(
@@ -103457,6 +103549,57 @@ a=end-of-candidates
103457
103549
  }
103458
103550
  };
103459
103551
 
103552
+ // src/core/aggregation/conditions.ts
103553
+ init_shim();
103554
+ var KNOWN_BEACON_TYPES = ["CASBeacon", "SMTBeacon"];
103555
+ function checkPair(problems, label, min, max) {
103556
+ if (min !== void 0 && (!Number.isInteger(min) || min < 0)) {
103557
+ problems.push(`min${label} must be an integer >= 0`);
103558
+ }
103559
+ if (max !== void 0 && (!Number.isInteger(max) || max < 0)) {
103560
+ problems.push(`max${label} must be an integer >= 0`);
103561
+ }
103562
+ if (min !== void 0 && max !== void 0 && Number.isInteger(min) && Number.isInteger(max) && max < min) {
103563
+ problems.push(`max${label} must be >= min${label}`);
103564
+ }
103565
+ }
103566
+ function checkCost(problems, label, cost) {
103567
+ if (cost === void 0) return;
103568
+ if (typeof cost.amount !== "number" || !Number.isFinite(cost.amount) || cost.amount < 0) {
103569
+ problems.push(`${label}.amount must be a finite number >= 0`);
103570
+ }
103571
+ if (typeof cost.unit !== "string" || cost.unit.length === 0) {
103572
+ problems.push(`${label}.unit must be a non-empty string`);
103573
+ }
103574
+ if (cost.basis !== void 0 && cost.basis !== "per-did" && cost.basis !== "per-participant") {
103575
+ problems.push(`${label}.basis must be 'per-did' or 'per-participant'`);
103576
+ }
103577
+ }
103578
+ function validateCohortConditions(c2) {
103579
+ const problems = [];
103580
+ if (!KNOWN_BEACON_TYPES.includes(c2.beaconType)) {
103581
+ problems.push(`beaconType must be one of ${KNOWN_BEACON_TYPES.join(", ")}`);
103582
+ }
103583
+ if (!Number.isInteger(c2.minParticipants) || c2.minParticipants < 1) {
103584
+ problems.push("minParticipants must be an integer >= 1");
103585
+ }
103586
+ if (c2.maxParticipants !== void 0) {
103587
+ if (!Number.isInteger(c2.maxParticipants) || c2.maxParticipants < 1) {
103588
+ problems.push("maxParticipants must be an integer >= 1");
103589
+ } else if (Number.isInteger(c2.minParticipants) && c2.maxParticipants < c2.minParticipants) {
103590
+ problems.push("maxParticipants must be >= minParticipants");
103591
+ }
103592
+ }
103593
+ checkPair(problems, "DidsPerParticipant", c2.minDidsPerParticipant, c2.maxDidsPerParticipant);
103594
+ checkPair(problems, "SecondsBetweenAnnouncements", c2.minSecondsBetweenAnnouncements, c2.maxSecondsBetweenAnnouncements);
103595
+ if (c2.pendingUpdateTrigger !== void 0 && (!Number.isInteger(c2.pendingUpdateTrigger) || c2.pendingUpdateTrigger < 1)) {
103596
+ problems.push("pendingUpdateTrigger must be an integer >= 1");
103597
+ }
103598
+ checkCost(problems, "costOfEnrollment", c2.costOfEnrollment);
103599
+ checkCost(problems, "costPerAnnouncement", c2.costPerAnnouncement);
103600
+ return problems;
103601
+ }
103602
+
103460
103603
  // src/core/aggregation/messages/base.ts
103461
103604
  init_shim();
103462
103605
  var AGGREGATION_WIRE_VERSION = 1;
@@ -103614,8 +103757,14 @@ a=end-of-candidates
103614
103757
  signature;
103615
103758
  /** Current signing session phase. */
103616
103759
  phase;
103617
- /** Participant's secret nonce (held only by the participant during signing). */
103618
- secretNonce;
103760
+ /**
103761
+ * Participant's MuSig2 secret nonce, held only on the participant side
103762
+ * between {@link generateNonceContribution} and {@link generatePartialSignature}.
103763
+ * Private and cleared on every terminal path (success, failure, or teardown
103764
+ * via {@link clearSecrets}) so a spent or abandoned nonce cannot be reused or
103765
+ * serialized.
103766
+ */
103767
+ #secretNonce;
103619
103768
  constructor({ id, cohort, pendingTx, prevOutScripts, prevOutValues }) {
103620
103769
  this.id = id || crypto.randomUUID();
103621
103770
  this.cohort = cohort;
@@ -103767,22 +103916,24 @@ a=end-of-candidates
103767
103916
  generateNonceContribution(participantPublicKey, participantSecretKey) {
103768
103917
  const aggPublicKey = keyAggExport(keyAggregate(this.cohort.cohortKeys));
103769
103918
  const nonces = nonceGen(participantPublicKey, participantSecretKey, aggPublicKey);
103770
- this.secretNonce = nonces.secret;
103919
+ this.#secretNonce = nonces.secret;
103771
103920
  return nonces.public;
103772
103921
  }
103773
103922
  /**
103774
103923
  * Generates a partial signature using the participant's secret key + secret nonce.
103775
103924
  * Requires the aggregated nonce to have been set first (via the service).
103776
103925
  *
103777
- * Zeros the stored `secretNonce` after use. JS cannot truly erase memory (GC
103778
- * and immutable strings), but overwriting the bytes shortens the exposure
103779
- * window and prevents accidental reuse or serialization of a spent nonce.
103926
+ * Clears the stored secret nonce after use on every path (success or throw)
103927
+ * via {@link clearSecrets}. JS cannot truly erase memory (GC may relocate
103928
+ * buffers), but overwriting the bytes shortens the exposure window and
103929
+ * prevents accidental reuse or serialization of a spent nonce - reuse of a
103930
+ * MuSig2 nonce leaks the secret key.
103780
103931
  */
103781
103932
  generatePartialSignature(participantSecretKey) {
103782
103933
  if (!this.aggregatedNonce) {
103783
103934
  throw new SigningSessionError("Aggregated nonce not available.", "MISSING_AGGREGATED_NONCE");
103784
103935
  }
103785
- if (!this.secretNonce) {
103936
+ if (!this.#secretNonce) {
103786
103937
  throw new SigningSessionError("Secret nonce not available \u2014 generateNonceContribution() must be called first.", "MISSING_SECRET_NONCE");
103787
103938
  }
103788
103939
  const session = new Session(
@@ -103792,10 +103943,23 @@ a=end-of-candidates
103792
103943
  [this.cohort.tapTweak],
103793
103944
  [true]
103794
103945
  );
103795
- const partialSig = session.sign(this.secretNonce, participantSecretKey);
103796
- this.secretNonce.fill(0);
103797
- this.secretNonce = void 0;
103798
- return partialSig;
103946
+ try {
103947
+ return session.sign(this.#secretNonce, participantSecretKey);
103948
+ } finally {
103949
+ this.clearSecrets();
103950
+ }
103951
+ }
103952
+ /**
103953
+ * Zeroize any retained secret nonce. Safe to call repeatedly and on any path
103954
+ * (completion, failure, or teardown of an abandoned session). Callers that
103955
+ * drop a session before it reaches a partial signature should invoke this so
103956
+ * the secret nonce does not linger on the live object.
103957
+ */
103958
+ clearSecrets() {
103959
+ if (this.#secretNonce) {
103960
+ wipe(this.#secretNonce);
103961
+ this.#secretNonce = void 0;
103962
+ }
103799
103963
  }
103800
103964
  isComplete() {
103801
103965
  return this.phase === "Complete" /* Complete */;
@@ -103809,13 +103973,13 @@ a=end-of-candidates
103809
103973
  var DEFAULT_MAX_UPDATE_SIZE_BYTES = 256 * 1024;
103810
103974
  var AggregationService = class {
103811
103975
  did;
103812
- keys;
103976
+ publicKey;
103813
103977
  maxUpdateSizeBytes;
103814
103978
  /** Per-cohort state, keyed by cohortId. */
103815
103979
  #cohortStates = /* @__PURE__ */ new Map();
103816
- constructor({ did, keys, maxUpdateSizeBytes }) {
103980
+ constructor({ did, publicKey: publicKey2, maxUpdateSizeBytes }) {
103817
103981
  this.did = did;
103818
- this.keys = keys;
103982
+ this.publicKey = publicKey2;
103819
103983
  this.maxUpdateSizeBytes = maxUpdateSizeBytes ?? DEFAULT_MAX_UPDATE_SIZE_BYTES;
103820
103984
  }
103821
103985
  receive(message2) {
@@ -103870,6 +104034,14 @@ a=end-of-candidates
103870
104034
  * Cohort starts in `Created` phase — call `advertise()` to broadcast.
103871
104035
  */
103872
104036
  createCohort(config) {
104037
+ const problems = validateCohortConditions(config);
104038
+ if (problems.length > 0) {
104039
+ throw new AggregationServiceError(
104040
+ `Invalid cohort conditions: ${problems.join("; ")}`,
104041
+ "INVALID_COHORT_CONDITIONS",
104042
+ { problems }
104043
+ );
104044
+ }
103873
104045
  const cohort = new AggregationCohort({
103874
104046
  serviceDid: this.did,
103875
104047
  minParticipants: config.minParticipants,
@@ -103902,13 +104074,13 @@ a=end-of-candidates
103902
104074
  { cohortId, phase: state.phase }
103903
104075
  );
103904
104076
  }
104077
+ const { network, ...conditions } = state.config;
103905
104078
  const message2 = createCohortAdvertMessage({
103906
104079
  from: this.did,
103907
104080
  cohortId,
103908
- cohortSize: state.config.minParticipants,
103909
- beaconType: state.config.beaconType,
103910
- network: state.config.network,
103911
- communicationPk: this.keys.publicKey.compressed
104081
+ network,
104082
+ communicationPk: this.publicKey.compressed,
104083
+ ...conditions
103912
104084
  });
103913
104085
  state.phase = "Advertised" /* Advertised */;
103914
104086
  return [message2];
@@ -103967,6 +104139,14 @@ a=end-of-candidates
103967
104139
  { cohortId, participantDid }
103968
104140
  );
103969
104141
  }
104142
+ const maxParticipants = state.config.maxParticipants;
104143
+ if (maxParticipants !== void 0 && state.acceptedParticipants.size >= maxParticipants) {
104144
+ throw new AggregationServiceError(
104145
+ `Cohort ${cohortId} is full: ${maxParticipants} participants already accepted.`,
104146
+ "COHORT_FULL",
104147
+ { cohortId, maxParticipants }
104148
+ );
104149
+ }
103970
104150
  state.acceptedParticipants.add(participantDid);
103971
104151
  state.cohort.participants.push(participantDid);
103972
104152
  state.cohort.participantKeys.set(participantDid, optIn.participantPk);
@@ -104000,6 +104180,14 @@ a=end-of-candidates
104000
104180
  { cohortId }
104001
104181
  );
104002
104182
  }
104183
+ const maxParticipants = state.config.maxParticipants;
104184
+ if (maxParticipants !== void 0 && state.acceptedParticipants.size > maxParticipants) {
104185
+ throw new AggregationServiceError(
104186
+ `Cohort ${cohortId} has ${state.acceptedParticipants.size} accepted participants, exceeds max ${maxParticipants}.`,
104187
+ "TOO_MANY_PARTICIPANTS",
104188
+ { cohortId, maxParticipants }
104189
+ );
104190
+ }
104003
104191
  const beaconAddress = state.cohort.computeBeaconAddress();
104004
104192
  state.phase = "CohortSet" /* CohortSet */;
104005
104193
  const messages2 = [];
@@ -104317,14 +104505,72 @@ a=end-of-candidates
104317
104505
  // src/core/aggregation/participant.ts
104318
104506
  init_shim();
104319
104507
  init_utils();
104508
+
104509
+ // src/core/aggregation/messages/bodies.ts
104510
+ init_shim();
104511
+ var hasStr = (b, k) => !!b && typeof b[k] === "string";
104512
+ var hasIntMin = (b, k, min) => {
104513
+ const v = b ? b[k] : void 0;
104514
+ return typeof v === "number" && Number.isInteger(v) && v >= min;
104515
+ };
104516
+ var optIntMin = (b, k, min) => {
104517
+ const v = b ? b[k] : void 0;
104518
+ return v === void 0 || typeof v === "number" && Number.isInteger(v) && v >= min;
104519
+ };
104520
+ var hasBool = (b, k) => !!b && typeof b[k] === "boolean";
104521
+ var hasBytes = (b, k) => !!b && b[k] instanceof Uint8Array;
104522
+ var hasBytesArray = (b, k) => {
104523
+ const v = b ? b[k] : void 0;
104524
+ return Array.isArray(v) && v.every((x) => x instanceof Uint8Array);
104525
+ };
104526
+ function isCohortAdvertMessage(m2) {
104527
+ 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");
104528
+ }
104529
+ function isCohortOptInMessage(m2) {
104530
+ return m2.type === COHORT_OPT_IN && hasStr(m2.body, "cohortId") && hasBytes(m2.body, "participantPk") && hasBytes(m2.body, "communicationPk");
104531
+ }
104532
+ function isCohortOptInAcceptMessage(m2) {
104533
+ return m2.type === COHORT_OPT_IN_ACCEPT && hasStr(m2.body, "cohortId");
104534
+ }
104535
+ function isCohortReadyMessage(m2) {
104536
+ return m2.type === COHORT_READY && hasStr(m2.body, "cohortId") && hasStr(m2.body, "beaconAddress") && hasBytesArray(m2.body, "cohortKeys");
104537
+ }
104538
+ function isSubmitUpdateMessage(m2) {
104539
+ return m2.type === SUBMIT_UPDATE && hasStr(m2.body, "cohortId") && !!m2.body && typeof m2.body.signedUpdate === "object";
104540
+ }
104541
+ function isDistributeAggregatedDataMessage(m2) {
104542
+ return m2.type === DISTRIBUTE_AGGREGATED_DATA && hasStr(m2.body, "cohortId") && hasStr(m2.body, "beaconType") && hasStr(m2.body, "signalBytesHex");
104543
+ }
104544
+ function isValidationAckMessage(m2) {
104545
+ return m2.type === VALIDATION_ACK && hasStr(m2.body, "cohortId") && hasBool(m2.body, "approved");
104546
+ }
104547
+ function isAuthorizationRequestMessage(m2) {
104548
+ return m2.type === AUTHORIZATION_REQUEST && hasStr(m2.body, "cohortId") && hasStr(m2.body, "sessionId") && hasStr(m2.body, "pendingTx") && hasStr(m2.body, "prevOutScriptHex") && hasStr(m2.body, "prevOutValue");
104549
+ }
104550
+ function isNonceContributionMessage(m2) {
104551
+ return m2.type === NONCE_CONTRIBUTION && hasStr(m2.body, "cohortId") && hasStr(m2.body, "sessionId") && hasBytes(m2.body, "nonceContribution");
104552
+ }
104553
+ function isAggregatedNonceMessage(m2) {
104554
+ return m2.type === AGGREGATED_NONCE && hasStr(m2.body, "cohortId") && hasStr(m2.body, "sessionId") && hasBytes(m2.body, "aggregatedNonce");
104555
+ }
104556
+ function isSignatureAuthorizationMessage(m2) {
104557
+ return m2.type === SIGNATURE_AUTHORIZATION && hasStr(m2.body, "cohortId") && hasStr(m2.body, "sessionId") && hasBytes(m2.body, "partialSignature");
104558
+ }
104559
+
104560
+ // src/core/aggregation/participant.ts
104320
104561
  var AggregationParticipant = class {
104321
104562
  did;
104322
- keys;
104563
+ /** MuSig2 signing capability. The raw secret never lives as a field here. */
104564
+ #signer;
104323
104565
  /** Per-cohort state, keyed by cohortId. */
104324
104566
  #cohortStates = /* @__PURE__ */ new Map();
104325
- constructor({ did, keys }) {
104567
+ constructor({ did, signer }) {
104326
104568
  this.did = did;
104327
- this.keys = keys;
104569
+ this.#signer = signer;
104570
+ }
104571
+ /** The participant's compressed (33-byte) MuSig2 public key. Not secret. */
104572
+ get publicKey() {
104573
+ return this.#signer.publicKey;
104328
104574
  }
104329
104575
  /**
104330
104576
  * Process an incoming message. Updates internal state but never produces
@@ -104369,16 +104615,15 @@ a=end-of-candidates
104369
104615
  return map3;
104370
104616
  }
104371
104617
  #handleCohortAdvert(message2) {
104372
- const cohortId = message2.body?.cohortId;
104373
- if (!cohortId) return;
104618
+ if (!isCohortAdvertMessage(message2)) return;
104619
+ const { cohortId, network, communicationPk, ...conditions } = message2.body;
104374
104620
  if (this.#cohortStates.has(cohortId)) return;
104375
104621
  const advert = {
104376
104622
  cohortId,
104377
104623
  serviceDid: message2.from,
104378
- cohortSize: message2.body?.cohortSize ?? 0,
104379
- network: message2.body?.network ?? "",
104380
- beaconType: message2.body?.beaconType ?? "CASBeacon",
104381
- serviceCommunicationPk: message2.body?.communicationPk ?? new Uint8Array()
104624
+ network,
104625
+ serviceCommunicationPk: communicationPk,
104626
+ ...conditions
104382
104627
  };
104383
104628
  this.#cohortStates.set(cohortId, {
104384
104629
  phase: "Discovered" /* Discovered */,
@@ -104403,7 +104648,7 @@ a=end-of-candidates
104403
104648
  const cohort = new AggregationCohort({
104404
104649
  id: cohortId,
104405
104650
  serviceDid: state.serviceDid,
104406
- minParticipants: state.advert.cohortSize,
104651
+ minParticipants: state.advert.minParticipants,
104407
104652
  network: state.advert.network,
104408
104653
  beaconType: state.advert.beaconType
104409
104654
  });
@@ -104413,8 +104658,8 @@ a=end-of-candidates
104413
104658
  from: this.did,
104414
104659
  to: state.serviceDid,
104415
104660
  cohortId,
104416
- participantPk: this.keys.publicKey.compressed,
104417
- communicationPk: this.keys.publicKey.compressed
104661
+ participantPk: this.publicKey,
104662
+ communicationPk: this.publicKey
104418
104663
  });
104419
104664
  return [optInMessage];
104420
104665
  }
@@ -104444,7 +104689,7 @@ a=end-of-candidates
104444
104689
  const beaconAddress = message2.body?.beaconAddress;
104445
104690
  const cohortKeys = message2.body?.cohortKeys;
104446
104691
  if (!beaconAddress || !cohortKeys) return;
104447
- const participantPkHex = bytesToHex(this.keys.publicKey.compressed);
104692
+ const participantPkHex = bytesToHex(this.publicKey);
104448
104693
  const cohortKeysHex = cohortKeys.map((k) => bytesToHex(new Uint8Array(k)));
104449
104694
  state.cohort.validateMembership(participantPkHex, cohortKeysHex, beaconAddress);
104450
104695
  state.phase = "CohortReady" /* CohortReady */;
@@ -104611,9 +104856,8 @@ a=end-of-candidates
104611
104856
  prevOutValues
104612
104857
  });
104613
104858
  state.signingSession = session;
104614
- const nonceContribution = session.generateNonceContribution(
104615
- this.keys.publicKey.compressed,
104616
- this.keys.secretKey.bytes
104859
+ const nonceContribution = this.#signer.withSecret(
104860
+ (secretKey) => session.generateNonceContribution(this.publicKey, secretKey)
104617
104861
  );
104618
104862
  state.phase = "NonceSent" /* NonceSent */;
104619
104863
  return [createNonceContributionMessage({
@@ -104656,7 +104900,10 @@ a=end-of-candidates
104656
104900
  { cohortId }
104657
104901
  );
104658
104902
  }
104659
- const partialSig = state.signingSession.generatePartialSignature(this.keys.secretKey.bytes);
104903
+ const signingSession = state.signingSession;
104904
+ const partialSig = this.#signer.withSecret(
104905
+ (secretKey) => signingSession.generatePartialSignature(secretKey)
104906
+ );
104660
104907
  state.phase = "Complete" /* Complete */;
104661
104908
  return [createSignatureAuthorizationMessage({
104662
104909
  from: this.did,
@@ -104669,6 +104916,37 @@ a=end-of-candidates
104669
104916
  getCohortPhase(cohortId) {
104670
104917
  return this.#cohortStates.get(cohortId)?.phase;
104671
104918
  }
104919
+ /**
104920
+ * Zeroize any retained MuSig2 secret nonces across all cohorts. The raw
104921
+ * signing key is never held here (it lives behind the {@link AggregationSigner}
104922
+ * and is wiped per-operation), but an abandoned signing session can still hold
104923
+ * a secret nonce; call this on teardown to clear it deterministically.
104924
+ */
104925
+ clearSecrets() {
104926
+ for (const state of this.#cohortStates.values()) {
104927
+ state.signingSession?.clearSecrets();
104928
+ }
104929
+ }
104930
+ };
104931
+
104932
+ // src/core/aggregation/signer.ts
104933
+ init_shim();
104934
+ var KeyPairAggregationSigner = class {
104935
+ publicKey;
104936
+ #keys;
104937
+ /** @param {SchnorrKeyPair} keys The keypair whose secret backs this signer. */
104938
+ constructor(keys) {
104939
+ this.#keys = keys;
104940
+ this.publicKey = keys.publicKey.compressed;
104941
+ }
104942
+ withSecret(fn) {
104943
+ const secretKey = this.#keys.secretKey.bytes;
104944
+ try {
104945
+ return fn(secretKey);
104946
+ } finally {
104947
+ wipe(secretKey);
104948
+ }
104949
+ }
104672
104950
  };
104673
104951
 
104674
104952
  // src/core/aggregation/logger.ts
@@ -104693,50 +104971,6 @@ a=end-of-candidates
104693
104971
  // src/core/aggregation/messages/index.ts
104694
104972
  init_shim();
104695
104973
 
104696
- // src/core/aggregation/messages/bodies.ts
104697
- init_shim();
104698
- var hasStr = (b, k) => !!b && typeof b[k] === "string";
104699
- var hasNum = (b, k) => !!b && typeof b[k] === "number";
104700
- var hasBool = (b, k) => !!b && typeof b[k] === "boolean";
104701
- var hasBytes = (b, k) => !!b && b[k] instanceof Uint8Array;
104702
- var hasBytesArray = (b, k) => {
104703
- const v = b ? b[k] : void 0;
104704
- return Array.isArray(v) && v.every((x) => x instanceof Uint8Array);
104705
- };
104706
- function isCohortAdvertMessage(m2) {
104707
- return m2.type === COHORT_ADVERT && hasStr(m2.body, "cohortId") && hasNum(m2.body, "cohortSize") && hasStr(m2.body, "beaconType") && hasStr(m2.body, "network") && hasBytes(m2.body, "communicationPk");
104708
- }
104709
- function isCohortOptInMessage(m2) {
104710
- return m2.type === COHORT_OPT_IN && hasStr(m2.body, "cohortId") && hasBytes(m2.body, "participantPk") && hasBytes(m2.body, "communicationPk");
104711
- }
104712
- function isCohortOptInAcceptMessage(m2) {
104713
- return m2.type === COHORT_OPT_IN_ACCEPT && hasStr(m2.body, "cohortId");
104714
- }
104715
- function isCohortReadyMessage(m2) {
104716
- return m2.type === COHORT_READY && hasStr(m2.body, "cohortId") && hasStr(m2.body, "beaconAddress") && hasBytesArray(m2.body, "cohortKeys");
104717
- }
104718
- function isSubmitUpdateMessage(m2) {
104719
- return m2.type === SUBMIT_UPDATE && hasStr(m2.body, "cohortId") && !!m2.body && typeof m2.body.signedUpdate === "object";
104720
- }
104721
- function isDistributeAggregatedDataMessage(m2) {
104722
- return m2.type === DISTRIBUTE_AGGREGATED_DATA && hasStr(m2.body, "cohortId") && hasStr(m2.body, "beaconType") && hasStr(m2.body, "signalBytesHex");
104723
- }
104724
- function isValidationAckMessage(m2) {
104725
- return m2.type === VALIDATION_ACK && hasStr(m2.body, "cohortId") && hasBool(m2.body, "approved");
104726
- }
104727
- function isAuthorizationRequestMessage(m2) {
104728
- return m2.type === AUTHORIZATION_REQUEST && hasStr(m2.body, "cohortId") && hasStr(m2.body, "sessionId") && hasStr(m2.body, "pendingTx") && hasStr(m2.body, "prevOutScriptHex") && hasStr(m2.body, "prevOutValue");
104729
- }
104730
- function isNonceContributionMessage(m2) {
104731
- return m2.type === NONCE_CONTRIBUTION && hasStr(m2.body, "cohortId") && hasStr(m2.body, "sessionId") && hasBytes(m2.body, "nonceContribution");
104732
- }
104733
- function isAggregatedNonceMessage(m2) {
104734
- return m2.type === AGGREGATED_NONCE && hasStr(m2.body, "cohortId") && hasStr(m2.body, "sessionId") && hasBytes(m2.body, "aggregatedNonce");
104735
- }
104736
- function isSignatureAuthorizationMessage(m2) {
104737
- return m2.type === SIGNATURE_AUTHORIZATION && hasStr(m2.body, "cohortId") && hasStr(m2.body, "sessionId") && hasBytes(m2.body, "partialSignature");
104738
- }
104739
-
104740
104974
  // src/core/aggregation/messages/guards.ts
104741
104975
  init_shim();
104742
104976
  var KEYGEN_VALUES = /* @__PURE__ */ new Set([
@@ -105425,7 +105659,7 @@ ${value2}`;
105425
105659
  body: JSON.stringify(envelope)
105426
105660
  });
105427
105661
  if (!res.ok) {
105428
- const body = await safeText(res);
105662
+ const body = await safeText2(res);
105429
105663
  throw new HttpTransportError(
105430
105664
  `sendMessage failed: HTTP ${res.status}`,
105431
105665
  "SEND_MESSAGE_HTTP",
@@ -105602,7 +105836,7 @@ ${value2}`;
105602
105836
  else signal.addEventListener("abort", onAbort, { once: true });
105603
105837
  });
105604
105838
  }
105605
- async function safeText(res) {
105839
+ async function safeText2(res) {
105606
105840
  try {
105607
105841
  return await res.text();
105608
105842
  } catch {
@@ -111181,10 +111415,10 @@ ${value2}`;
111181
111415
  start() {
111182
111416
  }
111183
111417
  registerActor(did, keys) {
111184
- this.#actors.set(did, { keys, handlers: /* @__PURE__ */ new Map() });
111418
+ this.#actors.set(did, { publicKey: keys.publicKey.compressed, handlers: /* @__PURE__ */ new Map() });
111185
111419
  }
111186
111420
  getActorPk(did) {
111187
- return this.#actors.get(did)?.keys.publicKey.compressed;
111421
+ return this.#actors.get(did)?.publicKey;
111188
111422
  }
111189
111423
  /** True if `did` is registered on this transport. Used by the bus for routing. */
111190
111424
  hasActor(did) {
@@ -111407,8 +111641,11 @@ ${value2}`;
111407
111641
  this.#phaseTimeoutMs = options2.phaseTimeoutMs;
111408
111642
  this.#advertRepeatIntervalMs = options2.advertRepeatIntervalMs ?? DEFAULT_ADVERT_REPEAT_INTERVAL_MS;
111409
111643
  this.session = new AggregationService({
111644
+ // The coordinator never signs, so the state machine receives only the
111645
+ // public half of the operator's keypair (see ADR 038). The full keypair
111646
+ // remains the operator's transport/communication identity.
111410
111647
  did: options2.did,
111411
- keys: options2.keys,
111648
+ publicKey: options2.keys.publicKey,
111412
111649
  maxUpdateSizeBytes: options2.maxUpdateSizeBytes
111413
111650
  });
111414
111651
  }
@@ -111579,6 +111816,11 @@ ${value2}`;
111579
111816
  }
111580
111817
  const decision = await this.#onOptInReceived(optIn);
111581
111818
  if (!decision.accepted) return;
111819
+ const maxParticipants = this.#config.maxParticipants;
111820
+ const cohortNow = this.session.getCohort(this.#cohortId);
111821
+ if (maxParticipants !== void 0 && cohortNow && cohortNow.participants.length >= maxParticipants) {
111822
+ return;
111823
+ }
111582
111824
  await this.#sendAll(this.session.acceptParticipant(this.#cohortId, msg.from));
111583
111825
  this.emit("participant-accepted", { participantDid: msg.from });
111584
111826
  const cohort = this.session.getCohort(this.#cohortId);
@@ -111761,7 +112003,10 @@ ${value2}`;
111761
112003
  this.#onProvideUpdate = options2.onProvideUpdate;
111762
112004
  this.#onValidateData = options2.onValidateData ?? (async (info) => ({ approved: info.matches }));
111763
112005
  this.#onApproveSigning = options2.onApproveSigning ?? (async () => ({ approved: true }));
111764
- this.session = new AggregationParticipant({ did: options2.did, keys: options2.keys });
112006
+ this.session = new AggregationParticipant({
112007
+ did: options2.did,
112008
+ signer: new KeyPairAggregationSigner(options2.keys)
112009
+ });
111765
112010
  }
111766
112011
  /**
111767
112012
  * Start listening for cohorts. The runner stays active until {@link stop}
@@ -111774,6 +112019,7 @@ ${value2}`;
111774
112019
  stop() {
111775
112020
  this.#stopped = true;
111776
112021
  this.#unregisterHandlers();
112022
+ this.session.clearSecrets();
111777
112023
  }
111778
112024
  /** Message types this runner listens for on the transport. */
111779
112025
  static #HANDLED_MESSAGE_TYPES = [
@@ -112615,84 +112861,6 @@ ${value2}`;
112615
112861
  // src/core/beacon/signal-discovery.ts
112616
112862
  init_shim();
112617
112863
 
112618
- // ../bitcoin/dist/esm/index.js
112619
- init_shim();
112620
-
112621
- // ../bitcoin/dist/esm/connection.js
112622
- init_shim();
112623
-
112624
- // ../bitcoin/dist/esm/client/rest/index.js
112625
- init_shim();
112626
-
112627
- // ../bitcoin/dist/esm/client/rest/address.js
112628
- init_shim();
112629
-
112630
- // ../bitcoin/dist/esm/client/rest/block.js
112631
- init_shim();
112632
-
112633
- // ../bitcoin/dist/esm/errors.js
112634
- init_shim();
112635
-
112636
- // ../bitcoin/dist/esm/client/rest/transaction.js
112637
- init_shim();
112638
-
112639
- // ../bitcoin/dist/esm/client/rest/protocol.js
112640
- init_shim();
112641
-
112642
- // ../bitcoin/dist/esm/client/http.js
112643
- init_shim();
112644
-
112645
- // ../bitcoin/dist/esm/client/rpc/index.js
112646
- init_shim();
112647
-
112648
- // ../bitcoin/dist/esm/client/rpc/json-rpc.js
112649
- init_shim();
112650
-
112651
- // ../bitcoin/dist/esm/client/utils.js
112652
- init_shim();
112653
-
112654
- // ../bitcoin/dist/esm/client/rpc/protocol.js
112655
- init_shim();
112656
-
112657
- // ../bitcoin/dist/esm/network.js
112658
- init_shim();
112659
- var REGTEST_NETWORK = {
112660
- bech32: "bcrt",
112661
- pubKeyHash: 111,
112662
- scriptHash: 196,
112663
- wif: 239
112664
- };
112665
- function getNetwork(network) {
112666
- switch (network) {
112667
- case "bitcoin":
112668
- return NETWORK;
112669
- case "testnet3":
112670
- case "testnet4":
112671
- case "signet":
112672
- case "mutinynet":
112673
- return TEST_NETWORK;
112674
- case "regtest":
112675
- return REGTEST_NETWORK;
112676
- default:
112677
- throw new Error(`Unknown network "${network}"`);
112678
- }
112679
- }
112680
-
112681
- // ../bitcoin/dist/esm/constants.js
112682
- init_shim();
112683
- var TXIN_WITNESS_COINBASE = "0000000000000000000000000000000000000000000000000000000000000000";
112684
- var GENESIS_TX_ID = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b";
112685
-
112686
- // ../bitcoin/dist/esm/types.js
112687
- init_shim();
112688
- var VerbosityLevel;
112689
- (function(VerbosityLevel2) {
112690
- VerbosityLevel2[VerbosityLevel2["hex"] = 0] = "hex";
112691
- VerbosityLevel2[VerbosityLevel2["json"] = 1] = "json";
112692
- VerbosityLevel2[VerbosityLevel2["jsonext"] = 2] = "jsonext";
112693
- VerbosityLevel2[VerbosityLevel2["jsonextprev"] = 3] = "jsonextprev";
112694
- })(VerbosityLevel || (VerbosityLevel = {}));
112695
-
112696
112864
  // src/core/beacon/utils.ts
112697
112865
  init_shim();
112698
112866