@atlasauth/pca-mpc 0.0.0-stage → 0.1.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.
- package/README.md +30 -2
- package/dist/beaver.d.ts +49 -0
- package/dist/beaver.js +72 -0
- package/dist/beaver.js.map +1 -0
- package/dist/compose.d.ts +21 -0
- package/dist/compose.js +33 -0
- package/dist/compose.js.map +1 -0
- package/dist/csprng.d.ts +46 -0
- package/dist/csprng.js +86 -0
- package/dist/csprng.js.map +1 -0
- package/dist/ec.d.ts +110 -0
- package/dist/ec.js +306 -0
- package/dist/ec.js.map +1 -0
- package/dist/field.d.ts +34 -0
- package/dist/field.js +65 -0
- package/dist/field.js.map +1 -0
- package/dist/gf128.d.ts +23 -0
- package/dist/gf128.js +59 -0
- package/dist/gf128.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/kem-ot.d.ts +137 -0
- package/dist/kem-ot.js +224 -0
- package/dist/kem-ot.js.map +1 -0
- package/dist/mascot.d.ts +189 -0
- package/dist/mascot.js +322 -0
- package/dist/mascot.js.map +1 -0
- package/dist/ot.d.ts +212 -0
- package/dist/ot.js +536 -0
- package/dist/ot.js.map +1 -0
- package/dist/party.d.ts +38 -0
- package/dist/party.js +40 -0
- package/dist/party.js.map +1 -0
- package/dist/runner.d.ts +56 -0
- package/dist/runner.js +97 -0
- package/dist/runner.js.map +1 -0
- package/dist/sharing.d.ts +40 -0
- package/dist/sharing.js +97 -0
- package/dist/sharing.js.map +1 -0
- package/dist/spdz-runner.d.ts +50 -0
- package/dist/spdz-runner.js +107 -0
- package/dist/spdz-runner.js.map +1 -0
- package/dist/spdz.d.ts +136 -0
- package/dist/spdz.js +243 -0
- package/dist/spdz.js.map +1 -0
- package/dist/thermometer.d.ts +22 -0
- package/dist/thermometer.js +41 -0
- package/dist/thermometer.js.map +1 -0
- package/package.json +31 -3
package/README.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @atlasauth/pca-mpc
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
PCA evolution B5 reference prototype: a multi-stakeholder Policy VM composed under secure multi-party computation (semi-honest additive secret sharing over a prime field), so user+org+regulator jointly decide allow/threshold without any party revealing its own policy.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm i @atlasauth/pca-mpc
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Depends on the core `@atlasauth/pca` (installed transitively).
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { evaluateParty, composeSecure } from '@atlasauth/pca-mpc';
|
|
17
|
+
|
|
18
|
+
// Each stakeholder evaluates its OWN private policy to a tiny decision vector { allow, t, rQuant }.
|
|
19
|
+
const user = evaluateParty({ id: 'user', decideInput: userInput });
|
|
20
|
+
const org = evaluateParty({ id: 'org', decideInput: orgInput });
|
|
21
|
+
const regulator = evaluateParty({ id: 'regulator', decideInput: regulatorInput });
|
|
22
|
+
|
|
23
|
+
// Compose under MPC: only the joint decision is revealed, no party's own policy or vector.
|
|
24
|
+
const result = composeSecure([user, org, regulator]);
|
|
25
|
+
result.composed; // { allow, t, rQuant } — composed allow + MAX threshold + MAX risk
|
|
26
|
+
result.opened; // the Beaver openings (the only online messages)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`spdz` / `spdz-runner` provide the malicious-with-abort (dishonest-majority) layer. This computes a joint policy verdict; it does not authorize on its own — the resource server's verifier decides. See `docs/specs/pca-mpc-policy-vm.md` for the security model.
|
|
30
|
+
|
|
31
|
+
Part of Proof-Carrying Authority — see `@atlasauth/pca`.
|
package/dist/beaver.d.ts
ADDED
|
@@ -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;
|
package/dist/compose.js
ADDED
|
@@ -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"}
|
package/dist/csprng.d.ts
ADDED
|
@@ -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,110 @@
|
|
|
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 is NOT achieved and cannot be, in pure JS.** BigInt modular arithmetic branches on
|
|
24
|
+
* operand size and is not constant-time; this is a genuine language/runtime boundary (see docs §7.1),
|
|
25
|
+
* not a shortcut — full constant-time needs a native/WASM constant-time field, exactly as the TEE
|
|
26
|
+
* seam needs real hardware. We add what is cheap and honest (constant-time byte comparison for
|
|
27
|
+
* commitments lives in `ot.ts`), and do not pretend the field layer is constant-time.
|
|
28
|
+
* - Discrete log in the order-`L` subgroup is the hardness the OT's receiver-choice privacy relies on.
|
|
29
|
+
*/
|
|
30
|
+
/** Field prime q = 2^255 − 19. */
|
|
31
|
+
export declare const Q: bigint;
|
|
32
|
+
/** Prime order of the base-point subgroup. Scalars live in [0, L). */
|
|
33
|
+
export declare const L: bigint;
|
|
34
|
+
/** A curve point in extended twisted-Edwards coordinates (X:Y:Z:T), x=X/Z, y=Y/Z, T=XY/Z. */
|
|
35
|
+
export interface Point {
|
|
36
|
+
X: bigint;
|
|
37
|
+
Y: bigint;
|
|
38
|
+
Z: bigint;
|
|
39
|
+
T: bigint;
|
|
40
|
+
}
|
|
41
|
+
/** The identity (neutral) element, affine (0, 1). */
|
|
42
|
+
export declare const IDENTITY: Point;
|
|
43
|
+
/** The standard Ed25519 base point B (a generator of the order-L subgroup). */
|
|
44
|
+
export declare const BASE: Point;
|
|
45
|
+
/** Canonical representative in [0, q). */
|
|
46
|
+
export declare function modFq(x: bigint): bigint;
|
|
47
|
+
/**
|
|
48
|
+
* Unified complete addition (twisted Edwards a=−1, "add-2008-hwcd-3"). Works for P=Q too (so doubling
|
|
49
|
+
* is just `add(P, P)`), and is complete on the prime-order subgroup — no exceptional cases.
|
|
50
|
+
*/
|
|
51
|
+
export declare function add(p1: Point, p2: Point): Point;
|
|
52
|
+
/**
|
|
53
|
+
* Dedicated doubling (twisted Edwards a=−1, "dbl-2008-hwcd") — cheaper than the unified add, used to
|
|
54
|
+
* speed up the scalar ladder. Complete on the subgroup.
|
|
55
|
+
*/
|
|
56
|
+
export declare function dbl(p: Point): Point;
|
|
57
|
+
/** Scalar multiplication [k]P (double-and-add). k is reduced mod L; k=0 ⇒ identity. */
|
|
58
|
+
export declare function mul(k: bigint, p: Point): Point;
|
|
59
|
+
/** [k]·BASE. */
|
|
60
|
+
export declare function mulBase(k: bigint): Point;
|
|
61
|
+
/** P − Q. */
|
|
62
|
+
export declare function sub(p1: Point, p2: Point): Point;
|
|
63
|
+
/** −P = (−X : Y : Z : −T). */
|
|
64
|
+
export declare function neg(p: Point): Point;
|
|
65
|
+
/** Affine (x, y) of a point (one inversion). Identity ⇒ (0, 1). */
|
|
66
|
+
export declare function toAffine(p: Point): {
|
|
67
|
+
x: bigint;
|
|
68
|
+
y: bigint;
|
|
69
|
+
};
|
|
70
|
+
/** Whether two points are equal as group elements (compare in affine). */
|
|
71
|
+
export declare function equal(p1: Point, p2: Point): boolean;
|
|
72
|
+
/** Canonical 64-byte encoding of a point (affine x‖y), the input to the OT key-derivation hash. */
|
|
73
|
+
export declare function encodePoint(p: Point): Uint8Array;
|
|
74
|
+
/** Raised when a received point fails on-curve / subgroup / identity validation. */
|
|
75
|
+
export declare class PointValidationError extends Error {
|
|
76
|
+
constructor(message: string);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Whether `p`'s extended coordinates lie on the Ed25519 curve. Checks projectively (no inversion):
|
|
80
|
+
* - `Z ≠ 0` (a valid projective point),
|
|
81
|
+
* - the T-invariant `T·Z ≡ X·Y`,
|
|
82
|
+
* - the twisted-Edwards equation (`a = −1`) cleared of denominators: `(Y² − X²)·Z² ≡ Z⁴ + d·X²·Y²`.
|
|
83
|
+
* An off-curve / malformed point fails here before any scalar work.
|
|
84
|
+
*/
|
|
85
|
+
export declare function isOnCurve(p: Point): boolean;
|
|
86
|
+
/** Whether `p` is the identity (neutral) element (affine `(0, 1)`). */
|
|
87
|
+
export declare function isIdentity(p: Point): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* The cofactor check: whether `[L]·P = O`, i.e. `p` lies in the prime-order-`L` subgroup. For an
|
|
90
|
+
* on-curve point this is true iff `p` has order dividing `L`; since `L` is prime this means order `1`
|
|
91
|
+
* (identity) or order `L`. So on-curve ∧ `hasPrimeOrder` ∧ ¬identity ⟺ a genuine prime-order point.
|
|
92
|
+
* Rejects every small-order point (order | 8) and every mixed-order point (order `h·L`, `h | 8`, `h>1`).
|
|
93
|
+
*/
|
|
94
|
+
export declare function hasPrimeOrder(p: Point): boolean;
|
|
95
|
+
/** Whether `p` is a valid non-identity point of the prime-order subgroup (the OT point contract). */
|
|
96
|
+
export declare function isInSubgroup(p: Point): boolean;
|
|
97
|
+
/** Assert `p` is a valid non-identity prime-order-subgroup point, else throw `PointValidationError`. */
|
|
98
|
+
export declare function assertInSubgroup(p: Point, label?: string): void;
|
|
99
|
+
/** A Fiat–Shamir Schnorr proof of knowledge of `y` s.t. `S = y·B`: commitment `U = r·B` and `z = r + e·y`. */
|
|
100
|
+
export interface SchnorrProof {
|
|
101
|
+
U: Point;
|
|
102
|
+
z: bigint;
|
|
103
|
+
}
|
|
104
|
+
/** Prove knowledge of `y` with `S = y·B`. `rnd` draws the (secret) nonce `r ∈ [1, L)`. */
|
|
105
|
+
export declare function schnorrProve(y: bigint, S: Point, rnd: () => bigint): SchnorrProof;
|
|
106
|
+
/**
|
|
107
|
+
* Verify a Schnorr proof for `S`: `z` is in range, `U` is a valid subgroup point, and `z·B = U + e·S`
|
|
108
|
+
* for the recomputed challenge `e`. Returns false on any failure (the OT layer turns that into an abort).
|
|
109
|
+
*/
|
|
110
|
+
export declare function schnorrVerify(S: Point, proof: SchnorrProof): boolean;
|