@atlasauth/pca-mpc 0.3.0 → 0.4.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.
Files changed (85) hide show
  1. package/README.md +81 -3
  2. package/dist/beaver.js +4 -0
  3. package/dist/beaver.js.map +1 -1
  4. package/dist/compose.d.ts +9 -2
  5. package/dist/compose.js +24 -1
  6. package/dist/compose.js.map +1 -1
  7. package/dist/ec.d.ts +6 -3
  8. package/dist/ec.js +27 -9
  9. package/dist/ec.js.map +1 -1
  10. package/dist/enc.d.ts +46 -0
  11. package/dist/enc.js +88 -0
  12. package/dist/enc.js.map +1 -0
  13. package/dist/endemic-ot.js +24 -1
  14. package/dist/endemic-ot.js.map +1 -1
  15. package/dist/hybrid-ot.js +3 -0
  16. package/dist/hybrid-ot.js.map +1 -1
  17. package/dist/kem-ot.js +2 -0
  18. package/dist/kem-ot.js.map +1 -1
  19. package/dist/mascot.d.ts +81 -30
  20. package/dist/mascot.js +232 -113
  21. package/dist/mascot.js.map +1 -1
  22. package/dist/net/certs.d.ts +14 -0
  23. package/dist/net/certs.js +30 -0
  24. package/dist/net/certs.js.map +1 -0
  25. package/dist/net/faults.d.ts +49 -0
  26. package/dist/net/faults.js +222 -0
  27. package/dist/net/faults.js.map +1 -0
  28. package/dist/net/forensics.d.ts +81 -0
  29. package/dist/net/forensics.js +181 -0
  30. package/dist/net/forensics.js.map +1 -0
  31. package/dist/net/gadget.d.ts +44 -0
  32. package/dist/net/gadget.js +102 -0
  33. package/dist/net/gadget.js.map +1 -0
  34. package/dist/net/index.d.ts +11 -0
  35. package/dist/net/index.js +28 -0
  36. package/dist/net/index.js.map +1 -0
  37. package/dist/net/launch-local.d.ts +53 -0
  38. package/dist/net/launch-local.js +190 -0
  39. package/dist/net/launch-local.js.map +1 -0
  40. package/dist/net/launch-support.d.ts +6 -0
  41. package/dist/net/launch-support.js +65 -0
  42. package/dist/net/launch-support.js.map +1 -0
  43. package/dist/net/mesh.d.ts +135 -0
  44. package/dist/net/mesh.js +491 -0
  45. package/dist/net/mesh.js.map +1 -0
  46. package/dist/net/offline-net.d.ts +58 -0
  47. package/dist/net/offline-net.js +485 -0
  48. package/dist/net/offline-net.js.map +1 -0
  49. package/dist/net/ot-net.d.ts +42 -0
  50. package/dist/net/ot-net.js +219 -0
  51. package/dist/net/ot-net.js.map +1 -0
  52. package/dist/net/pa.d.ts +65 -0
  53. package/dist/net/pa.js +118 -0
  54. package/dist/net/pa.js.map +1 -0
  55. package/dist/net/party-main.d.ts +48 -0
  56. package/dist/net/party-main.js +146 -0
  57. package/dist/net/party-main.js.map +1 -0
  58. package/dist/net/prep.d.ts +40 -0
  59. package/dist/net/prep.js +12 -0
  60. package/dist/net/prep.js.map +1 -0
  61. package/dist/net/protocol.d.ts +126 -0
  62. package/dist/net/protocol.js +655 -0
  63. package/dist/net/protocol.js.map +1 -0
  64. package/dist/net/wire.d.ts +89 -0
  65. package/dist/net/wire.js +242 -0
  66. package/dist/net/wire.js.map +1 -0
  67. package/dist/ot.d.ts +176 -17
  68. package/dist/ot.js +479 -252
  69. package/dist/ot.js.map +1 -1
  70. package/dist/otx.d.ts +76 -0
  71. package/dist/otx.js +238 -0
  72. package/dist/otx.js.map +1 -0
  73. package/dist/runner.d.ts +1 -1
  74. package/dist/runner.js +6 -1
  75. package/dist/runner.js.map +1 -1
  76. package/dist/sharing.d.ts +2 -0
  77. package/dist/sharing.js +7 -4
  78. package/dist/sharing.js.map +1 -1
  79. package/dist/spdz-runner.d.ts +20 -3
  80. package/dist/spdz-runner.js +21 -3
  81. package/dist/spdz-runner.js.map +1 -1
  82. package/dist/spdz.d.ts +96 -22
  83. package/dist/spdz.js +196 -47
  84. package/dist/spdz.js.map +1 -1
  85. package/package.json +15 -6
@@ -0,0 +1,655 @@
1
+ "use strict";
2
+ /**
3
+ * The DISTRIBUTED malicious-with-abort (SPDZ-style) composition protocol (research prototype).
4
+ *
5
+ * Same circuit and semantics as `composeSecureMalicious` (allow = AND, t = MAX, r = MAX via
6
+ * thermometers; authenticated Beaver multiplication; batched MAC-check before any output), but each
7
+ * party is its OWN process holding ONLY its own shares, and every opening is a real message over the
8
+ * TLS mesh (`mesh.ts`). Rounds (n parties, C = 1 + 3 + Q independent chains, k = MAC_KEYS global keys):
9
+ *
10
+ * prep dealer -> party : [TRUSTED-DEALER mode] key shares, sacrificed-triple material, input masks, pairwise tags
11
+ * (or, in NO-DEALER mode, the OT-based offline phase of `offline-net.ts` runs here instead)
12
+ * coin1-* all-to-all : commit/open of a random seed -> public challenges for the triple sacrifice
13
+ * sac-rho all-to-all : value shares of rho = e*a - a-hat for every triple and sacrifice partner
14
+ * sac-tau all-to-all : value shares of tau = e*c - c-hat - rho*b; every tau must be 0
15
+ * inputs all-to-all : [dealer mode] owner broadcasts x - r (r is the owner's dealer-given mask)
16
+ * in-check all-to-all : [no-dealer mode] opening of a masked random combination of the inputs
17
+ * mul0.. all-to-all : value shares of d = x-a, e = y-b for all C chains at once (n-1 rounds)
18
+ * out all-to-all : value shares of the C output bits
19
+ * <rd>.t all-to-all : [dealer mode] after every opening, the pairwise tags: each receiver verifies
20
+ * the sender's opened shares IMMEDIATELY and blames the sender if one fails
21
+ * coin-c/o all-to-all : commit/open of a random seed -> public MAC-check challenges
22
+ * sig-c/o all-to-all : commit/open of each party's MAC residual shares (one per key); sums must be 0
23
+ * digest all-to-all : hash of the whole broadcast transcript; any disagreement aborts
24
+ * blame all-to-all : [only after a failed MAC-check, dealer mode] every party opens its combined MAC
25
+ * shares and key shares with their pairwise tags; each verifier checks them and
26
+ * names the party whose data is inconsistent (or the dealer, if none is)
27
+ *
28
+ * No output is decoded until the MAC-check and the transcript echo pass. Everything else (timeouts,
29
+ * malformed/duplicate/out-of-order frames, disconnects, aborts) is handled by the mesh.
30
+ *
31
+ * OFFLINE MODES (the result names which one ran): `trusted-dealer` - a separate process that knows every
32
+ * share and key (it can break privacy and integrity if it colludes; but a dealer that gives a WRONG triple is
33
+ * detected by the sacrifice, and a dealer that deals inconsistent MACs is named by the blame round); and
34
+ * `no-dealer` - the parties derive the MAC keys, triples, authenticated inputs and pairwise tags themselves
35
+ * from OT.
36
+ *
37
+ * IDENTIFIABLE ABORT: in BOTH modes every share is pairwise-authenticated (`pa.ts`; dealt by the dealer, or
38
+ * generated by the parties from OT in `offline-net.ts`), so a party that lies about a share, a tag, a MAC share
39
+ * or a residual in the ONLINE phase is named by the honest verifiers with first-hand evidence. What stays
40
+ * unnamed: in no-dealer mode, a wrong OT operand in the offline triple products (the sacrifice detects the bad
41
+ * triple but its tags are consistent with it) - named only for n = 2, where the peer is the only possible
42
+ * culprit; and a verifier's accusation is evidence for the accuser only, never proof for a third party.
43
+ * Network-level misbehaviour (framing, commitments, timeouts) is attributed in both modes.
44
+ *
45
+ * HONEST LIMITS: there is no security proof for this implementation; the echo-broadcast is a hash exchange, not
46
+ * a Byzantine agreement protocol; the trusted dealer is trusted for privacy.
47
+ */
48
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
49
+ if (k2 === undefined) k2 = k;
50
+ var desc = Object.getOwnPropertyDescriptor(m, k);
51
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
52
+ desc = { enumerable: true, get: function() { return m[k]; } };
53
+ }
54
+ Object.defineProperty(o, k2, desc);
55
+ }) : (function(o, m, k, k2) {
56
+ if (k2 === undefined) k2 = k;
57
+ o[k2] = m[k];
58
+ }));
59
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
60
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
61
+ }) : function(o, v) {
62
+ o["default"] = v;
63
+ });
64
+ var __importStar = (this && this.__importStar) || (function () {
65
+ var ownKeys = function(o) {
66
+ ownKeys = Object.getOwnPropertyNames || function (o) {
67
+ var ar = [];
68
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
69
+ return ar;
70
+ };
71
+ return ownKeys(o);
72
+ };
73
+ return function (mod) {
74
+ if (mod && mod.__esModule) return mod;
75
+ var result = {};
76
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
77
+ __setModuleDefault(result, mod);
78
+ return result;
79
+ };
80
+ })();
81
+ Object.defineProperty(exports, "__esModule", { value: true });
82
+ exports.chainCount = exports.SilentParty = exports.T_M = void 0;
83
+ exports.runDealer = runDealer;
84
+ exports.runParty = runParty;
85
+ const node_crypto_1 = require("node:crypto");
86
+ const field_1 = require("../field");
87
+ const csprng_1 = require("../csprng");
88
+ const spdz_1 = require("../spdz");
89
+ const thermometer_1 = require("../thermometer");
90
+ const wire_1 = require("./wire");
91
+ const forensics_1 = require("./forensics");
92
+ const pa_1 = require("./pa");
93
+ const prep_1 = require("./prep");
94
+ exports.T_M = 3;
95
+ /** Thrown by the test-only `silent` behaviour; the host process then idles with its sockets open. */
96
+ class SilentParty extends Error {
97
+ constructor() {
98
+ super('silent');
99
+ this.name = 'SilentParty';
100
+ }
101
+ }
102
+ exports.SilentParty = SilentParty;
103
+ const DEFAULT_Q = 100;
104
+ /** Number of independent circuit chains (= output bits = input bits per owner). */
105
+ const chainCount = (Q) => 1 + exports.T_M + Q;
106
+ exports.chainCount = chainCount;
107
+ /** Shared helper: canonical numbers per party in a dealer `prep` frame. */
108
+ function prepLayout(n, C, keys, partners, tagged) {
109
+ const aw = (0, pa_1.awWidth)(n, keys, tagged);
110
+ const triples = (n - 1) * C;
111
+ const inputs = n * C;
112
+ const nums = (tagged ? 1 : 0) + keys * (0, pa_1.paWidth)(n, tagged) + triples * (0, prep_1.sharingsPerTriple)(partners) * aw + inputs * aw + C;
113
+ return { nums, triples, inputs };
114
+ }
115
+ /** Hex numbers per `prep` frame (keeps every frame far below the frame cap). */
116
+ const PREP_CHUNK = 30_000;
117
+ /**
118
+ * The TRUSTED DEALER role: samples the MAC keys, the verification keys, sacrifice-certified authenticated
119
+ * triples and authenticated input masks, and ships each party only its own slice. It never sees any party's
120
+ * input bit (inputs are masked by r and the masked value is broadcast only among parties), but it knows every
121
+ * key and share, so it can break privacy/integrity if it colludes with parties. That is the stated trust
122
+ * assumption of this mode. A dealer that deals a wrong triple is caught by the parties' sacrifice check.
123
+ */
124
+ async function runDealer(cfg) {
125
+ const { n, mesh } = cfg;
126
+ const Q = cfg.Q ?? DEFAULT_Q;
127
+ const rng = cfg.seed !== undefined ? new field_1.FieldRng(cfg.seed) : new csprng_1.SecureFieldRng();
128
+ const C = (0, exports.chainCount)(Q);
129
+ const ctx = (0, spdz_1.setupMac)(n, rng, spdz_1.MAC_KEYS);
130
+ const keys = (0, spdz_1.macKeys)(ctx);
131
+ const deltas = Array.from({ length: n }, () => {
132
+ let d = rng.next();
133
+ while (d === 0n)
134
+ d = rng.next();
135
+ return d;
136
+ });
137
+ /** Deal the pairwise tags/keys for one sharing x_0..x_{n-1}: T_ij = K_ij + Δ_j·x_i. */
138
+ const dealPA = (x) => {
139
+ const K = x.map((_, i) => x.map((__, j) => (i === j ? 0n : rng.next())));
140
+ return x.map((xi, p) => ({
141
+ x: xi,
142
+ tag: x.map((_, j) => (j === p ? 0n : (0, field_1.fadd)(K[p][j], (0, field_1.fmul)(deltas[j], xi)))),
143
+ key: x.map((_, j) => (j === p ? 0n : K[j][p])),
144
+ }));
145
+ };
146
+ const dealAW = (sv) => {
147
+ const comps = [sv.value, sv.mac, ...sv.extraMac].map(dealPA);
148
+ return Array.from({ length: n }, (_, p) => ({ v: comps[0][p], m: comps.slice(1).map((c) => c[p]) }));
149
+ };
150
+ const perParty = Array.from({ length: n }, (_, p) => [deltas[p]]);
151
+ const alphaPA = keys.map((k) => dealPA(k.alphaShares));
152
+ for (let p = 0; p < n; p++)
153
+ for (const a of alphaPA)
154
+ perParty[p].push(...(0, pa_1.paFlat)(a[p], p, n, true));
155
+ const nTriples = (n - 1) * C;
156
+ for (let t = 0; t < nTriples; t++) {
157
+ const tr = (0, spdz_1.genTripleWithPartners)(ctx, rng, spdz_1.SACRIFICE_PARTNERS, cfg.fault);
158
+ const sharings = [tr.keep.a, tr.keep.b, tr.keep.c, ...tr.partners.flatMap((q) => [q.a, q.c])];
159
+ for (const sv of sharings) {
160
+ const aws = dealAW(sv);
161
+ for (let p = 0; p < n; p++)
162
+ perParty[p].push(...(0, pa_1.awFlat)(aws[p], p, n, true));
163
+ }
164
+ }
165
+ const masksByOwner = Array.from({ length: n }, () => []);
166
+ for (let q = 0; q < n * C; q++) {
167
+ const owner = Math.floor(q / C);
168
+ const r = rng.next();
169
+ masksByOwner[owner].push(r);
170
+ const sv = (0, spdz_1.authShare)(r, ctx, rng);
171
+ if (cfg.macFaultAtInput === q)
172
+ sv.mac[0] = (0, field_1.fadd)(sv.mac[0], 1n);
173
+ const aws = dealAW(sv);
174
+ for (let p = 0; p < n; p++)
175
+ perParty[p].push(...(0, pa_1.awFlat)(aws[p], p, n, true));
176
+ }
177
+ for (let p = 0; p < n; p++)
178
+ perParty[p].push(...masksByOwner[p]);
179
+ for (let p = 0; p < n; p++) {
180
+ const nums = perParty[p].map(wire_1.hx);
181
+ const chunks = Math.max(1, Math.ceil(nums.length / PREP_CHUNK));
182
+ mesh.send(p, 'prep', { total: nums.length, chunks, nums: nums.slice(0, PREP_CHUNK) });
183
+ for (let c = 1; c < chunks; c++)
184
+ mesh.send(p, `prep.${c}`, nums.slice(c * PREP_CHUNK, (c + 1) * PREP_CHUNK));
185
+ }
186
+ await mesh.finish();
187
+ }
188
+ const sha = (...parts) => {
189
+ const h = (0, node_crypto_1.createHash)('sha256');
190
+ for (const p of parts) {
191
+ h.update(typeof p === 'string' ? Buffer.from(p) : p);
192
+ h.update(Buffer.from([0]));
193
+ }
194
+ return h.digest();
195
+ };
196
+ /** Uniform non-zero field element `j` of the public challenge stream `label` derived from `seed` (rejection on p). */
197
+ function challenge(seed, label, j) {
198
+ for (let ctr = 0;; ctr++) {
199
+ const h = sha('chal', seed, `${label}:${j}:${ctr}`);
200
+ const x = h.readBigUInt64BE(0) & ((1n << 61n) - 1n);
201
+ if (x < field_1.PRIME && x !== 0n)
202
+ return x;
203
+ }
204
+ }
205
+ async function runParty(cfg) {
206
+ const { id, n, mesh, sessionId } = cfg;
207
+ const Q = cfg.Q ?? DEFAULT_Q;
208
+ const dealerId = cfg.dealerId ?? n;
209
+ const offlineMode = cfg.offline ?? 'trusted-dealer';
210
+ const C = (0, exports.chainCount)(Q);
211
+ const K = spdz_1.MAC_KEYS;
212
+ const P = spdz_1.SACRIFICE_PARTNERS;
213
+ if (cfg.behavior && cfg.unsafeTestHooks !== true) {
214
+ throw new wire_1.NetAbort('local', null, id, 'the behavior test hook requires unsafeTestHooks: true (test-only)');
215
+ }
216
+ const beh = cfg.behavior;
217
+ const t0 = performance.now();
218
+ const rounds = [];
219
+ const transcript = (0, node_crypto_1.createHash)('sha256');
220
+ const others = [];
221
+ for (let i = 0; i < n; i++)
222
+ if (i !== id)
223
+ others.push(i);
224
+ if (!Number.isInteger(n) || n < 1)
225
+ throw new wire_1.NetAbort('local', null, id, 'n must be >= 1');
226
+ const v = cfg.input;
227
+ if (![0, 1].includes(v.allow) || ![1, 2, 3].includes(v.t) || !Number.isInteger(v.rQuant) || v.rQuant < 0 || v.rQuant > Q) {
228
+ throw new wire_1.NetAbort('local', null, id, 'invalid decision vector');
229
+ }
230
+ const totals = () => {
231
+ let o = 0;
232
+ let i = 0;
233
+ for (const s of mesh.stats()) {
234
+ o += s.appBytesOut;
235
+ i += s.appBytesIn;
236
+ }
237
+ return { out: o, in: i };
238
+ };
239
+ const crashIf = (rd) => {
240
+ if (beh?.kind === 'crash' && beh.round === rd)
241
+ process.exit(7);
242
+ if (beh?.kind === 'silent' && beh.round === rd)
243
+ throw new SilentParty();
244
+ };
245
+ /**
246
+ * One all-to-all round among the parties: send `mine(peer)` to each peer, collect + validate theirs.
247
+ * `hashed = false` keeps a round (the peer-specific pairwise tags) out of the echoed transcript.
248
+ */
249
+ async function exchange(rd, mine, parse, common, hashed = true, timeoutMs) {
250
+ crashIf(rd);
251
+ const a = totals();
252
+ const s = performance.now();
253
+ for (const p of others)
254
+ mesh.send(p, rd, mine(p));
255
+ const raw = await mesh.collect(rd, others, timeoutMs);
256
+ const parsed = new Map();
257
+ for (const p of others) {
258
+ try {
259
+ parsed.set(p, parse(p, raw.get(p)));
260
+ }
261
+ catch (e) {
262
+ if (e instanceof wire_1.NetAbort) {
263
+ mesh.fail(e);
264
+ throw e;
265
+ }
266
+ const ne = new wire_1.NetAbort('malformed', p, id, `round '${rd}': ${e.message}`);
267
+ mesh.fail(ne);
268
+ throw ne;
269
+ }
270
+ }
271
+ if (hashed) {
272
+ // transcript: party order, canonical JSON of what each party broadcast in this round
273
+ transcript.update(rd);
274
+ for (let p = 0; p < n; p++) {
275
+ transcript.update(`|${p}:`);
276
+ transcript.update(JSON.stringify(p === id ? common : raw.get(p)));
277
+ }
278
+ }
279
+ const b = totals();
280
+ rounds.push({ rd, ms: performance.now() - s, bytesOut: b.out - a.out, bytesIn: b.in - a.in });
281
+ return parsed;
282
+ }
283
+ const bad = (p, what) => {
284
+ throw new wire_1.NetAbort('malformed', p, id, what);
285
+ };
286
+ const arr = (p, raw, len) => (0, wire_1.unhxArray)(raw, len) ?? bad(p, `expected ${len} canonical field elements`);
287
+ const abort = (e) => {
288
+ mesh.fail(e);
289
+ throw e;
290
+ };
291
+ // ---- offline phase: prep from the trusted dealer, or the OT-based no-dealer phase ----
292
+ let prep;
293
+ crashIf('prep');
294
+ const prepStart = performance.now();
295
+ const before = totals();
296
+ if (offlineMode === 'trusted-dealer') {
297
+ prep = await receiveDealerPrep();
298
+ }
299
+ else {
300
+ const { runOfflineNet } = await Promise.resolve().then(() => __importStar(require('./offline-net')));
301
+ prep = await runOfflineNet({ mesh, id, n, C, keys: K, partners: P, sessionId, input: v, Q, behavior: beh, exchange: (rd, mine, parse) => exchange(rd, mine, parse, null, false), crashIf, bad, arr });
302
+ }
303
+ const afterPrep = totals();
304
+ rounds.push({ rd: 'prep', ms: performance.now() - prepStart, bytesOut: afterPrep.out - before.out, bytesIn: afterPrep.in - before.in });
305
+ async function receiveDealerPrep() {
306
+ const lay = prepLayout(n, C, K, P, true);
307
+ const first = (await mesh.recv(dealerId, 'prep'));
308
+ let nums = [];
309
+ try {
310
+ if (typeof first?.total !== 'number' || first.total !== lay.nums)
311
+ bad(dealerId, 'bad prep size');
312
+ if (typeof first?.chunks !== 'number' || first.chunks !== Math.max(1, Math.ceil(lay.nums / PREP_CHUNK)))
313
+ bad(dealerId, 'bad prep chunk count');
314
+ const take = (raw, len) => (0, wire_1.unhxArray)(raw, len) ?? bad(dealerId, 'bad prep numbers');
315
+ nums = take(first?.nums, Math.min(PREP_CHUNK, lay.nums));
316
+ for (let c = 1; c < first?.chunks; c++) {
317
+ const part = (await mesh.recv(dealerId, `prep.${c}`));
318
+ nums = nums.concat(take(part, Math.min(PREP_CHUNK, lay.nums - c * PREP_CHUNK)));
319
+ }
320
+ }
321
+ catch (e) {
322
+ const ne = e instanceof wire_1.NetAbort ? e : new wire_1.NetAbort('malformed', dealerId, id, e.message);
323
+ return abort(ne);
324
+ }
325
+ let o = 0;
326
+ const delta = nums[o++];
327
+ if (delta === 0n)
328
+ return abort(new wire_1.NetAbort('dealer', dealerId, id, 'the dealer gave me a zero verification key'));
329
+ const w = (0, pa_1.paWidth)(n, true);
330
+ const alpha = [];
331
+ for (let l = 0; l < K; l++) {
332
+ alpha.push((0, pa_1.paParse)(nums, o, id, n, true));
333
+ o += w;
334
+ }
335
+ const aw = (0, pa_1.awWidth)(n, K, true);
336
+ const triples = [];
337
+ for (let t = 0; t < lay.triples; t++) {
338
+ const sh = [];
339
+ for (let s = 0; s < (0, prep_1.sharingsPerTriple)(P); s++) {
340
+ sh.push((0, pa_1.awParse)(nums, o, id, n, K, true));
341
+ o += aw;
342
+ }
343
+ triples.push(sh);
344
+ }
345
+ const inputs = [];
346
+ for (let q = 0; q < lay.inputs; q++) {
347
+ inputs.push((0, pa_1.awParse)(nums, o, id, n, K, true));
348
+ o += aw;
349
+ }
350
+ const myPlain = nums.slice(o, o + C);
351
+ return { mode: 'trusted-dealer', tagged: true, delta, alpha, triples, inputs, myPlain };
352
+ }
353
+ const env = { id, n, delta: prep.delta, tagged: prep.tagged };
354
+ const addPub = (x, c) => (0, pa_1.wAddPublic)(env, x, c, prep.alpha);
355
+ const not = (x) => addPub((0, pa_1.wNeg)(x), 1n);
356
+ // ---- retained for the MAC-check / blame: every opened value, with my MAC shares at that opening ----
357
+ const opened = [];
358
+ /** Open authenticated values: broadcast value shares, verify pairwise tags (dealer mode), record for the MAC-check. */
359
+ async function openMany(rd, ws) {
360
+ let sendVals = ws.map((x) => x.v.x);
361
+ const macs = ws.map((x) => x.m);
362
+ if (beh?.round === rd) {
363
+ if (beh.kind === 'open-lie')
364
+ sendVals = sendVals.map((x, i) => (i === 0 ? (0, field_1.fadd)(x, 1n) : x));
365
+ if (beh.kind === 'mac-lie')
366
+ macs[0] = macs[0].map((pa, l) => (l === 0 ? { ...pa, x: (0, field_1.fadd)(pa.x, 1n) } : pa));
367
+ }
368
+ const common = sendVals.map(wire_1.hx);
369
+ const mineFn = (peer) => {
370
+ if (beh?.kind === 'equivocate' && beh.round === rd && peer === others[0]) {
371
+ return common.map((s, i) => (i === 0 ? (0, wire_1.hx)((0, field_1.fadd)(BigInt('0x' + s), 1n)) : s));
372
+ }
373
+ if (beh?.kind === 'short-array' && beh.round === rd)
374
+ return common.slice(1);
375
+ if (beh?.kind === 'noncanonical' && beh.round === rd)
376
+ return common.map((s, i) => (i === 0 ? (0, wire_1.hx)(field_1.PRIME) : s));
377
+ return common;
378
+ };
379
+ const got = await exchange(rd, mineFn, (p, raw) => arr(p, raw, ws.length), common);
380
+ if (prep.tagged) {
381
+ // pairwise tags: peer-specific, so a separate un-echoed round; each receiver verifies at once
382
+ const tagMsg = (peer) => ws.map((x, i) => (0, wire_1.hx)(beh?.kind === 'bad-tag' && beh.round === rd && i === 0 ? (0, field_1.fadd)(x.v.tag[peer], 1n) : x.v.tag[peer]));
383
+ const tags = await exchange(`${rd}.t`, tagMsg, (p, raw) => arr(p, raw, ws.length), null, false);
384
+ for (const p of others) {
385
+ for (let i = 0; i < ws.length; i++) {
386
+ if (!(0, pa_1.paVerify)(env, ws[i].v, p, got.get(p)[i], tags.get(p)[i])) {
387
+ return abort(new wire_1.NetAbort('mac-check', p, id, `pairwise MAC on party ${p}'s opened share failed in round '${rd}' (index ${i}): the share or its tag is not what the dealer issued`));
388
+ }
389
+ }
390
+ }
391
+ }
392
+ const out = [];
393
+ for (let i = 0; i < ws.length; i++) {
394
+ let sum = sendVals[i];
395
+ for (const p of others)
396
+ sum = (0, field_1.fadd)(sum, got.get(p)[i]);
397
+ out.push(sum);
398
+ opened.push({ val: sum, mac: macs[i] });
399
+ }
400
+ return out;
401
+ }
402
+ // ---- coin flip (commit / open of a random seed) -> a joint public seed ----
403
+ async function coin(label, tamperOpen) {
404
+ const mySeed = (0, node_crypto_1.randomBytes)(32);
405
+ const seedCommit = sha('seed', sessionId, label, String(id), mySeed).toString('hex');
406
+ const seedCommits = await exchange(`${label}-c`, () => seedCommit, (p, raw) => (typeof raw === 'string' && /^[0-9a-f]{64}$/.test(raw) ? raw : bad(p, 'bad commitment')), seedCommit);
407
+ const openSeedMsg = () => (tamperOpen && beh?.kind === 'bad-coin-open' ? (0, node_crypto_1.randomBytes)(32).toString('hex') : mySeed.toString('hex'));
408
+ const seeds = await exchange(`${label}-o`, openSeedMsg, (p, raw) => {
409
+ if (typeof raw !== 'string' || !/^[0-9a-f]{64}$/.test(raw))
410
+ return bad(p, 'bad seed');
411
+ const sd = Buffer.from(raw, 'hex');
412
+ if (sha('seed', sessionId, label, String(p), sd).toString('hex') !== seedCommits.get(p)) {
413
+ throw new wire_1.NetAbort('commitment', p, id, `party ${p} opened a coin seed that does not match its commitment`);
414
+ }
415
+ return sd;
416
+ }, mySeed.toString('hex'));
417
+ return sha('joint', sessionId, label, ...Array.from({ length: n }, (_, p) => (p === id ? mySeed : seeds.get(p))));
418
+ }
419
+ // ---- sacrifice: certify every triple against its partners before it is used ----
420
+ const nT = prep.triples.length;
421
+ if (nT > 0) {
422
+ const joint1 = await coin('coin1', false);
423
+ const rhoW = [];
424
+ const idx = (t, r) => t * P + r;
425
+ for (let t = 0; t < nT; t++) {
426
+ const [a, , , ...parts] = prep.triples[t];
427
+ for (let r = 0; r < P; r++) {
428
+ const e = challenge(joint1, 'sac', idx(t, r));
429
+ rhoW.push((0, pa_1.wSub)((0, pa_1.wScale)(a, e), parts[2 * r]));
430
+ }
431
+ }
432
+ const rho = await openMany('sac-rho', rhoW);
433
+ const tauW = [];
434
+ for (let t = 0; t < nT; t++) {
435
+ const [, b, c, ...parts] = prep.triples[t];
436
+ for (let r = 0; r < P; r++) {
437
+ const e = challenge(joint1, 'sac', idx(t, r));
438
+ tauW.push((0, pa_1.wSub)((0, pa_1.wSub)((0, pa_1.wScale)(c, e), parts[2 * r + 1]), (0, pa_1.wScale)(b, rho[idx(t, r)])));
439
+ }
440
+ }
441
+ const tau = await openMany('sac-tau', tauW);
442
+ const badAt = tau.findIndex((x) => x !== 0n);
443
+ if (badAt >= 0) {
444
+ // Dealer-free mode: the triple is discarded, so its OT transcript can be disclosed and audited to name a party
445
+ // that fed a wrong operand into a triple product (forensics.ts). Every honest party reaches this point together.
446
+ let attributed = null;
447
+ let note = '';
448
+ const fx = prep.mode === 'no-dealer' ? prep.forensics : undefined;
449
+ if (fx) {
450
+ const bt = Math.floor(badAt / P);
451
+ const mine = fx.disclose(bt);
452
+ // R3-17: commit first, open only once every commitment is in hand (forensics.ts); a late or absent party times out at the shared deadline.
453
+ const got = await (0, forensics_1.exchangeDisclosures)({
454
+ sessionId,
455
+ id,
456
+ n,
457
+ tripleIndex: bt,
458
+ mine,
459
+ slots: fx.slots,
460
+ rows: fx.rows,
461
+ deadlineMs: cfg.forensicDeadlineMs ?? forensics_1.DEFAULT_FORENSIC_DEADLINE_MS,
462
+ exchange: (rd, m, parse, t) => exchange(rd, m, parse, null, false, t),
463
+ bad: (p, what) => bad(p, what),
464
+ });
465
+ const all = new Map([[id, mine]]);
466
+ for (const [p, d] of got)
467
+ all.set(p, d);
468
+ const res = (0, forensics_1.auditDisclosures)(all, fx.gadget);
469
+ attributed = (0, forensics_1.blameFrom)(id, res);
470
+ note =
471
+ attributed === null
472
+ ? ` (OT transcript audit: ${res.pairBad.length} inconsistent pair(s), no party can be named from this view: a pair failure between two other parties is not evidence against either of them to me)`
473
+ : ` (OT transcript audit names party ${attributed})`;
474
+ }
475
+ // every honest tag already verified, so the opened tau are the true ones: the dealt triple is wrong.
476
+ return abort(new wire_1.NetAbort(prep.mode === 'trusted-dealer' ? 'dealer' : 'mac-check', prep.mode === 'trusted-dealer' ? dealerId : (attributed ?? (n === 2 ? 1 - id : null)), id, `triple sacrifice FAILED at triple ${Math.floor(badAt / P)} (partner ${badAt % P}): c != a*b in the offline material; aborting with no output${note}`, undefined, undefined, { reason: 'the triple sacrifice failed in dealer-free mode and the OT transcript audit of this party\'s own view names no one (a failing pair that does not involve me is not evidence I can use, and an accusation is never accepted against a third party)' }));
477
+ }
478
+ }
479
+ // ---- inputs: bit k of owner o is the C-vector [allow, t-therm(3), r-therm(Q)] ----
480
+ const myBits = [v.allow, ...(0, thermometer_1.thermometerEncode)(v.t, exports.T_M), ...(0, thermometer_1.thermometerEncode)(v.rQuant, Q)];
481
+ let bitShare;
482
+ if (prep.mode === 'trusted-dealer') {
483
+ const masked = myBits.map((b, k) => (0, field_1.fsub)(BigInt(b), prep.myPlain[k]));
484
+ const maskedMsg = masked.map(wire_1.hx);
485
+ const gotMasked = await exchange('inputs', () => maskedMsg, (p, raw) => arr(p, raw, C), maskedMsg);
486
+ bitShare = (owner, k) => {
487
+ const pub = owner === id ? masked[k] : gotMasked.get(owner)[k];
488
+ return addPub(prep.inputs[owner * C + k], pub);
489
+ };
490
+ }
491
+ else {
492
+ // No-dealer: the inputs are already authenticated sharings of the bits. Run the MASCOT input-consistency
493
+ // check: open a random masked combination (the opening joins the batched MAC-check below).
494
+ bitShare = (owner, k) => prep.inputs[owner * C + k];
495
+ const jointIn = await coin('coin-in', false);
496
+ let w = prep.checkMask;
497
+ for (let q = 0; q < n * C; q++)
498
+ w = (0, pa_1.wAdd)(w, (0, pa_1.wScale)(prep.inputs[q], challenge(jointIn, 'in', q)));
499
+ await openMany('in-check', [w]);
500
+ }
501
+ // ---- chains: acc_c over owners; allow uses AND, others OR(=NOT AND NOT) ----
502
+ const isOr = (c) => c !== 0;
503
+ let acc = [];
504
+ for (let c = 0; c < C; c++) {
505
+ const b0 = bitShare(0, c);
506
+ acc.push(isOr(c) ? not(b0) : b0);
507
+ }
508
+ let mults = 0;
509
+ for (let s = 1; s < n; s++) {
510
+ const trs = Array.from({ length: C }, (_, c) => prep.triples[(s - 1) * C + c]);
511
+ const ys = Array.from({ length: C }, (_, c) => {
512
+ const b = bitShare(s, c);
513
+ return isOr(c) ? not(b) : b;
514
+ });
515
+ const ds = acc.map((x, c) => (0, pa_1.wSub)(x, trs[c][0]));
516
+ const es = ys.map((y, c) => (0, pa_1.wSub)(y, trs[c][1]));
517
+ const pub = await openMany(`mul${s - 1}`, [...ds, ...es]);
518
+ acc = acc.map((_, c) => {
519
+ const d = pub[c];
520
+ const e = pub[C + c];
521
+ const [ta, tb, tc] = trs[c];
522
+ let z = (0, pa_1.wAdd)(tc, (0, pa_1.wAdd)((0, pa_1.wScale)(tb, d), (0, pa_1.wScale)(ta, e)));
523
+ z = addPub(z, (0, field_1.fmul)(d, e));
524
+ return z;
525
+ });
526
+ mults += C;
527
+ }
528
+ // ---- outputs: allow as-is; or-chains are NOT of the product ----
529
+ const outShares = acc.map((x, c) => (isOr(c) ? not(x) : x));
530
+ const outVals = await openMany('out', outShares);
531
+ // ---- MAC-check: coin-flip for the challenges, one independent challenge vector per key ----
532
+ const joint = await coin('coin', true);
533
+ const chal = (l, j) => challenge(joint, `mac${l}`, j);
534
+ const aPub = [];
535
+ for (let l = 0; l < K; l++) {
536
+ let a = 0n;
537
+ opened.forEach((o, j) => {
538
+ a = (0, field_1.fadd)(a, (0, field_1.fmul)(chal(l, j), o.val));
539
+ });
540
+ aPub.push(a);
541
+ }
542
+ const gammaOf = (l) => {
543
+ let g = 0n;
544
+ opened.forEach((o, j) => {
545
+ g = (0, field_1.fadd)(g, (0, field_1.fmul)(chal(l, j), o.mac[l].x));
546
+ });
547
+ return g;
548
+ };
549
+ const Gamma = Array.from({ length: K }, (_, l) => gammaOf(l));
550
+ const sigma = Gamma.map((g, l) => (0, field_1.fsub)(g, (0, field_1.fmul)(prep.alpha[l].x, aPub[l])));
551
+ const sigNonce = (0, node_crypto_1.randomBytes)(32);
552
+ const sigCommit = (p, s, nonce) => sha('sigma', sessionId, String(p), s.map(wire_1.hx).join(','), nonce).toString('hex');
553
+ const sigmaSent = beh?.kind === 'sigma-lie' ? sigma.map((s, l) => (l === 0 ? (0, field_1.fadd)(s, 1n) : s)) : sigma;
554
+ const mySigCommit = sigCommit(id, sigmaSent, sigNonce);
555
+ const sigCommits = await exchange('sig-c', () => mySigCommit, (p, raw) => (typeof raw === 'string' && /^[0-9a-f]{64}$/.test(raw) ? raw : bad(p, 'bad commitment')), mySigCommit);
556
+ const mySigOpen = { s: sigmaSent.map(wire_1.hx), nonce: sigNonce.toString('hex') };
557
+ const sigOpens = await exchange('sig-o', () => (beh?.kind === 'bad-sigma-open' ? { s: sigmaSent.map((s, l) => (0, wire_1.hx)(l === 0 ? (0, field_1.fadd)(s, 1n) : s)), nonce: mySigOpen.nonce } : mySigOpen), (p, raw) => {
558
+ const o = raw;
559
+ const sv = (0, wire_1.unhxArray)(o?.s, K);
560
+ if (sv === null || typeof o?.nonce !== 'string' || !/^[0-9a-f]{64}$/.test(o.nonce))
561
+ return bad(p, 'bad sigma opening');
562
+ if (sigCommit(p, sv, Buffer.from(o.nonce, 'hex')) !== sigCommits.get(p)) {
563
+ throw new wire_1.NetAbort('commitment', p, id, `party ${p} opened a MAC residual that does not match its commitment`);
564
+ }
565
+ return sv;
566
+ }, mySigOpen);
567
+ const residual = Array.from({ length: K }, (_, l) => {
568
+ let r = sigmaSent[l];
569
+ for (const p of others)
570
+ r = (0, field_1.fadd)(r, sigOpens.get(p)[l]);
571
+ return r;
572
+ });
573
+ const macFailed = residual.some((r) => r !== 0n);
574
+ // ---- transcript echo: everyone must have seen the same broadcasts (also fixes the sigma data for the blame) ----
575
+ const digest = transcript.copy().digest('hex');
576
+ const digests = await exchange('digest', () => digest, (p, raw) => (typeof raw === 'string' && /^[0-9a-f]{64}$/.test(raw) ? raw : bad(p, 'bad digest')), digest);
577
+ for (const p of others) {
578
+ if (digests.get(p) !== digest) {
579
+ return abort(new wire_1.NetAbort('equivocation', p, id, `transcript digest of party ${p} differs from mine (a broadcast was inconsistent)`));
580
+ }
581
+ }
582
+ if (macFailed) {
583
+ const msg = 'MAC-check FAILED: an opened value or MAC share was inconsistent; aborting with no output';
584
+ if (!prep.tagged) {
585
+ // No pairwise tags in this mode: the evidence cannot name a party, except in the two-party case.
586
+ return abort(new wire_1.NetAbort('mac-check', n === 2 ? 1 - id : null, id, msg, undefined, undefined, { reason: 'this mode carries no pairwise tags, so a MAC-check failure cannot name a party (only possible with two parties)' }));
587
+ }
588
+ // ---- blame: every party opens its combined MAC shares with their pairwise tags; each verifier checks them ----
589
+ const combo = (l, pick) => {
590
+ let g = 0n;
591
+ opened.forEach((o, j) => {
592
+ g = (0, field_1.fadd)(g, (0, field_1.fmul)(chal(l, j), pick(o)));
593
+ });
594
+ return g;
595
+ };
596
+ const blameMsg = (peer) => ({
597
+ g: Gamma.map(wire_1.hx),
598
+ gt: Array.from({ length: K }, (_, l) => (0, wire_1.hx)(combo(l, (o) => o.mac[l].tag[peer]))),
599
+ a: prep.alpha.map((a) => (0, wire_1.hx)(a.x)),
600
+ at: prep.alpha.map((a) => (0, wire_1.hx)(a.tag[peer])),
601
+ });
602
+ const blames = await exchange('blame', blameMsg, (p, raw) => {
603
+ const o = raw;
604
+ return { g: arr(p, o?.g, K), gt: arr(p, o?.gt, K), a: arr(p, o?.a, K), at: arr(p, o?.at, K) };
605
+ }, null, false);
606
+ for (const p of others) {
607
+ const b = blames.get(p);
608
+ for (let l = 0; l < K; l++) {
609
+ const keyCombo = combo(l, (o) => o.mac[l].key[p]);
610
+ if ((0, field_1.fadd)(keyCombo, (0, field_1.fmul)(env.delta, b.g[l])) !== b.gt[l]) {
611
+ return abort(new wire_1.NetAbort('mac-check', p, id, `${msg} - party ${p}'s MAC share (key ${l}) does not match its pairwise tag`));
612
+ }
613
+ if ((0, field_1.fadd)(prep.alpha[l].key[p], (0, field_1.fmul)(env.delta, b.a[l])) !== b.at[l]) {
614
+ return abort(new wire_1.NetAbort('mac-check', p, id, `${msg} - party ${p}'s key share (key ${l}) does not match its pairwise tag`));
615
+ }
616
+ if ((0, field_1.fsub)(b.g[l], (0, field_1.fmul)(b.a[l], aPub[l])) !== sigOpens.get(p)[l]) {
617
+ return abort(new wire_1.NetAbort('mac-check', p, id, `${msg} - party ${p}'s opened MAC residual (key ${l}) is not what its authenticated shares give`));
618
+ }
619
+ }
620
+ }
621
+ // Every other party's data is consistent with its pairwise tags; so is mine: the offline material disagrees.
622
+ if (prep.mode === 'trusted-dealer') {
623
+ return abort(new wire_1.NetAbort('dealer', dealerId, id, `${msg} - every party's shares verify, so the trusted dealer's MACs are inconsistent with its values`));
624
+ }
625
+ return abort(new wire_1.NetAbort('mac-check', n === 2 ? 1 - id : null, id, `${msg} - every party's shares verify under its pairwise tags, so the offline material is inconsistent (a wrong OT operand): not attributable`, undefined, undefined, { reason: 'every party\'s shares verify under its pairwise tags, so the failure is in the offline OT material and no party can be named' }));
626
+ }
627
+ // ---- only now decode ----
628
+ const bit = (x) => (x === 1n ? 1 : 0);
629
+ const allow = bit(outVals[0]);
630
+ const tBits = outVals.slice(1, 1 + exports.T_M).map(bit);
631
+ const rBits = outVals.slice(1 + exports.T_M).map(bit);
632
+ const composed = {
633
+ allow,
634
+ t: Math.min(3, Math.max(1, (0, thermometer_1.thermometerDecode)(tBits))),
635
+ rQuant: (0, thermometer_1.thermometerDecode)(rBits),
636
+ };
637
+ await mesh.finish();
638
+ return {
639
+ id,
640
+ n,
641
+ Q,
642
+ composed,
643
+ rounds,
644
+ transcriptDigest: digest,
645
+ wallMs: performance.now() - t0,
646
+ multiplications: mults,
647
+ opens: opened.length,
648
+ offlineMode: prep.mode,
649
+ macKeys: K,
650
+ sacrificedTriples: nT,
651
+ sacrificePartners: P,
652
+ identifiableAbort: prep.tagged,
653
+ };
654
+ }
655
+ //# sourceMappingURL=protocol.js.map