@did-btcr2/method 0.35.0 → 0.36.1
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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +460 -223
- package/dist/browser.mjs +460 -223
- package/dist/cjs/index.js +461 -223
- package/dist/esm/core/aggregation/cohort.js +3 -1
- package/dist/esm/core/aggregation/cohort.js.map +1 -1
- package/dist/esm/core/aggregation/conditions.js +75 -0
- package/dist/esm/core/aggregation/conditions.js.map +1 -0
- package/dist/esm/core/aggregation/messages/base.js.map +1 -1
- package/dist/esm/core/aggregation/messages/bodies.js +16 -2
- package/dist/esm/core/aggregation/messages/bodies.js.map +1 -1
- package/dist/esm/core/aggregation/messages/factories.js.map +1 -1
- package/dist/esm/core/aggregation/participant.js +20 -7
- package/dist/esm/core/aggregation/participant.js.map +1 -1
- package/dist/esm/core/aggregation/runner/participant-runner.js +37 -2
- package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -1
- package/dist/esm/core/aggregation/runner/service-runner.js +323 -189
- package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -1
- package/dist/esm/core/aggregation/service.js +23 -3
- package/dist/esm/core/aggregation/service.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/types/core/aggregation/cohort.d.ts.map +1 -1
- package/dist/types/core/aggregation/conditions.d.ts +58 -0
- package/dist/types/core/aggregation/conditions.d.ts.map +1 -0
- package/dist/types/core/aggregation/messages/base.d.ts +2 -3
- package/dist/types/core/aggregation/messages/base.d.ts.map +1 -1
- package/dist/types/core/aggregation/messages/bodies.d.ts +2 -3
- package/dist/types/core/aggregation/messages/bodies.d.ts.map +1 -1
- package/dist/types/core/aggregation/messages/factories.d.ts +2 -3
- package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -1
- package/dist/types/core/aggregation/participant.d.ts +16 -4
- package/dist/types/core/aggregation/participant.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/events.d.ts +22 -11
- package/dist/types/core/aggregation/runner/events.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/participant-runner.d.ts +21 -12
- package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/service-runner.d.ts +76 -22
- package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -1
- package/dist/types/core/aggregation/service.d.ts +8 -4
- package/dist/types/core/aggregation/service.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/core/aggregation/cohort.ts +3 -1
- package/src/core/aggregation/conditions.ts +116 -0
- package/src/core/aggregation/messages/base.ts +6 -3
- package/src/core/aggregation/messages/bodies.ts +18 -6
- package/src/core/aggregation/messages/factories.ts +2 -3
- package/src/core/aggregation/participant.ts +28 -11
- package/src/core/aggregation/runner/events.ts +23 -14
- package/src/core/aggregation/runner/participant-runner.ts +43 -13
- package/src/core/aggregation/runner/service-runner.ts +375 -195
- package/src/core/aggregation/service.ts +39 -7
- package/src/index.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"participant-runner.d.ts","sourceRoot":"","sources":["../../../../../src/core/aggregation/runner/participant-runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"participant-runner.d.ts","sourceRoot":"","sources":["../../../../../src/core/aggregation/runner/participant-runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAUzD,OAAO,KAAK,EACV,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EAAC,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EACL,sBAAsB,EACvB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAE,4BAA4B,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,yEAAyE;AACzE,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpE,wEAAwE;AACxE,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEjC,4DAA4D;AAC5D,MAAM,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,iBAAiB,KAAK,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAEzF,oDAAoD;AACpD,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,qBAAqB,KAAK,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAE/F,MAAM,WAAW,mCAAmC;IAClD,4BAA4B;IAC5B,SAAS,EAAE,SAAS,CAAC;IAErB,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,cAAc,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IAExB;;;OAGG;IACH,eAAe,EAAE,eAAe,CAAC;IAEjC;;;OAGG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,qBAAa,4BAA6B,SAAQ,iBAAiB,CAAC,4BAA4B,CAAC;;IAC/F,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,sBAAsB,CAAC;gBAY7B,OAAO,EAAE,mCAAmC;IAexD;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,8EAA8E;IAC9E,IAAI,IAAI,IAAI;IA4BZ;;;;OAIG;WACU,SAAS,CACpB,OAAO,EAAE,mCAAmC,GAC3C,OAAO,CAAC,kBAAkB,CAAC;IAY9B;;;;;;;;;;;;;;;OAeG;WACU,YAAY,CACvB,OAAO,EAAE,mCAAmC,EAC5C,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,kBAAkB,EAAE,CAAC;CA+NjC"}
|
|
@@ -27,8 +27,13 @@ export interface AggregationServiceRunnerOptions {
|
|
|
27
27
|
/** This service's identity. */
|
|
28
28
|
did: string;
|
|
29
29
|
keys: SchnorrKeyPair;
|
|
30
|
-
/**
|
|
31
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Default cohort configuration for the {@link AggregationServiceRunner.run}
|
|
32
|
+
* convenience path. Optional: omit it when driving the runner with
|
|
33
|
+
* {@link AggregationServiceRunner.advertiseCohort}, which takes a per-cohort
|
|
34
|
+
* config and can be called many times on one runner.
|
|
35
|
+
*/
|
|
36
|
+
config?: CohortConfig;
|
|
32
37
|
/**
|
|
33
38
|
* Decide whether to accept a participant's opt-in.
|
|
34
39
|
* Default: auto-accept all opt-ins.
|
|
@@ -52,24 +57,26 @@ export interface AggregationServiceRunnerOptions {
|
|
|
52
57
|
*/
|
|
53
58
|
maxUpdateSizeBytes?: number;
|
|
54
59
|
/**
|
|
55
|
-
* Overall wall-clock budget for
|
|
56
|
-
* On expiry the cohort is dropped, `cohort-failed` is
|
|
57
|
-
* rejects with a timeout error.
|
|
60
|
+
* Overall wall-clock budget for each cohort, from advertise to
|
|
61
|
+
* signing-complete. On expiry the cohort is dropped, `cohort-failed` is
|
|
62
|
+
* emitted, and that cohort's completion rejects with a timeout error. Other
|
|
63
|
+
* cohorts on the same runner are unaffected. Leave undefined to disable.
|
|
58
64
|
*/
|
|
59
65
|
cohortTtlMs?: number;
|
|
60
66
|
/**
|
|
61
|
-
* Maximum time allowed between phase transitions. Protects
|
|
62
|
-
* cohorts (e.g. a participant vanishing mid-protocol). Reset
|
|
63
|
-
* on every observed phase change.
|
|
67
|
+
* Maximum time allowed between phase transitions for a cohort. Protects
|
|
68
|
+
* against stalled cohorts (e.g. a participant vanishing mid-protocol). Reset
|
|
69
|
+
* automatically on every observed phase change. Applied per cohort. Leave
|
|
70
|
+
* undefined to disable.
|
|
64
71
|
*/
|
|
65
72
|
phaseTimeoutMs?: number;
|
|
66
73
|
/**
|
|
67
|
-
* Re-publish COHORT_ADVERT on this interval until keygen is
|
|
68
|
-
* Works around relays that don't backfill historical events to
|
|
69
|
-
* subscribers — a republish gives late joiners a window to discover the
|
|
74
|
+
* Re-publish COHORT_ADVERT on this interval until a cohort's keygen is
|
|
75
|
+
* finalized. Works around relays that don't backfill historical events to
|
|
76
|
+
* late subscribers — a republish gives late joiners a window to discover the
|
|
70
77
|
* advert without protocol changes. The first publish is immediate;
|
|
71
|
-
* subsequent publishes fire every `advertRepeatIntervalMs` until
|
|
72
|
-
* keygen
|
|
78
|
+
* subsequent publishes fire every `advertRepeatIntervalMs` until that
|
|
79
|
+
* cohort's keygen completes, fails, or is stopped. Defaults to
|
|
73
80
|
* {@link DEFAULT_ADVERT_REPEAT_INTERVAL_MS} (60 s). Set to 0 to publish
|
|
74
81
|
* once and never retry.
|
|
75
82
|
*/
|
|
@@ -84,6 +91,14 @@ export declare const DEFAULT_ADVERT_REPEAT_INTERVAL_MS = 60000;
|
|
|
84
91
|
* encapsulating message handler registration, outgoing message dispatch,
|
|
85
92
|
* and decision callback orchestration.
|
|
86
93
|
*
|
|
94
|
+
* A single runner is a long-lived multiplexer: it advertises and drives many
|
|
95
|
+
* cohorts concurrently over one transport. Each advertised cohort owns an
|
|
96
|
+
* independent completion promise and fails in isolation — a stalled or failed
|
|
97
|
+
* cohort never settles its siblings (see ADR 040). Use
|
|
98
|
+
* {@link AggregationServiceRunner.advertiseCohort} for the multi-cohort path;
|
|
99
|
+
* {@link AggregationServiceRunner.run} is a thin single-cohort convenience over
|
|
100
|
+
* it.
|
|
101
|
+
*
|
|
87
102
|
* @example
|
|
88
103
|
* ```typescript
|
|
89
104
|
* const transport = new NostrTransport({ relays: [RELAY] });
|
|
@@ -93,16 +108,21 @@ export declare const DEFAULT_ADVERT_REPEAT_INTERVAL_MS = 60000;
|
|
|
93
108
|
* transport,
|
|
94
109
|
* did: serviceDid,
|
|
95
110
|
* keys: serviceKeys,
|
|
96
|
-
*
|
|
97
|
-
* onProvideTxData: async ({ beaconAddress, signalBytes }) => {
|
|
111
|
+
* onProvideTxData: async ({ cohortId, beaconAddress, signalBytes }) => {
|
|
98
112
|
* return await buildBeaconTransaction(beaconAddress, signalBytes, bitcoin);
|
|
99
113
|
* },
|
|
100
114
|
* });
|
|
101
115
|
*
|
|
102
|
-
* runner.on('keygen-complete', ({ beaconAddress }) => console.log(beaconAddress));
|
|
103
|
-
* runner.on('signing-complete', ({ signature }) => console.log('done'));
|
|
116
|
+
* runner.on('keygen-complete', ({ cohortId, beaconAddress }) => console.log(beaconAddress));
|
|
117
|
+
* runner.on('signing-complete', ({ cohortId, signature }) => console.log('done', cohortId));
|
|
104
118
|
*
|
|
105
|
-
*
|
|
119
|
+
* // Multi-cohort: advertise several cohorts; each completion resolves independently.
|
|
120
|
+
* const a = runner.advertiseCohort({ minParticipants: 2, network: 'mutinynet', beaconType: 'CASBeacon' });
|
|
121
|
+
* const b = runner.advertiseCohort({ minParticipants: 3, network: 'mutinynet', beaconType: 'SMTBeacon' });
|
|
122
|
+
* const [ra, rb] = await Promise.all([a.completion, b.completion]);
|
|
123
|
+
*
|
|
124
|
+
* // Single-cohort convenience (requires `config` in the options):
|
|
125
|
+
* // const result = await runner.run();
|
|
106
126
|
* ```
|
|
107
127
|
*
|
|
108
128
|
* For full manual control, drop down to the underlying state machine via
|
|
@@ -117,15 +137,49 @@ export declare class AggregationServiceRunner extends TypedEventEmitter<Aggregat
|
|
|
117
137
|
readonly session: AggregationService;
|
|
118
138
|
constructor(options: AggregationServiceRunnerOptions);
|
|
119
139
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
140
|
+
* Advertise a new cohort and begin driving it to completion. Callable many
|
|
141
|
+
* times on one runner; each cohort runs concurrently and independently.
|
|
142
|
+
*
|
|
143
|
+
* @param config Per-cohort conditions + network (see {@link CohortConfig}).
|
|
144
|
+
* @returns The new cohort's id and a `completion` promise that resolves with
|
|
145
|
+
* that cohort's {@link AggregationResult} (or rejects if it fails/stalls).
|
|
146
|
+
* @throws If the runner has been stopped, or the config is invalid
|
|
147
|
+
* (fail-fast via `createCohort`).
|
|
148
|
+
*/
|
|
149
|
+
advertiseCohort(config: CohortConfig): {
|
|
150
|
+
cohortId: string;
|
|
151
|
+
completion: Promise<AggregationResult>;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Run a single cohort to completion using the `config` supplied in the
|
|
155
|
+
* runner options. Thin convenience over {@link advertiseCohort} for the
|
|
156
|
+
* single-cohort case (and the path {@link AggregationRunner.solo} rides).
|
|
122
157
|
*
|
|
123
158
|
* @returns {Promise<AggregationResult>} The final result with signature and signed tx.
|
|
124
159
|
*/
|
|
125
160
|
run(): Promise<AggregationResult>;
|
|
126
161
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
162
|
+
* Wait for every currently-outstanding cohort to settle and return the
|
|
163
|
+
* successful results. Dynamic drain: cohorts advertised while this is pending
|
|
164
|
+
* are included, and it resolves only once no cohorts remain. Failed cohorts
|
|
165
|
+
* are surfaced via `error` / `cohort-failed` events and their rejected
|
|
166
|
+
* `completion` promises; they are omitted from the returned array (this
|
|
167
|
+
* method does not throw). Bound long-running cohorts with `cohortTtlMs` /
|
|
168
|
+
* `phaseTimeoutMs` or this may never resolve.
|
|
169
|
+
*
|
|
170
|
+
* @returns {Promise<AggregationResult[]>} Results of the cohorts that completed.
|
|
171
|
+
*/
|
|
172
|
+
runAll(): Promise<AggregationResult[]>;
|
|
173
|
+
/**
|
|
174
|
+
* Stop a single cohort early without affecting the rest of the runner. Drops
|
|
175
|
+
* the cohort's state machine state; its `completion` promise rejects with a
|
|
176
|
+
* stopped error.
|
|
177
|
+
*/
|
|
178
|
+
stopCohort(cohortId: string): void;
|
|
179
|
+
/**
|
|
180
|
+
* Stop the whole runner. Fails every outstanding cohort, then detaches the
|
|
181
|
+
* shared transport handlers so a restart or a new runner doesn't inherit
|
|
182
|
+
* stale dispatch. Safe to call repeatedly.
|
|
129
183
|
*/
|
|
130
184
|
stop(): void;
|
|
131
185
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-runner.d.ts","sourceRoot":"","sources":["../../../../../src/core/aggregation/runner/service-runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"service-runner.d.ts","sourceRoot":"","sources":["../../../../../src/core/aggregation/runner/service-runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAWzD,OAAO,KAAK,EACV,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,aAAa,EAAC,MAAM,eAAe,CAAC;AACtC,OAAO,EACL,kBAAkB,EACnB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,kEAAkE;AAClE,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAEtF,6EAA6E;AAC7E,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB,KAAK,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAErC,mEAAmE;AACnE,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,UAAU,CAAC;CACzB,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;AAE7B,MAAM,WAAW,+BAA+B;IAC9C,kEAAkE;IAClE,SAAS,EAAE,SAAS,CAAC;IAErB,+BAA+B;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,cAAc,CAAC;IAErB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IAEtB;;;OAGG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAEtC;;;OAGG;IACH,eAAe,EAAE,eAAe,CAAC;IAEjC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;;;;;;OASG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,0FAA0F;AAC1F,eAAO,MAAM,iCAAiC,QAAS,CAAC;AAqCxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,qBAAa,wBAAyB,SAAQ,iBAAiB,CAAC,wBAAwB,CAAC;;IACvF,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;gBAiBzB,OAAO,EAAE,+BAA+B;IAyCpD;;;;;;;;;OASG;IACH,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAA;KAAE;IA6CnG;;;;;;OAMG;IACH,GAAG,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAcjC;;;;;;;;;;OAUG;IACG,MAAM,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAiI5C;;;;OAIG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IASlC;;;;OAIG;IACH,IAAI,IAAI,IAAI;CAyQb"}
|
|
@@ -2,13 +2,17 @@ import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
|
2
2
|
import type { CompressedSecp256k1PublicKey } from '@did-btcr2/keypair';
|
|
3
3
|
import type { Transaction } from '@scure/btc-signer';
|
|
4
4
|
import { AggregationCohort } from './cohort.js';
|
|
5
|
+
import { type CohortConditions } from './conditions.js';
|
|
5
6
|
import type { BaseMessage } from './messages/base.js';
|
|
6
7
|
import type { ServiceCohortPhaseType } from './phases.js';
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Cohort configuration set by the service operator: the advertised cohort
|
|
10
|
+
* {@link CohortConditions} plus the Bitcoin network. `beaconType` and
|
|
11
|
+
* `minParticipants` are required; the other conditions are optional (absent =
|
|
12
|
+
* unconstrained). See ADR 039.
|
|
13
|
+
*/
|
|
14
|
+
export interface CohortConfig extends CohortConditions {
|
|
10
15
|
network: string;
|
|
11
|
-
beaconType: string;
|
|
12
16
|
}
|
|
13
17
|
/** Pending opt-in awaiting service operator approval. */
|
|
14
18
|
export interface PendingOptIn {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../../src/core/aggregation/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../../src/core/aggregation/service.ts"],"names":[],"mappings":"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"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './core/aggregation/service.js';
|
|
2
2
|
export * from './core/aggregation/participant.js';
|
|
3
3
|
export * from './core/aggregation/signer.js';
|
|
4
|
+
export * from './core/aggregation/conditions.js';
|
|
4
5
|
export * from './core/aggregation/cohort.js';
|
|
5
6
|
export * from './core/aggregation/signing-session.js';
|
|
6
7
|
export * from './core/aggregation/phases.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sCAAsC,CAAC;AACrD,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AAGnD,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AAGvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iCAAiC,CAAC;AAChD,cAAc,yBAAyB,CAAC;AAGxC,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sCAAsC,CAAC;AACrD,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AAGnD,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AAGvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iCAAiC,CAAC;AAChD,cAAc,yBAAyB,CAAC;AAGxC,cAAc,gBAAgB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@did-btcr2/method",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Reference implementation for the did:btcr2 DID method written in TypeScript and JavaScript. did:btcr2 is a censorship resistant DID Method using the Bitcoin blockchain as a Verifiable Data Registry to announce changes to the DID document. This is the core method implementation for the did-btcr2-js monorepo.",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -80,11 +80,11 @@
|
|
|
80
80
|
"helia": "^5.5.1",
|
|
81
81
|
"multiformats": "^13.4.2",
|
|
82
82
|
"nostr-tools": "^2.23.3",
|
|
83
|
-
"@did-btcr2/common": "^9.1.0",
|
|
84
83
|
"@did-btcr2/bitcoin": "^0.6.0",
|
|
85
|
-
"@did-btcr2/
|
|
84
|
+
"@did-btcr2/keypair": "^0.13.1",
|
|
86
85
|
"@did-btcr2/smt": "^0.3.0",
|
|
87
|
-
"@did-btcr2/
|
|
86
|
+
"@did-btcr2/common": "^9.1.0",
|
|
87
|
+
"@did-btcr2/cryptosuite": "^8.0.0"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
90
|
"@eslint/js": "^9.39.4",
|
|
@@ -91,7 +91,9 @@ export class AggregationCohort {
|
|
|
91
91
|
|
|
92
92
|
constructor({ id, minParticipants, serviceDid, network, beaconType }: AggregationCohortParams) {
|
|
93
93
|
this.id = id || crypto.randomUUID();
|
|
94
|
-
|
|
94
|
+
// `?? 2` (not `|| 2`) so a deliberately-passed 0 is preserved rather than
|
|
95
|
+
// silently coerced; the service rejects an invalid count at createCohort.
|
|
96
|
+
this.minParticipants = minParticipants ?? 2;
|
|
95
97
|
this.serviceDid = serviceDid || '';
|
|
96
98
|
this.network = network;
|
|
97
99
|
this.beaconType = beaconType || 'CASBeacon';
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cohort conditions: the constraints an Aggregation Service advertises for a
|
|
3
|
+
* cohort (did:btcr2 spec, "Step 1: Create Aggregation Cohort"). See ADR 039.
|
|
4
|
+
*
|
|
5
|
+
* The spec frames these as an optional menu ("the Aggregation Service can define
|
|
6
|
+
* conditions such as ..."), so only `beaconType` and `minParticipants` are
|
|
7
|
+
* required here; every other condition is optional and, when absent, means
|
|
8
|
+
* unconstrained.
|
|
9
|
+
*
|
|
10
|
+
* Enforcement is staged (ADR 039): `beaconType` and the participant bounds are
|
|
11
|
+
* enforced by the state machine now; DIDs-per-participant, timing/cadence, and
|
|
12
|
+
* the pending-update trigger are modeled and advertised here but enforced when
|
|
13
|
+
* the multi-cohort (AGG-4) and non-inclusion (AGG-5) tracks land. The two cost
|
|
14
|
+
* conditions are advertised metadata only - the protocol performs no payment or
|
|
15
|
+
* settlement (consistent with ADR 008).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Beacon types that support aggregation (singleton is single-party only, per ADR 037). */
|
|
19
|
+
export const KNOWN_BEACON_TYPES = ['CASBeacon', 'SMTBeacon'] as const;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* An advertised price. `unit` is operator-defined (the spec does not specify a
|
|
23
|
+
* currency); `basis` distinguishes a per-DID from a per-participant charge for
|
|
24
|
+
* "cost per announcement". Advertised only - never settled by the protocol.
|
|
25
|
+
*/
|
|
26
|
+
export interface CohortCost {
|
|
27
|
+
amount: number;
|
|
28
|
+
unit: string;
|
|
29
|
+
basis?: 'per-did' | 'per-participant';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The seven spec cohort conditions. Only beaconType + minParticipants are required. */
|
|
33
|
+
export interface CohortConditions {
|
|
34
|
+
/** 1. Beacon mechanism: 'CASBeacon' or 'SMTBeacon'. Enforced. */
|
|
35
|
+
beaconType: string;
|
|
36
|
+
/** 2. Lower bound on cohort size. Enforced (finalize floor). */
|
|
37
|
+
minParticipants: number;
|
|
38
|
+
/** 2. Upper bound on cohort size. Enforced (accept/finalize ceiling). */
|
|
39
|
+
maxParticipants?: number;
|
|
40
|
+
/** 3. Lower bound on DIDs a participant may register. Advertised; enforcement staged (AGG-5). */
|
|
41
|
+
minDidsPerParticipant?: number;
|
|
42
|
+
/** 3. Upper bound on DIDs a participant may register. Advertised; enforcement staged (AGG-5). */
|
|
43
|
+
maxDidsPerParticipant?: number;
|
|
44
|
+
/** 4. One-time enrollment price. Advertised only - no settlement. */
|
|
45
|
+
costOfEnrollment?: CohortCost;
|
|
46
|
+
/** 5. Recurring per-announcement price. Advertised only - no settlement. */
|
|
47
|
+
costPerAnnouncement?: CohortCost;
|
|
48
|
+
/** 6. Floor on time between announcements (seconds). Advertised; enforcement staged (AGG-4/5). */
|
|
49
|
+
minSecondsBetweenAnnouncements?: number;
|
|
50
|
+
/** 6. Ceiling on time between announcements (seconds). Advertised; enforcement staged - generalizes the ADR 027 Cohort TTL. */
|
|
51
|
+
maxSecondsBetweenAnnouncements?: number;
|
|
52
|
+
/** 7. Pending-update count that triggers an announcement. Advertised; enforcement staged (AGG-5) - generalizes hasAllUpdates(). */
|
|
53
|
+
pendingUpdateTrigger?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Validate an optional [min, max] integer pair. */
|
|
57
|
+
function checkPair(problems: string[], label: string, min?: number, max?: number): void {
|
|
58
|
+
if(min !== undefined && (!Number.isInteger(min) || min < 0)) {
|
|
59
|
+
problems.push(`min${label} must be an integer >= 0`);
|
|
60
|
+
}
|
|
61
|
+
if(max !== undefined && (!Number.isInteger(max) || max < 0)) {
|
|
62
|
+
problems.push(`max${label} must be an integer >= 0`);
|
|
63
|
+
}
|
|
64
|
+
if(min !== undefined && max !== undefined && Number.isInteger(min) && Number.isInteger(max) && max < min) {
|
|
65
|
+
problems.push(`max${label} must be >= min${label}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Validate an optional advertised cost. */
|
|
70
|
+
function checkCost(problems: string[], label: string, cost?: CohortCost): void {
|
|
71
|
+
if(cost === undefined) return;
|
|
72
|
+
if(typeof cost.amount !== 'number' || !Number.isFinite(cost.amount) || cost.amount < 0) {
|
|
73
|
+
problems.push(`${label}.amount must be a finite number >= 0`);
|
|
74
|
+
}
|
|
75
|
+
if(typeof cost.unit !== 'string' || cost.unit.length === 0) {
|
|
76
|
+
problems.push(`${label}.unit must be a non-empty string`);
|
|
77
|
+
}
|
|
78
|
+
if(cost.basis !== undefined && cost.basis !== 'per-did' && cost.basis !== 'per-participant') {
|
|
79
|
+
problems.push(`${label}.basis must be 'per-did' or 'per-participant'`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Validate a set of cohort conditions. Returns a list of human-readable problems
|
|
85
|
+
* (empty when valid) so the caller can decide how to surface them. Used by
|
|
86
|
+
* `createCohort()` to fail fast instead of discovering invalidity at finalize.
|
|
87
|
+
*/
|
|
88
|
+
export function validateCohortConditions(c: CohortConditions): string[] {
|
|
89
|
+
const problems: string[] = [];
|
|
90
|
+
|
|
91
|
+
if(!(KNOWN_BEACON_TYPES as readonly string[]).includes(c.beaconType)) {
|
|
92
|
+
problems.push(`beaconType must be one of ${KNOWN_BEACON_TYPES.join(', ')}`);
|
|
93
|
+
}
|
|
94
|
+
if(!Number.isInteger(c.minParticipants) || c.minParticipants < 1) {
|
|
95
|
+
problems.push('minParticipants must be an integer >= 1');
|
|
96
|
+
}
|
|
97
|
+
if(c.maxParticipants !== undefined) {
|
|
98
|
+
if(!Number.isInteger(c.maxParticipants) || c.maxParticipants < 1) {
|
|
99
|
+
problems.push('maxParticipants must be an integer >= 1');
|
|
100
|
+
} else if(Number.isInteger(c.minParticipants) && c.maxParticipants < c.minParticipants) {
|
|
101
|
+
problems.push('maxParticipants must be >= minParticipants');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
checkPair(problems, 'DidsPerParticipant', c.minDidsPerParticipant, c.maxDidsPerParticipant);
|
|
106
|
+
checkPair(problems, 'SecondsBetweenAnnouncements', c.minSecondsBetweenAnnouncements, c.maxSecondsBetweenAnnouncements);
|
|
107
|
+
|
|
108
|
+
if(c.pendingUpdateTrigger !== undefined && (!Number.isInteger(c.pendingUpdateTrigger) || c.pendingUpdateTrigger < 1)) {
|
|
109
|
+
problems.push('pendingUpdateTrigger must be an integer >= 1');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
checkCost(problems, 'costOfEnrollment', c.costOfEnrollment);
|
|
113
|
+
checkCost(problems, 'costPerAnnouncement', c.costPerAnnouncement);
|
|
114
|
+
|
|
115
|
+
return problems;
|
|
116
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { CohortConditions } from '../conditions.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Current on-the-wire protocol version.
|
|
3
5
|
*
|
|
@@ -7,9 +9,11 @@
|
|
|
7
9
|
*/
|
|
8
10
|
export const AGGREGATION_WIRE_VERSION = 1;
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
// Cohort conditions (beaconType, minParticipants, maxParticipants, ...) ride on
|
|
13
|
+
// the wire as flat optional body fields, supplied via `Partial<CohortConditions>`
|
|
14
|
+
// below so the bag stays a single source of truth for the advertised conditions.
|
|
15
|
+
export type BaseBody = Partial<CohortConditions> & {
|
|
11
16
|
cohortId: string;
|
|
12
|
-
cohortSize?: number;
|
|
13
17
|
network?: string;
|
|
14
18
|
participantPk?: Uint8Array;
|
|
15
19
|
beaconAddress?: string;
|
|
@@ -23,7 +27,6 @@ export type BaseBody = {
|
|
|
23
27
|
prevOutScriptHex?: string;
|
|
24
28
|
prevOutValue?: string;
|
|
25
29
|
communicationPk?: Uint8Array;
|
|
26
|
-
beaconType?: string;
|
|
27
30
|
data?: string;
|
|
28
31
|
signedUpdate?: Record<string, unknown>;
|
|
29
32
|
casAnnouncement?: Record<string, string>;
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { SerializedSMTProof } from '@did-btcr2/smt';
|
|
15
|
+
import type { CohortConditions } from '../conditions.js';
|
|
15
16
|
import type { BaseMessage } from './base.js';
|
|
16
17
|
import {
|
|
17
18
|
AGGREGATED_NONCE,
|
|
@@ -29,10 +30,8 @@ import {
|
|
|
29
30
|
|
|
30
31
|
// ── Cohort formation (Step 1) ─────────────────────────────────────────────
|
|
31
32
|
|
|
32
|
-
export interface CohortAdvertBody {
|
|
33
|
+
export interface CohortAdvertBody extends CohortConditions {
|
|
33
34
|
cohortId: string;
|
|
34
|
-
cohortSize: number;
|
|
35
|
-
beaconType: string;
|
|
36
35
|
network: string;
|
|
37
36
|
communicationPk: Uint8Array;
|
|
38
37
|
}
|
|
@@ -135,8 +134,16 @@ export type AggregationMessage =
|
|
|
135
134
|
|
|
136
135
|
const hasStr = (b: unknown, k: string): boolean =>
|
|
137
136
|
!!b && typeof (b as Record<string, unknown>)[k] === 'string';
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
/** Present, an integer, and >= min. */
|
|
138
|
+
const hasIntMin = (b: unknown, k: string, min: number): boolean => {
|
|
139
|
+
const v = b ? (b as Record<string, unknown>)[k] : undefined;
|
|
140
|
+
return typeof v === 'number' && Number.isInteger(v) && v >= min;
|
|
141
|
+
};
|
|
142
|
+
/** Absent, or present as an integer >= min. */
|
|
143
|
+
const optIntMin = (b: unknown, k: string, min: number): boolean => {
|
|
144
|
+
const v = b ? (b as Record<string, unknown>)[k] : undefined;
|
|
145
|
+
return v === undefined || (typeof v === 'number' && Number.isInteger(v) && v >= min);
|
|
146
|
+
};
|
|
140
147
|
const hasBool = (b: unknown, k: string): boolean =>
|
|
141
148
|
!!b && typeof (b as Record<string, unknown>)[k] === 'boolean';
|
|
142
149
|
const hasBytes = (b: unknown, k: string): boolean =>
|
|
@@ -147,9 +154,14 @@ const hasBytesArray = (b: unknown, k: string): boolean => {
|
|
|
147
154
|
};
|
|
148
155
|
|
|
149
156
|
export function isCohortAdvertMessage(m: BaseMessage): m is CohortAdvertMessage {
|
|
157
|
+
// Range-check the participant bounds so a malformed advert (missing or
|
|
158
|
+
// zero/negative minParticipants) is rejected rather than silently accepted as
|
|
159
|
+
// a zero-floor cohort. The service does the full cross-field validation at
|
|
160
|
+
// createCohort (see validateCohortConditions); here we guard the wire shape.
|
|
150
161
|
return m.type === COHORT_ADVERT
|
|
151
162
|
&& hasStr(m.body, 'cohortId')
|
|
152
|
-
&&
|
|
163
|
+
&& hasIntMin(m.body, 'minParticipants', 1)
|
|
164
|
+
&& optIntMin(m.body, 'maxParticipants', 1)
|
|
153
165
|
&& hasStr(m.body, 'beaconType')
|
|
154
166
|
&& hasStr(m.body, 'network')
|
|
155
167
|
&& hasBytes(m.body, 'communicationPk');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CohortConditions } from '../conditions.js';
|
|
1
2
|
import { BaseMessage } from './base.js';
|
|
2
3
|
import {
|
|
3
4
|
AGGREGATED_NONCE,
|
|
@@ -18,11 +19,9 @@ import {
|
|
|
18
19
|
* Factory functions for creating messages related to the cohort formation step, where cohorts are
|
|
19
20
|
* formed and participants opt in to join the cohort.
|
|
20
21
|
*/
|
|
21
|
-
type CohortAdvertMessage = {
|
|
22
|
+
type CohortAdvertMessage = CohortConditions & {
|
|
22
23
|
from: string;
|
|
23
24
|
cohortId: string;
|
|
24
|
-
cohortSize: number;
|
|
25
|
-
beaconType: string;
|
|
26
25
|
network: string;
|
|
27
26
|
communicationPk: Uint8Array;
|
|
28
27
|
};
|
|
@@ -5,8 +5,10 @@ import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
|
|
|
5
5
|
import { Transaction } from '@scure/btc-signer';
|
|
6
6
|
import { getBeaconStrategy } from './beacon-strategy.js';
|
|
7
7
|
import { AggregationCohort } from './cohort.js';
|
|
8
|
+
import type { CohortConditions } from './conditions.js';
|
|
8
9
|
import { AggregationParticipantError } from './errors.js';
|
|
9
10
|
import type { BaseMessage } from './messages/base.js';
|
|
11
|
+
import { isCohortAdvertMessage } from './messages/bodies.js';
|
|
10
12
|
import { AGGREGATION_WIRE_VERSION } from './messages/base.js';
|
|
11
13
|
import {
|
|
12
14
|
AGGREGATED_NONCE,
|
|
@@ -28,13 +30,15 @@ import { ParticipantCohortPhase } from './phases.js';
|
|
|
28
30
|
import type { AggregationSigner } from './signer.js';
|
|
29
31
|
import { BeaconSigningSession } from './signing-session.js';
|
|
30
32
|
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Cohort advert as discovered by the participant (UI: list of joinable cohorts).
|
|
35
|
+
* Carries the advertised {@link CohortConditions} (beaconType, minParticipants,
|
|
36
|
+
* maxParticipants, costs, ...) so a `shouldJoin` decision can inspect them.
|
|
37
|
+
*/
|
|
38
|
+
export interface CohortAdvert extends CohortConditions {
|
|
33
39
|
cohortId: string;
|
|
34
40
|
serviceDid: string;
|
|
35
|
-
cohortSize: number;
|
|
36
41
|
network: string;
|
|
37
|
-
beaconType: string;
|
|
38
42
|
serviceCommunicationPk: Uint8Array;
|
|
39
43
|
}
|
|
40
44
|
|
|
@@ -168,17 +172,18 @@ export class AggregationParticipant {
|
|
|
168
172
|
}
|
|
169
173
|
|
|
170
174
|
#handleCohortAdvert(message: BaseMessage): void {
|
|
171
|
-
|
|
172
|
-
|
|
175
|
+
// Validate the wire shape (incl. minParticipants range) before trusting it,
|
|
176
|
+
// rather than reading fields with `?? 0` fallbacks (see ADR 039).
|
|
177
|
+
if(!isCohortAdvertMessage(message)) return;
|
|
178
|
+
const { cohortId, network, communicationPk, ...conditions } = message.body;
|
|
173
179
|
if(this.#cohortStates.has(cohortId)) return; // Already known
|
|
174
180
|
|
|
175
181
|
const advert: CohortAdvert = {
|
|
176
182
|
cohortId,
|
|
177
183
|
serviceDid : message.from,
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
serviceCommunicationPk : message.body?.communicationPk ?? new Uint8Array(),
|
|
184
|
+
network,
|
|
185
|
+
serviceCommunicationPk : communicationPk,
|
|
186
|
+
...conditions,
|
|
182
187
|
};
|
|
183
188
|
|
|
184
189
|
this.#cohortStates.set(cohortId, {
|
|
@@ -206,7 +211,7 @@ export class AggregationParticipant {
|
|
|
206
211
|
const cohort = new AggregationCohort({
|
|
207
212
|
id : cohortId,
|
|
208
213
|
serviceDid : state.serviceDid,
|
|
209
|
-
minParticipants : state.advert!.
|
|
214
|
+
minParticipants : state.advert!.minParticipants,
|
|
210
215
|
network : state.advert!.network,
|
|
211
216
|
beaconType : state.advert!.beaconType,
|
|
212
217
|
});
|
|
@@ -302,6 +307,18 @@ export class AggregationParticipant {
|
|
|
302
307
|
return map;
|
|
303
308
|
}
|
|
304
309
|
|
|
310
|
+
/**
|
|
311
|
+
* The validated aggregated data retained for a cohort, regardless of phase.
|
|
312
|
+
* Unlike {@link pendingValidations} (which lists only cohorts still awaiting
|
|
313
|
+
* the validate decision), this returns the stored validation — including the
|
|
314
|
+
* participant's sidecar (the CAS Announcement map or its SMT inclusion proof)
|
|
315
|
+
* — so it is still readable once the cohort reaches Complete. Returns
|
|
316
|
+
* undefined before aggregated data has been received.
|
|
317
|
+
*/
|
|
318
|
+
public getValidation(cohortId: string): PendingValidation | undefined {
|
|
319
|
+
return this.#cohortStates.get(cohortId)?.validation;
|
|
320
|
+
}
|
|
321
|
+
|
|
305
322
|
#handleDistributeAggregatedData(message: BaseMessage): void {
|
|
306
323
|
const cohortId = message.body?.cohortId;
|
|
307
324
|
if(!cohortId) return;
|
|
@@ -2,6 +2,23 @@ import type { SerializedSMTProof } from '@did-btcr2/smt';
|
|
|
2
2
|
import type { CohortAdvert, PendingSigningRequest, PendingValidation } from '../participant.js';
|
|
3
3
|
import type { AggregationResult, PendingOptIn, Rejection } from '../service.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Sidecar data a participant keeps when a cohort completes from its
|
|
7
|
+
* perspective: the cohort's beacon coordinates plus the off-chain artifact it
|
|
8
|
+
* needs for future DID resolution (the CAS Announcement map for CAS beacons, or
|
|
9
|
+
* the SMT inclusion proof for SMT beacons). Emitted via `cohort-complete` and
|
|
10
|
+
* returned by the participant runner's join helpers.
|
|
11
|
+
*/
|
|
12
|
+
export interface CohortCompleteInfo {
|
|
13
|
+
cohortId: string;
|
|
14
|
+
beaconAddress: string;
|
|
15
|
+
beaconType: string;
|
|
16
|
+
/** DID → base64url update hash. Populated only for CAS beacons. */
|
|
17
|
+
casAnnouncement?: Record<string, string>;
|
|
18
|
+
/** Merkle inclusion proof for this participant's slot. Populated only for SMT beacons. */
|
|
19
|
+
smtProof?: SerializedSMTProof;
|
|
20
|
+
}
|
|
21
|
+
|
|
5
22
|
/**
|
|
6
23
|
* AggregationServiceRunner events are emitted by the AggregationServiceRunner to signal important
|
|
7
24
|
* milestones and actions during the aggregation process. They can be listened to by external code
|
|
@@ -15,13 +32,13 @@ export type AggregationServiceEvents = {
|
|
|
15
32
|
'opt-in-received': [PendingOptIn];
|
|
16
33
|
|
|
17
34
|
/** A participant has been accepted into the cohort. */
|
|
18
|
-
'participant-accepted': [{ participantDid: string }];
|
|
35
|
+
'participant-accepted': [{ cohortId: string; participantDid: string }];
|
|
19
36
|
|
|
20
37
|
/** Keygen has been finalized — beacon address is now available. */
|
|
21
38
|
'keygen-complete': [{ cohortId: string; beaconAddress: string }];
|
|
22
39
|
|
|
23
40
|
/** A participant has submitted a signed update. */
|
|
24
|
-
'update-received': [{ participantDid: string }];
|
|
41
|
+
'update-received': [{ cohortId: string; participantDid: string }];
|
|
25
42
|
|
|
26
43
|
/**
|
|
27
44
|
* An inbound message was silently dropped by the state machine (bad proof,
|
|
@@ -34,13 +51,13 @@ export type AggregationServiceEvents = {
|
|
|
34
51
|
'data-distributed': [{ cohortId: string }];
|
|
35
52
|
|
|
36
53
|
/** A participant has acknowledged validation (approved or rejected). */
|
|
37
|
-
'validation-received': [{ participantDid: string; approved: boolean }];
|
|
54
|
+
'validation-received': [{ cohortId: string; participantDid: string; approved: boolean }];
|
|
38
55
|
|
|
39
56
|
/** Signing has started — auth requests sent to participants. */
|
|
40
|
-
'signing-started': [{ sessionId: string }];
|
|
57
|
+
'signing-started': [{ cohortId: string; sessionId: string }];
|
|
41
58
|
|
|
42
59
|
/** A participant has contributed their MuSig2 nonce. */
|
|
43
|
-
'nonce-received': [{ participantDid: string }];
|
|
60
|
+
'nonce-received': [{ cohortId: string; participantDid: string }];
|
|
44
61
|
|
|
45
62
|
/** Signing complete — final aggregated signature is ready to broadcast. */
|
|
46
63
|
'signing-complete': [AggregationResult];
|
|
@@ -83,15 +100,7 @@ export type AggregationParticipantEvents = {
|
|
|
83
100
|
* future DID resolution: the CAS Announcement map (for CAS beacons) or the
|
|
84
101
|
* SMT inclusion proof (for SMT beacons).
|
|
85
102
|
*/
|
|
86
|
-
'cohort-complete': [
|
|
87
|
-
cohortId: string;
|
|
88
|
-
beaconAddress: string;
|
|
89
|
-
beaconType: string;
|
|
90
|
-
/** DID → base64url update hash. Populated only for CAS beacons. */
|
|
91
|
-
casAnnouncement?: Record<string, string>;
|
|
92
|
-
/** Merkle inclusion proof for this participant's slot. Populated only for SMT beacons. */
|
|
93
|
-
smtProof?: SerializedSMTProof;
|
|
94
|
-
}];
|
|
103
|
+
'cohort-complete': [CohortCompleteInfo];
|
|
95
104
|
|
|
96
105
|
/** Cohort failed (rejected validation, signing error, etc.). */
|
|
97
106
|
'cohort-failed': [{ cohortId: string; reason: string }];
|