@atlasauth/pca-mpc 0.0.0-stage → 0.2.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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -2
  3. package/dist/beaver.d.ts +49 -0
  4. package/dist/beaver.js +72 -0
  5. package/dist/beaver.js.map +1 -0
  6. package/dist/compose.d.ts +21 -0
  7. package/dist/compose.js +33 -0
  8. package/dist/compose.js.map +1 -0
  9. package/dist/csprng.d.ts +46 -0
  10. package/dist/csprng.js +86 -0
  11. package/dist/csprng.js.map +1 -0
  12. package/dist/ec.d.ts +130 -0
  13. package/dist/ec.js +475 -0
  14. package/dist/ec.js.map +1 -0
  15. package/dist/endemic-ot.d.ts +209 -0
  16. package/dist/endemic-ot.js +405 -0
  17. package/dist/endemic-ot.js.map +1 -0
  18. package/dist/field.d.ts +34 -0
  19. package/dist/field.js +65 -0
  20. package/dist/field.js.map +1 -0
  21. package/dist/gf128.d.ts +23 -0
  22. package/dist/gf128.js +59 -0
  23. package/dist/gf128.js.map +1 -0
  24. package/dist/hybrid-ot.d.ts +153 -0
  25. package/dist/hybrid-ot.js +241 -0
  26. package/dist/hybrid-ot.js.map +1 -0
  27. package/dist/index.d.ts +29 -0
  28. package/dist/index.js +46 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/kem-ot.d.ts +292 -0
  31. package/dist/kem-ot.js +559 -0
  32. package/dist/kem-ot.js.map +1 -0
  33. package/dist/mascot.d.ts +189 -0
  34. package/dist/mascot.js +322 -0
  35. package/dist/mascot.js.map +1 -0
  36. package/dist/ot.d.ts +212 -0
  37. package/dist/ot.js +536 -0
  38. package/dist/ot.js.map +1 -0
  39. package/dist/party.d.ts +38 -0
  40. package/dist/party.js +40 -0
  41. package/dist/party.js.map +1 -0
  42. package/dist/runner.d.ts +56 -0
  43. package/dist/runner.js +97 -0
  44. package/dist/runner.js.map +1 -0
  45. package/dist/sharing.d.ts +40 -0
  46. package/dist/sharing.js +97 -0
  47. package/dist/sharing.js.map +1 -0
  48. package/dist/spdz-runner.d.ts +50 -0
  49. package/dist/spdz-runner.js +107 -0
  50. package/dist/spdz-runner.js.map +1 -0
  51. package/dist/spdz.d.ts +136 -0
  52. package/dist/spdz.js +243 -0
  53. package/dist/spdz.js.map +1 -0
  54. package/dist/thermometer.d.ts +22 -0
  55. package/dist/thermometer.js +41 -0
  56. package/dist/thermometer.js.map +1 -0
  57. package/package.json +58 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Atlas Authorization
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,66 @@
1
- # Temporary Holding Version
1
+ # @atlasauth/pca-mpc
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Experimental reference prototype: a multi-stakeholder policy VM composed under secure multi-party computation, for Proof-Carrying Authority (PCA). Several stakeholders (for example user, organization, regulator) each evaluate their own private PCA policy to a tiny decision vector `{ allow, t, rQuant }`, then compose them under MPC so only the joint result is revealed: allow if every party allows, the maximum required proof threshold, and the maximum quantized risk. No party reveals its own policy or its own vector.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm i @atlasauth/pca-mpc @atlasauth/pca
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { generateKeyPair, encodeKey, mintGrant, DEFAULT_RISK_POLICY } from '@atlasauth/pca';
15
+ import { evaluateParty, composeSecure, composeSecureMalicious } from '@atlasauth/pca-mpc';
16
+
17
+ const principal = generateKeyPair();
18
+ const holder = generateKeyPair();
19
+
20
+ // Each stakeholder builds a PartyPolicy from its own grant + decision input.
21
+ function party(id: string, verb = '*') {
22
+ const { grant } = mintGrant({
23
+ principalSecret: principal.secretKey,
24
+ principalPublic: encodeKey(principal.publicKey),
25
+ holder: encodeKey(holder.publicKey),
26
+ goal: 'wire funds',
27
+ envelope: { predicates: [{ verb, resource: '*' }], caveats: [], agent_binding: {}, risk_policy: DEFAULT_RISK_POLICY },
28
+ });
29
+ return {
30
+ id,
31
+ decideInput: {
32
+ grant,
33
+ action: { action: { verb: 'wire', resource: 'acct:1', params: {} } },
34
+ risk: { semanticDistance: 0.1, reversibility: 1, blastRadius: 0.1, taint: 0, confidence: 1, age: 0 },
35
+ budget: { B: 1, tau: 0 },
36
+ now: Date.now(),
37
+ },
38
+ };
39
+ }
40
+
41
+ // Local step: each party reduces its private policy to a decision vector.
42
+ const vectors = [party('user'), party('org'), party('regulator', 'read')].map((p) => evaluateParty(p));
43
+
44
+ // Joint step: semi-honest MPC (additive sharing over a prime field + Beaver triples).
45
+ const result = composeSecure(vectors);
46
+ result.composed; // { allow: 0, t: 3, rQuant: 5 } (the regulator only permits 'read', so the joint answer is deny)
47
+
48
+ // Malicious-with-abort variant (SPDZ-style MACs): correct result, or a thrown abort.
49
+ const strict = composeSecureMalicious(vectors);
50
+ ```
51
+
52
+ ## API
53
+
54
+ - `evaluateParty(policy, Q?)` - reduce a party's private PCA decision input to a `DecisionVector`.
55
+ - `composeSecure(vectors, config?)` - semi-honest composition; returns `composed`, the public Beaver `opened` messages, and per-party views.
56
+ - `composeSecureMalicious(vectors, config?)` - dishonest-majority layer with information-theoretic MACs and a MAC check that aborts on deviation; an optional no-dealer offline provider (OT-based) replaces the trusted dealer.
57
+ - `composeClear(vectors)` - the cleartext reference that `composeSecure` must equal.
58
+ - Lower-level building blocks (field arithmetic, sharing, Beaver triples, oblivious transfer including a post-quantum KEM-based base OT) are exported too.
59
+
60
+ ## Status
61
+
62
+ This is a research prototype, not a production MPC system. All parties run in one process and exchange data through in-memory structures, so it demonstrates and tests the protocol logic but is not a networked deployment. The semi-honest layer assumes parties follow the protocol and uses a trusted dealer for triples; the malicious layer detects deviation by aborting and can use an OT-based offline phase instead of a dealer. The cryptography has not been audited. The composed verdict informs a decision; it does not authorize anything on its own - the resource server's verifier decides.
63
+
64
+ ## License
65
+
66
+ MIT - see LICENSE
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Secure MULTIPLICATION of two additively-shared values via Beaver triples.
3
+ *
4
+ * Additive sharing gives addition and public-scalar ops for free, but NOT multiplication (the
5
+ * product of two secrets is quadratic in the shares and has no local form). Beaver's trick turns one
6
+ * multiplication into two PUBLIC openings plus local arithmetic, using a pre-shared random triple
7
+ * (a, b, c=a*b):
8
+ *
9
+ * x*y = (d+a)(e+b) = d*e + d*b + e*a + a*b where d = x-a, e = y-b
10
+ *
11
+ * The parties open d and e (reconstruct them in the clear). Because a and b are uniform and secret,
12
+ * d = x-a and e = y-b are uniform masks that reveal NOTHING about x or y. Given public d, e each
13
+ * party then forms its share of the product locally:
14
+ *
15
+ * z_i = c_i + d*b_i + e*a_i (and party 0 additionally adds the public term d*e)
16
+ *
17
+ * so Σ z_i = c + d*b + e*a + d*e = a*b + d*b + e*a + d*e = x*y.
18
+ *
19
+ * PREPROCESSING MODEL (prototype): a TRUSTED DEALER generates the triples and shares them. This is a
20
+ * standard and legitimate semi-honest preprocessing choice; the correctness/privacy of the ONLINE
21
+ * phase (what the parties exchange) does not depend on the dealer beyond "the triples are correct and
22
+ * the a,b stay secret." A production system removes the trusted dealer with an offline triple
23
+ * generation protocol (OT-based or homomorphic-encryption-based), and adds MACs (SPDZ) for malicious
24
+ * security. See the design doc.
25
+ */
26
+ import { FieldRng } from './field';
27
+ import { type SharedValue } from './sharing';
28
+ /** A Beaver multiplication triple, each component additively shared across the N parties. */
29
+ export interface Triple {
30
+ a: SharedValue;
31
+ b: SharedValue;
32
+ c: SharedValue;
33
+ }
34
+ /** One opened (reconstructed, public) protocol message. `shares` are the per-party broadcasts. */
35
+ export interface OpenedMsg {
36
+ label: 'd' | 'e';
37
+ value: bigint;
38
+ shares: bigint[];
39
+ }
40
+ /** Trusted-dealer preprocessing: generate `count` correct multiplication triples for `n` parties. */
41
+ export declare function genTriples(count: number, n: number, rng: FieldRng): Triple[];
42
+ /** Trusted-dealer preprocessing: one correct triple (a, b, c=a*b), each component shared. */
43
+ export declare function genTriple(n: number, rng: FieldRng): Triple;
44
+ /**
45
+ * Secure multiply: returns a sharing of x*y, consuming `triple`. Every opened value (d, e) is pushed
46
+ * to `transcript` when provided — that transcript IS the adversary's view of the online messages, and
47
+ * the privacy tests assert it reveals nothing about the inputs.
48
+ */
49
+ export declare function beaverMul(x: SharedValue, y: SharedValue, triple: Triple, transcript?: OpenedMsg[]): SharedValue;
package/dist/beaver.js ADDED
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ /**
3
+ * Secure MULTIPLICATION of two additively-shared values via Beaver triples.
4
+ *
5
+ * Additive sharing gives addition and public-scalar ops for free, but NOT multiplication (the
6
+ * product of two secrets is quadratic in the shares and has no local form). Beaver's trick turns one
7
+ * multiplication into two PUBLIC openings plus local arithmetic, using a pre-shared random triple
8
+ * (a, b, c=a*b):
9
+ *
10
+ * x*y = (d+a)(e+b) = d*e + d*b + e*a + a*b where d = x-a, e = y-b
11
+ *
12
+ * The parties open d and e (reconstruct them in the clear). Because a and b are uniform and secret,
13
+ * d = x-a and e = y-b are uniform masks that reveal NOTHING about x or y. Given public d, e each
14
+ * party then forms its share of the product locally:
15
+ *
16
+ * z_i = c_i + d*b_i + e*a_i (and party 0 additionally adds the public term d*e)
17
+ *
18
+ * so Σ z_i = c + d*b + e*a + d*e = a*b + d*b + e*a + d*e = x*y.
19
+ *
20
+ * PREPROCESSING MODEL (prototype): a TRUSTED DEALER generates the triples and shares them. This is a
21
+ * standard and legitimate semi-honest preprocessing choice; the correctness/privacy of the ONLINE
22
+ * phase (what the parties exchange) does not depend on the dealer beyond "the triples are correct and
23
+ * the a,b stay secret." A production system removes the trusted dealer with an offline triple
24
+ * generation protocol (OT-based or homomorphic-encryption-based), and adds MACs (SPDZ) for malicious
25
+ * security. See the design doc.
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.genTriples = genTriples;
29
+ exports.genTriple = genTriple;
30
+ exports.beaverMul = beaverMul;
31
+ const field_1 = require("./field");
32
+ const sharing_1 = require("./sharing");
33
+ const sharing_2 = require("./sharing");
34
+ /** Trusted-dealer preprocessing: generate `count` correct multiplication triples for `n` parties. */
35
+ function genTriples(count, n, rng) {
36
+ const triples = [];
37
+ for (let i = 0; i < count; i++)
38
+ triples.push(genTriple(n, rng));
39
+ return triples;
40
+ }
41
+ /** Trusted-dealer preprocessing: one correct triple (a, b, c=a*b), each component shared. */
42
+ function genTriple(n, rng) {
43
+ const a = rng.next();
44
+ const b = rng.next();
45
+ const c = (0, field_1.fmul)(a, b);
46
+ return { a: (0, sharing_2.share)(a, n, rng), b: (0, sharing_2.share)(b, n, rng), c: (0, sharing_2.share)(c, n, rng) };
47
+ }
48
+ /**
49
+ * Secure multiply: returns a sharing of x*y, consuming `triple`. Every opened value (d, e) is pushed
50
+ * to `transcript` when provided — that transcript IS the adversary's view of the online messages, and
51
+ * the privacy tests assert it reveals nothing about the inputs.
52
+ */
53
+ function beaverMul(x, y, triple, transcript) {
54
+ if (x.length !== y.length)
55
+ throw new Error('beaverMul: operand party counts differ');
56
+ const dShares = (0, sharing_1.subSV)(x, triple.a);
57
+ const eShares = (0, sharing_1.subSV)(y, triple.b);
58
+ const d = (0, sharing_1.reconstruct)(dShares);
59
+ const e = (0, sharing_1.reconstruct)(eShares);
60
+ if (transcript) {
61
+ transcript.push({ label: 'd', value: d, shares: dShares });
62
+ transcript.push({ label: 'e', value: e, shares: eShares });
63
+ }
64
+ const de = (0, field_1.fmul)(d, e);
65
+ return triple.c.map((ci, i) => {
66
+ let zi = (0, field_1.fadd)(ci, (0, field_1.fadd)((0, field_1.fmul)(d, triple.b[i]), (0, field_1.fmul)(e, triple.a[i])));
67
+ if (i === 0)
68
+ zi = (0, field_1.fadd)(zi, de); // the public d*e term lives on exactly one party
69
+ return zi;
70
+ });
71
+ }
72
+ //# sourceMappingURL=beaver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beaver.js","sourceRoot":"","sources":["../src/beaver.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;;AAqBH,gCAIC;AAGD,8BAKC;AAOD,8BAqBC;AA3DD,mCAA+C;AAC/C,uCAAiE;AACjE,uCAAkC;AAgBlC,qGAAqG;AACrG,SAAgB,UAAU,CAAC,KAAa,EAAE,CAAS,EAAE,GAAa;IAChE,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE;QAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAChE,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,6FAA6F;AAC7F,SAAgB,SAAS,CAAC,CAAS,EAAE,GAAa;IAChD,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACrB,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACrB,MAAM,CAAC,GAAG,IAAA,YAAI,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACrB,OAAO,EAAE,CAAC,EAAE,IAAA,eAAK,EAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,IAAA,eAAK,EAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,IAAA,eAAK,EAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AAC3E,CAAC;AAED;;;;GAIG;AACH,SAAgB,SAAS,CACvB,CAAc,EACd,CAAc,EACd,MAAc,EACd,UAAwB;IAExB,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACrF,MAAM,OAAO,GAAG,IAAA,eAAK,EAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,IAAA,eAAK,EAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,CAAC,GAAG,IAAA,qBAAW,EAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,IAAA,qBAAW,EAAC,OAAO,CAAC,CAAC;IAC/B,IAAI,UAAU,EAAE,CAAC;QACf,UAAU,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;QAC3D,UAAU,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD,MAAM,EAAE,GAAG,IAAA,YAAI,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACtB,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE;QAC5B,IAAI,EAAE,GAAG,IAAA,YAAI,EAAC,EAAE,EAAE,IAAA,YAAI,EAAC,IAAA,YAAI,EAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,EAAE,IAAA,YAAI,EAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC;QACtE,IAAI,CAAC,KAAK,CAAC;YAAE,EAAE,GAAG,IAAA,YAAI,EAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,iDAAiD;QACjF,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Composition semantics (the "meet" of stakeholder authorities) and the CLEARTEXT reference.
3
+ *
4
+ * Composition is deliberately conservative — it can only ever TIGHTEN, never loosen, matching PCA's
5
+ * fail-closed philosophy:
6
+ * - allow = AND over parties (every stakeholder must admit; one deny => composed deny)
7
+ * - t = MAX over parties (the strictest required proof strength wins)
8
+ * - rQuant = MAX over parties (the highest assessed risk wins)
9
+ *
10
+ * The secure runner (`runner.ts`) computes EXACTLY this function under MPC; `composeClear` is the
11
+ * ground truth the correctness tests check it against.
12
+ */
13
+ import type { DecisionVector } from './party';
14
+ /** The composed, revealed decision. */
15
+ export interface ComposedDecision {
16
+ allow: 0 | 1;
17
+ t: 1 | 2 | 3;
18
+ rQuant: number;
19
+ }
20
+ /** Cleartext composition over the parties' decision vectors. Requires at least one party. */
21
+ export declare function composeClear(vectors: ReadonlyArray<DecisionVector>): ComposedDecision;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ /**
3
+ * Composition semantics (the "meet" of stakeholder authorities) and the CLEARTEXT reference.
4
+ *
5
+ * Composition is deliberately conservative — it can only ever TIGHTEN, never loosen, matching PCA's
6
+ * fail-closed philosophy:
7
+ * - allow = AND over parties (every stakeholder must admit; one deny => composed deny)
8
+ * - t = MAX over parties (the strictest required proof strength wins)
9
+ * - rQuant = MAX over parties (the highest assessed risk wins)
10
+ *
11
+ * The secure runner (`runner.ts`) computes EXACTLY this function under MPC; `composeClear` is the
12
+ * ground truth the correctness tests check it against.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.composeClear = composeClear;
16
+ /** Cleartext composition over the parties' decision vectors. Requires at least one party. */
17
+ function composeClear(vectors) {
18
+ if (vectors.length === 0)
19
+ throw new Error('composeClear: no parties (fail closed)');
20
+ let allow = 1;
21
+ let t = 1;
22
+ let rQuant = 0;
23
+ for (const v of vectors) {
24
+ if (v.allow === 0)
25
+ allow = 0;
26
+ if (v.t > t)
27
+ t = v.t;
28
+ if (v.rQuant > rQuant)
29
+ rQuant = v.rQuant;
30
+ }
31
+ return { allow, t, rQuant };
32
+ }
33
+ //# sourceMappingURL=compose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;AAYH,oCAWC;AAZD,6FAA6F;AAC7F,SAAgB,YAAY,CAAC,OAAsC;IACjE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACpF,IAAI,KAAK,GAAU,CAAC,CAAC;IACrB,IAAI,CAAC,GAAc,CAAC,CAAC;IACrB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC;YAAE,KAAK,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;YAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACrB,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM;YAAE,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Cryptographically-secure randomness — the default source for ALL protocol randomness (docs §7.1
3
+ * item c). Every secret the protocol samples — base-OT scalars `x`/`y`, the Schnorr nonce, IKNP seeds,
4
+ * Gilboa masks `ρ`, the MAC key shares `α_i`, triple shares `a_i`/`b_i`, and the sacrifice / input /
5
+ * MAC-check coefficients — is drawn here from `node:crypto.randomBytes` unless a test explicitly asks
6
+ * for a reproducible seeded stream.
7
+ *
8
+ * The deterministic SplitMix64 path (`field.ts` / `ot.ts`) is retained ONLY behind an explicit
9
+ * `testSeed`, so tests stay bit-reproducible while production never silently runs on a deterministic
10
+ * PRNG. The one deliberately-NON-secret source is the KOS challenge `χ` (`ot.ts`), which is a
11
+ * public-coin Fiat–Shamir value derived from the committed transcript — it must be reproducible by
12
+ * both parties and is therefore a transcript hash by design, not a secret the CSPRNG governs.
13
+ *
14
+ * NOTE on the remaining boundary (honest, docs §7.1): a real deployment also needs a *secure coin-flip*
15
+ * among the parties for the public MAC-check / sacrifice coefficients (here a CSPRNG stands in for that
16
+ * interactive sub-protocol), and a secret per-session seed management story. Using `randomBytes` closes
17
+ * the "deterministic PRNG" caveat; it does not itself provide the multiparty coin-flip, which is a
18
+ * network/protocol layer (§7.1 item: real async network).
19
+ */
20
+ import { FieldRng } from './field';
21
+ import type { OtRandom } from './ot';
22
+ /** A minimal field-random source: `next()` yields a uniform element of F_p. `FieldRng` satisfies it. */
23
+ export interface FieldSource {
24
+ next(): bigint;
25
+ }
26
+ /**
27
+ * A CSPRNG-backed field RNG. Drop-in for `FieldRng` everywhere (it extends it and overrides `next()`),
28
+ * so `share` / `setupMac` / `macCheck` / the no-dealer offline all accept it with no signature change.
29
+ * Draws unbiased elements of F_p by rejection sampling over fresh `randomBytes`.
30
+ */
31
+ export declare class SecureFieldRng extends FieldRng {
32
+ constructor();
33
+ next(): bigint;
34
+ }
35
+ /**
36
+ * A CSPRNG-backed `OtRandom` (scalars in `[1, L)` by rejection, raw bytes from `randomBytes`). This is
37
+ * the default randomness for every base OT / OT-extension / Schnorr nonce when no test seed is set.
38
+ */
39
+ export declare function secureOtRandom(): OtRandom;
40
+ /**
41
+ * The field-randomness policy for the whole package: a CSPRNG by default, or a reproducible seeded
42
+ * `FieldRng` ONLY when an explicit `testSeed` is given. Production calls this with no argument.
43
+ */
44
+ export declare function fieldRandom(opts?: {
45
+ testSeed?: bigint;
46
+ }): FieldSource;
package/dist/csprng.js ADDED
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ /**
3
+ * Cryptographically-secure randomness — the default source for ALL protocol randomness (docs §7.1
4
+ * item c). Every secret the protocol samples — base-OT scalars `x`/`y`, the Schnorr nonce, IKNP seeds,
5
+ * Gilboa masks `ρ`, the MAC key shares `α_i`, triple shares `a_i`/`b_i`, and the sacrifice / input /
6
+ * MAC-check coefficients — is drawn here from `node:crypto.randomBytes` unless a test explicitly asks
7
+ * for a reproducible seeded stream.
8
+ *
9
+ * The deterministic SplitMix64 path (`field.ts` / `ot.ts`) is retained ONLY behind an explicit
10
+ * `testSeed`, so tests stay bit-reproducible while production never silently runs on a deterministic
11
+ * PRNG. The one deliberately-NON-secret source is the KOS challenge `χ` (`ot.ts`), which is a
12
+ * public-coin Fiat–Shamir value derived from the committed transcript — it must be reproducible by
13
+ * both parties and is therefore a transcript hash by design, not a secret the CSPRNG governs.
14
+ *
15
+ * NOTE on the remaining boundary (honest, docs §7.1): a real deployment also needs a *secure coin-flip*
16
+ * among the parties for the public MAC-check / sacrifice coefficients (here a CSPRNG stands in for that
17
+ * interactive sub-protocol), and a secret per-session seed management story. Using `randomBytes` closes
18
+ * the "deterministic PRNG" caveat; it does not itself provide the multiparty coin-flip, which is a
19
+ * network/protocol layer (§7.1 item: real async network).
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.SecureFieldRng = void 0;
23
+ exports.secureOtRandom = secureOtRandom;
24
+ exports.fieldRandom = fieldRandom;
25
+ const node_crypto_1 = require("node:crypto");
26
+ const field_1 = require("./field");
27
+ const ec_1 = require("./ec");
28
+ const MASK64 = (1n << 64n) - 1n;
29
+ /**
30
+ * A CSPRNG-backed field RNG. Drop-in for `FieldRng` everywhere (it extends it and overrides `next()`),
31
+ * so `share` / `setupMac` / `macCheck` / the no-dealer offline all accept it with no signature change.
32
+ * Draws unbiased elements of F_p by rejection sampling over fresh `randomBytes`.
33
+ */
34
+ class SecureFieldRng extends field_1.FieldRng {
35
+ constructor() {
36
+ // The super ctor seeds the (now-unused) SplitMix64 state; `next()` below ignores it entirely.
37
+ super(0n);
38
+ }
39
+ next() {
40
+ // Largest multiple of p that fits in 64 bits; reject anything at/above it so `% p` is exactly uniform.
41
+ const limit = ((MASK64 + 1n) / field_1.PRIME) * field_1.PRIME;
42
+ for (;;) {
43
+ const buf = (0, node_crypto_1.randomBytes)(8);
44
+ let u = 0n;
45
+ for (let i = 0; i < 8; i++)
46
+ u = (u << 8n) | BigInt(buf[i]);
47
+ if (u < limit)
48
+ return u % field_1.PRIME;
49
+ }
50
+ }
51
+ }
52
+ exports.SecureFieldRng = SecureFieldRng;
53
+ /**
54
+ * A CSPRNG-backed `OtRandom` (scalars in `[1, L)` by rejection, raw bytes from `randomBytes`). This is
55
+ * the default randomness for every base OT / OT-extension / Schnorr nonce when no test seed is set.
56
+ */
57
+ function secureOtRandom() {
58
+ return {
59
+ scalar() {
60
+ // 32 bytes → reject the top `2^256 mod L` residues so reduction mod L is exactly uniform; reject 0.
61
+ const bound = (1n << 256n) - ((1n << 256n) % ec_1.L);
62
+ for (;;) {
63
+ const buf = (0, node_crypto_1.randomBytes)(32);
64
+ let acc = 0n;
65
+ for (let i = 0; i < 32; i++)
66
+ acc = (acc << 8n) | BigInt(buf[i]);
67
+ if (acc >= bound)
68
+ continue;
69
+ const s = acc % ec_1.L;
70
+ if (s !== 0n)
71
+ return s;
72
+ }
73
+ },
74
+ bytes(n) {
75
+ return new Uint8Array((0, node_crypto_1.randomBytes)(n));
76
+ },
77
+ };
78
+ }
79
+ /**
80
+ * The field-randomness policy for the whole package: a CSPRNG by default, or a reproducible seeded
81
+ * `FieldRng` ONLY when an explicit `testSeed` is given. Production calls this with no argument.
82
+ */
83
+ function fieldRandom(opts) {
84
+ return opts?.testSeed === undefined ? new SecureFieldRng() : new field_1.FieldRng(opts.testSeed);
85
+ }
86
+ //# sourceMappingURL=csprng.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csprng.js","sourceRoot":"","sources":["../src/csprng.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;GAkBG;;;AAyCH,wCAkBC;AAMD,kCAEC;AAjED,6CAA0C;AAC1C,mCAA0C;AAC1C,6BAAyB;AAGzB,MAAM,MAAM,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC;AAOhC;;;;GAIG;AACH,MAAa,cAAe,SAAQ,gBAAQ;IAC1C;QACE,8FAA8F;QAC9F,KAAK,CAAC,EAAE,CAAC,CAAC;IACZ,CAAC;IAEQ,IAAI;QACX,uGAAuG;QACvG,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,aAAK,CAAC,GAAG,aAAK,CAAC;QAC9C,SAAS,CAAC;YACR,MAAM,GAAG,GAAG,IAAA,yBAAW,EAAC,CAAC,CAAC,CAAC;YAC3B,IAAI,CAAC,GAAG,EAAE,CAAC;YACX,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;gBAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAC;YAC5D,IAAI,CAAC,GAAG,KAAK;gBAAE,OAAO,CAAC,GAAG,aAAK,CAAC;QAClC,CAAC;IACH,CAAC;CACF;AAhBD,wCAgBC;AAED;;;GAGG;AACH,SAAgB,cAAc;IAC5B,OAAO;QACL,MAAM;YACJ,oGAAoG;YACpG,MAAM,KAAK,GAAG,CAAC,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,IAAI,CAAC,GAAG,MAAC,CAAC,CAAC;YAChD,SAAS,CAAC;gBACR,MAAM,GAAG,GAAG,IAAA,yBAAW,EAAC,EAAE,CAAC,CAAC;gBAC5B,IAAI,GAAG,GAAG,EAAE,CAAC;gBACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE;oBAAE,GAAG,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAC;gBACjE,IAAI,GAAG,IAAI,KAAK;oBAAE,SAAS;gBAC3B,MAAM,CAAC,GAAG,GAAG,GAAG,MAAC,CAAC;gBAClB,IAAI,CAAC,KAAK,EAAE;oBAAE,OAAO,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;QACD,KAAK,CAAC,CAAS;YACb,OAAO,IAAI,UAAU,CAAC,IAAA,yBAAW,EAAC,CAAC,CAAC,CAAC,CAAC;QACxC,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAgB,WAAW,CAAC,IAA4B;IACtD,OAAO,IAAI,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,cAAc,EAAE,CAAC,CAAC,CAAC,IAAI,gBAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC3F,CAAC"}
package/dist/ec.d.ts ADDED
@@ -0,0 +1,130 @@
1
+ /**
2
+ * A small, self-contained prime-order elliptic-curve group, used by the base oblivious transfer in
3
+ * `ot.ts`. The no-dealer offline phase (MASCOT-style triple generation) rests on real OT, and real OT
4
+ * rests on a real group with a hard discrete log — this module provides that group with nothing but
5
+ * BigInt arithmetic, so the whole stack stays dependency-free and testable in-process.
6
+ *
7
+ * Curve: **Ed25519** (twisted Edwards `−x² + y² = 1 + d·x²·y²` over `F_q`, `q = 2^255 − 19`). We work
8
+ * exclusively in the prime-order subgroup of order `L = 2^252 + 27742317777372353535851937790883648493`
9
+ * generated by the standard base point `B`, keeping every scalar in `[0, L)`; all group elements we
10
+ * ever form are scalar multiples of subgroup points, so they stay in the subgroup. Points are held in
11
+ * **extended coordinates** `(X, Y, Z, T)` with `x = X/Z`, `y = Y/Z`, `T = XY/Z`, and combined with the
12
+ * unified ("hwcd-3") addition law, which is *complete* on the subgroup — there are no special cases to
13
+ * branch on, which is exactly what keeps the implementation free of the subtle point-at-infinity bugs
14
+ * that plague short-Weierstrass code.
15
+ *
16
+ * SECURITY NOTES (prototype honesty, mirrored in docs §7.1):
17
+ * - **Point validation IS implemented** (`isOnCurve` / `hasPrimeOrder` / `assertInSubgroup`): every
18
+ * point a party RECEIVES across the OT trust boundary is validated to be on the curve and in the
19
+ * prime-order subgroup (cofactor check `[L]·P = O`), and non-identity, so a malicious party cannot
20
+ * inject an off-curve / small-order / mixed-order point to mount a small-subgroup attack. A
21
+ * `schnorrProve`/`schnorrVerify` NIZK of knowledge of the sender's discrete log `y` (`S = y·B`)
22
+ * binds `S` to a known exponent, which — with validation — is what makes the base OT malicious-secure.
23
+ * - **Constant-time: the secret-scalar multiplications now run in a constant-time WASM core by
24
+ * DEFAULT.** BigInt modular arithmetic branches on operand size and is not constant-time; rather than
25
+ * pretend otherwise, the security-critical ops (every secret-scalar multiply/point-combine: `mulBase`,
26
+ * `mul`, `add`, `sub`, `neg`, and the `[L]·P` cofactor ladder) are routed through `@atlasauth/pca-mpc-wasm`
27
+ * (a `curve25519-dalek` core compiled to WebAssembly) whenever it loads, which is the default. The
28
+ * pure-BigInt path below is retained verbatim as a graceful fallback for when the WASM core is
29
+ * unavailable, and is proven byte-for-byte equivalent to it (see the backend section and
30
+ * `ec-wasm.test.ts`). The remaining BigInt marshalling (point compress/decompress) operates on
31
+ * public point values, never the secret scalar, and is not claimed constant-time (docs §7.1).
32
+ * - Discrete log in the order-`L` subgroup is the hardness the OT's receiver-choice privacy relies on.
33
+ */
34
+ /** Field prime q = 2^255 − 19. */
35
+ export declare const Q: bigint;
36
+ /** Prime order of the base-point subgroup. Scalars live in [0, L). */
37
+ export declare const L: bigint;
38
+ /** A curve point in extended twisted-Edwards coordinates (X:Y:Z:T), x=X/Z, y=Y/Z, T=XY/Z. */
39
+ export interface Point {
40
+ X: bigint;
41
+ Y: bigint;
42
+ Z: bigint;
43
+ T: bigint;
44
+ }
45
+ /** The identity (neutral) element, affine (0, 1). */
46
+ export declare const IDENTITY: Point;
47
+ /** The standard Ed25519 base point B (a generator of the order-L subgroup). */
48
+ export declare const BASE: Point;
49
+ /** Canonical representative in [0, q). */
50
+ export declare function modFq(x: bigint): bigint;
51
+ /**
52
+ * Dedicated doubling (twisted Edwards a=−1, "dbl-2008-hwcd") — cheaper than the unified add, used to
53
+ * speed up the scalar ladder. Complete on the subgroup.
54
+ */
55
+ export declare function dbl(p: Point): Point;
56
+ /** Whether the constant-time WASM curve backend is active (else the pure-BigInt path is in use). */
57
+ export declare function isWasmCurveActive(): boolean;
58
+ /**
59
+ * Canonical 32-byte compressed Ed25519 encoding of a point (little-endian `y`, sign of `x` in bit 255)
60
+ * — the wire form the WASM core speaks. Faithful for on-curve points; the matching inverse is
61
+ * `decompressPoint`.
62
+ */
63
+ export declare function compressPoint(p: Point): Uint8Array;
64
+ /**
65
+ * Decode a canonical 32-byte compressed encoding back into an extended-coordinate `Point` (RFC 8032
66
+ * §5.1.3 x-recovery). Used to bring a WASM-core result back into the `Point` representation so the rest
67
+ * of `ec.ts`/`ot.ts` (affine encoding, KDF hashing, equality) sees byte-identical values to the pure
68
+ * path. Throws `PointValidationError` on a non-canonical / off-curve encoding.
69
+ */
70
+ export declare function decompressPoint(bytes: Uint8Array): Point;
71
+ /**
72
+ * Unified complete addition P + Q. Routes through the constant-time WASM core when available (inputs
73
+ * are on-curve points everywhere the OT stack calls this), else the pure-BigInt `add-2008-hwcd-3`.
74
+ */
75
+ export declare function add(p1: Point, p2: Point): Point;
76
+ /** Scalar multiplication [k]P (k reduced mod L; k=0 ⇒ identity). WASM core when available. */
77
+ export declare function mul(k: bigint, p: Point): Point;
78
+ /** [k]·BASE. WASM core when available (the fixed-base secret-scalar multiply). */
79
+ export declare function mulBase(k: bigint): Point;
80
+ /** P − Q. WASM core when available. */
81
+ export declare function sub(p1: Point, p2: Point): Point;
82
+ /** −P. WASM core when available. */
83
+ export declare function neg(p: Point): Point;
84
+ /** Affine (x, y) of a point (one inversion). Identity ⇒ (0, 1). */
85
+ export declare function toAffine(p: Point): {
86
+ x: bigint;
87
+ y: bigint;
88
+ };
89
+ /** Whether two points are equal as group elements (compare in affine). */
90
+ export declare function equal(p1: Point, p2: Point): boolean;
91
+ /** Canonical 64-byte encoding of a point (affine x‖y), the input to the OT key-derivation hash. */
92
+ export declare function encodePoint(p: Point): Uint8Array;
93
+ /** Raised when a received point fails on-curve / subgroup / identity validation. */
94
+ export declare class PointValidationError extends Error {
95
+ constructor(message: string);
96
+ }
97
+ /**
98
+ * Whether `p`'s extended coordinates lie on the Ed25519 curve. Checks projectively (no inversion):
99
+ * - `Z ≠ 0` (a valid projective point),
100
+ * - the T-invariant `T·Z ≡ X·Y`,
101
+ * - the twisted-Edwards equation (`a = −1`) cleared of denominators: `(Y² − X²)·Z² ≡ Z⁴ + d·X²·Y²`.
102
+ * An off-curve / malformed point fails here before any scalar work.
103
+ */
104
+ export declare function isOnCurve(p: Point): boolean;
105
+ /** Whether `p` is the identity (neutral) element (affine `(0, 1)`). */
106
+ export declare function isIdentity(p: Point): boolean;
107
+ /**
108
+ * The cofactor check `[L]·P = O`. When the constant-time WASM core is loaded, the `[L]·P` scalar work
109
+ * runs inside it (the point is compressed to its canonical 32-byte form — faithful for ON-curve points,
110
+ * which is the only case this is reached with in the validation flow — and the core's torsion check is
111
+ * exactly `[L]·P = O`); otherwise it falls back to the pure-BigInt raw ladder. Both agree bit-for-bit
112
+ * (the `[L]·P = O ⟺ torsion-free ∨ identity` equivalence), so the observable result is identical.
113
+ */
114
+ export declare function hasPrimeOrder(p: Point): boolean;
115
+ /** Whether `p` is a valid non-identity point of the prime-order subgroup (the OT point contract). */
116
+ export declare function isInSubgroup(p: Point): boolean;
117
+ /** Assert `p` is a valid non-identity prime-order-subgroup point, else throw `PointValidationError`. */
118
+ export declare function assertInSubgroup(p: Point, label?: string): void;
119
+ /** A Fiat–Shamir Schnorr proof of knowledge of `y` s.t. `S = y·B`: commitment `U = r·B` and `z = r + e·y`. */
120
+ export interface SchnorrProof {
121
+ U: Point;
122
+ z: bigint;
123
+ }
124
+ /** Prove knowledge of `y` with `S = y·B`. `rnd` draws the (secret) nonce `r ∈ [1, L)`. */
125
+ export declare function schnorrProve(y: bigint, S: Point, rnd: () => bigint): SchnorrProof;
126
+ /**
127
+ * Verify a Schnorr proof for `S`: `z` is in range, `U` is a valid subgroup point, and `z·B = U + e·S`
128
+ * for the recomputed challenge `e`. Returns false on any failure (the OT layer turns that into an abort).
129
+ */
130
+ export declare function schnorrVerify(S: Point, proof: SchnorrProof): boolean;