@atlasauth/pca-mpc 0.1.0 → 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.
- package/LICENSE +21 -0
- package/README.md +50 -15
- package/dist/ec.d.ts +38 -18
- package/dist/ec.js +187 -18
- package/dist/ec.js.map +1 -1
- package/dist/endemic-ot.d.ts +209 -0
- package/dist/endemic-ot.js +405 -0
- package/dist/endemic-ot.js.map +1 -0
- package/dist/hybrid-ot.d.ts +153 -0
- package/dist/hybrid-ot.js +241 -0
- package/dist/hybrid-ot.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/kem-ot.d.ts +167 -12
- package/dist/kem-ot.js +347 -12
- package/dist/kem-ot.js.map +1 -1
- package/package.json +31 -4
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid base OT — a SOUND combiner of the malicious-secure EC base OT (`ot.ts`/`ec.ts`) and the
|
|
3
|
+
* post-quantum committed ML-KEM base OT (`kem-ot.ts`), run together so the stack inherits the BEST
|
|
4
|
+
* guarantee of each half instead of the weakest.
|
|
5
|
+
*
|
|
6
|
+
* ─── WHY A COMBINER ────────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
* The two base OTs this package ships have complementary weaknesses:
|
|
8
|
+
* • The EC base OT (`malBaseRandomOT`) is MALICIOUS-secure against both roles — a Schnorr NIZK proof
|
|
9
|
+
* of knowledge of `y` on `S = y·B`, full prime-order-subgroup validation of every transmitted
|
|
10
|
+
* point, and a random-OT core with no sender-chosen ciphertexts — but its hardness is the curve25519
|
|
11
|
+
* discrete log, which a QUANTUM adversary breaks with Shor.
|
|
12
|
+
* • The ML-KEM base OT (`kemBaseOtMalRandom`) is POST-QUANTUM (Module-LWE) and has unconditional,
|
|
13
|
+
* even-against-a-quantum-sender choice privacy, but against an actively-malicious receiver it has an
|
|
14
|
+
* IRREDUCIBLE residue from the opaque primitive: a receiver that runs keygen twice and keeps BOTH
|
|
15
|
+
* secret keys learns both messages, and the decisional-MLWE indistinguishability that gives choice
|
|
16
|
+
* privacy makes that cheat undetectable to the sender (see `kem-ot.ts` header).
|
|
17
|
+
*
|
|
18
|
+
* The combiner runs BOTH base OTs on the SAME choice bit and the SAME session, then derives each
|
|
19
|
+
* transfer key from BOTH halves' shared secrets:
|
|
20
|
+
*
|
|
21
|
+
* k_b = H( DOMAIN_SEP ‖ sid ‖ i ‖ b ‖ ss_ec_b ‖ ss_kem_b ) for b ∈ {0, 1}
|
|
22
|
+
*
|
|
23
|
+
* where `ss_ec_b` is the EC random-OT pad (`malBaseRandomOT`) and `ss_kem_b` the committed ML-KEM
|
|
24
|
+
* random-OT secret (`kemBaseOtMalRandom`, the hardened variant — never the semi-honest one). The sender
|
|
25
|
+
* one-time-pads `m_b` under `k_b` and commits to it (`com_b`); the honest receiver reconstructs only
|
|
26
|
+
* `k_c` (it holds only `ss_ec_c` and `ss_kem_c`) and checks `com_c`.
|
|
27
|
+
*
|
|
28
|
+
* ─── WHAT THIS ACHIEVES (precise) ───────────────────────────────────────────────────────────────────
|
|
29
|
+
* To recover `m_{1−c}` an adversary needs `k_{1−c}`, hence BOTH `ss_ec_{1−c}` AND `ss_kem_{1−c}` (the
|
|
30
|
+
* hash is a random oracle; missing either input leaves the pad pseudorandom). Therefore:
|
|
31
|
+
* • vs a CLASSICAL malicious receiver — MALICIOUS-secure. Even a receiver that defeats the KEM half
|
|
32
|
+
* entirely by keeping both ML-KEM secrets (so it holds `ss_kem_{1−c}`) still cannot obtain
|
|
33
|
+
* `ss_ec_{1−c}`: the EC base OT is malicious-secure and `ss_ec_{1−c} = H(y·R − T)` is a point the
|
|
34
|
+
* receiver cannot compute without the discrete log of `S`. No `ss_ec_{1−c}` ⇒ no `k_{1−c}` ⇒ `m_{1−c}`
|
|
35
|
+
* stays hidden. This is the KILLER property the tests prove.
|
|
36
|
+
* • SENDER PRIVACY vs a SEMI-HONEST QUANTUM receiver — holds via the KEM half (Module-LWE): a quantum
|
|
37
|
+
* receiver that follows the protocol (discards the non-chosen material) cannot get `ss_kem_{1−c}`,
|
|
38
|
+
* so again `k_{1−c}` is unobtainable even though it could break the EC half with Shor.
|
|
39
|
+
* • CHOICE PRIVACY — POST-QUANTUM. The KEM half's choice privacy is unconditional (two i.i.d. real
|
|
40
|
+
* ML-KEM public keys on the wire); the EC half's is information-theoretic in its transcript (`R` is a
|
|
41
|
+
* uniform subgroup element whatever `c` is). Neither leaks `c`, against any adversary.
|
|
42
|
+
* • SESSION BINDING — both halves are bound to a COMMON `sid` derived from BOTH sub-protocols'
|
|
43
|
+
* session material (the KEM `(rn, sn)` nonces and the EC point `S`), and `sid` is hashed into every
|
|
44
|
+
* transfer key and commitment, so secrets from one session/opened-key-set cannot be replayed into
|
|
45
|
+
* another. The KEM half additionally aborts (`KemBaseOtAbort`) if the receiver opens keys other than
|
|
46
|
+
* the ones it committed to; the EC half aborts (`BaseOtAbort`/`PointValidationError`) on a bad proof
|
|
47
|
+
* or an off-/small-order point; and the combiner aborts (`HybridOtAbort`) if a tampered ciphertext
|
|
48
|
+
* fails its commitment.
|
|
49
|
+
*
|
|
50
|
+
* ─── THE HONEST BOUNDARY (NOT faked) ────────────────────────────────────────────────────────────────
|
|
51
|
+
* The one residue this combiner does NOT close is a receiver that is SIMULTANEOUSLY actively-malicious
|
|
52
|
+
* AND quantum: such a receiver could break the classical EC half with Shor (recovering `ss_ec_{1−c}`
|
|
53
|
+
* from the transcript) WHILE keeping both ML-KEM secrets (recovering `ss_kem_{1−c}`), and so assemble
|
|
54
|
+
* `k_{1−c}`. Closing THAT requires a base OT that is BOTH post-quantum AND malicious-secure in one
|
|
55
|
+
* primitive — i.e. an endemic/UC OT from Module-LWE with a CRS, which needs direct lattice module
|
|
56
|
+
* arithmetic (sampling / adding in ML-KEM's structured public-key space to plant a secretless slot).
|
|
57
|
+
* The opaque `ml_kem768` keygen/encaps/decaps interface this package is restricted to does NOT expose
|
|
58
|
+
* that arithmetic, and hand-rolling it is exactly the lattice crypto the module forbids. So this is the
|
|
59
|
+
* honest limit of what a KEM-opaque + EC combiner can reach; it is stated, not papered over.
|
|
60
|
+
*
|
|
61
|
+
* No hand-rolled crypto: the EC half goes through `malBaseRandomOT` (unchanged), the KEM half through
|
|
62
|
+
* `kemBaseOtMalRandom` (unchanged), and this module only hashes (SHA-256 RO) and XORs their outputs.
|
|
63
|
+
*/
|
|
64
|
+
import { type Point } from './ec';
|
|
65
|
+
import { type BaseOtTamper, type OtChannel, type OtRandom } from './ot';
|
|
66
|
+
import { type KemMalTamper } from './kem-ot';
|
|
67
|
+
/** Raised when the hybrid combiner aborts: a sender ciphertext inconsistent with its commitment is caught. */
|
|
68
|
+
export declare class HybridOtAbort extends Error {
|
|
69
|
+
constructor(message: string);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The COMMON session id binding both halves: `H(TAG ‖ kemSid ‖ encode(ecS))`. It folds the KEM
|
|
73
|
+
* sub-protocol's two-nonce session id (`rn, sn`) and the EC sub-protocol's point `S`, so neither party
|
|
74
|
+
* alone fixes it and secrets cannot be mixed across sessions. Both parties derive the same value from
|
|
75
|
+
* the public transcript.
|
|
76
|
+
*/
|
|
77
|
+
export declare function hybridSessionId(kemSid: Uint8Array, ecS: Point): Uint8Array;
|
|
78
|
+
/**
|
|
79
|
+
* The hybrid transfer key for one slot: `H(TAG ‖ sid ‖ i ‖ slot ‖ ss_ec ‖ ss_kem)` expanded (SHA-256
|
|
80
|
+
* counter mode) to `len` bytes. Modelled as a random oracle: the key is pseudorandom unless BOTH
|
|
81
|
+
* `ss_ec` and `ss_kem` (for that exact slot/index/session) are supplied — the soundness crux of the
|
|
82
|
+
* combiner.
|
|
83
|
+
*/
|
|
84
|
+
export declare function hybridTransferKey(sid: Uint8Array, index: number, slot: number, ssEc: Uint8Array, ssKem: Uint8Array, len: number): Uint8Array;
|
|
85
|
+
/** The sender's wire message of the hybrid combiner: one-time-padded messages + per-message commitments. */
|
|
86
|
+
export interface HybridSenderMessage {
|
|
87
|
+
/** For each transfer i, `[E_0, E_1]` with `E_b = m_b ⊕ k_b`. */
|
|
88
|
+
encrypted: Array<[Uint8Array, Uint8Array]>;
|
|
89
|
+
/** For each transfer i, `[com_0, com_1]` with `com_b = H(sid ‖ i ‖ b ‖ m_b ‖ k_b)`. */
|
|
90
|
+
commitments: Array<[Uint8Array, Uint8Array]>;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* SENDER side of the combiner: given BOTH halves' per-slot shared secrets, derive both transfer keys,
|
|
94
|
+
* one-time-pad each message, and commit to each. `ecSecrets[i] = [ss_ec_0, ss_ec_1]` (the EC random-OT
|
|
95
|
+
* `senderPads`), `kemSecrets[i] = [ss_kem_0, ss_kem_1]` (the committed-KEM `senderSecrets`).
|
|
96
|
+
*/
|
|
97
|
+
export declare function hybridSenderEncrypt(sid: Uint8Array, messages: Array<[Uint8Array, Uint8Array]>, ecSecrets: Array<[Uint8Array, Uint8Array]>, kemSecrets: Array<[Uint8Array, Uint8Array]>): HybridSenderMessage;
|
|
98
|
+
/**
|
|
99
|
+
* RECEIVER side of the combiner: reconstruct ONLY the chosen transfer key (from the chosen EC and KEM
|
|
100
|
+
* secrets the receiver legitimately holds), unmask the chosen message, and CHECK the commitment.
|
|
101
|
+
* `ecRecv[i] = ss_ec_{choices[i]}` (the EC `recvPads`), `kemRecv[i] = ss_kem_{choices[i]}` (the
|
|
102
|
+
* committed-KEM `recvSecrets`). Aborts (`HybridOtAbort`) if the recovered message fails its commitment.
|
|
103
|
+
*/
|
|
104
|
+
export declare function hybridReceiverRecover(sid: Uint8Array, choices: number[], sender: HybridSenderMessage, ecRecv: Uint8Array[], kemRecv: Uint8Array[]): Uint8Array[];
|
|
105
|
+
/**
|
|
106
|
+
* TEST-ONLY injection points modelling malicious behaviour in the hybrid combiner. The `ec` / `kem`
|
|
107
|
+
* bags are forwarded to the respective sub-OTs (so each half's real malicious checks run against the
|
|
108
|
+
* adversarial value); `tamperEncrypted` corrupts the hybrid sender's ciphertexts on the wire (caught by
|
|
109
|
+
* the commitment check).
|
|
110
|
+
*/
|
|
111
|
+
export interface HybridBaseOtTamper {
|
|
112
|
+
/** Forwarded to the EC half (`malBaseRandomOT`): malicious sender/receiver on the EC sub-OT. */
|
|
113
|
+
ec?: BaseOtTamper;
|
|
114
|
+
/** Forwarded to the KEM half (`kemBaseOtMalRandom`): malicious receiver on the committed KEM sub-OT. */
|
|
115
|
+
kem?: KemMalTamper;
|
|
116
|
+
/** Malicious hybrid SENDER: corrupt the padded messages on the wire (inconsistent with the commitment). */
|
|
117
|
+
tamperEncrypted?: (encrypted: Array<[Uint8Array, Uint8Array]>) => void;
|
|
118
|
+
}
|
|
119
|
+
/** A full hybrid base-OT transcript, exposed for inspection/tests. */
|
|
120
|
+
export interface HybridBaseOtTranscript {
|
|
121
|
+
/** The common session id both halves are bound to. */
|
|
122
|
+
sid: Uint8Array;
|
|
123
|
+
/** The EC sub-OT's public point `S` (with its Schnorr proof available via the EC half). */
|
|
124
|
+
ecS: Point;
|
|
125
|
+
/** The committed-KEM sub-OT session id `H(rn ‖ sn)`. */
|
|
126
|
+
kemSid: Uint8Array;
|
|
127
|
+
encrypted: Array<[Uint8Array, Uint8Array]>;
|
|
128
|
+
commitments: Array<[Uint8Array, Uint8Array]>;
|
|
129
|
+
}
|
|
130
|
+
export interface HybridBaseOtResult {
|
|
131
|
+
/** Receiver's recovered messages `m_{i, choices[i]}`. */
|
|
132
|
+
received: Uint8Array[];
|
|
133
|
+
transcript: HybridBaseOtTranscript;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* In-process combined simulator (both parties in one call), matching the shape of `baseOT`/`kemBaseOT`
|
|
137
|
+
* so it is a drop-in for `OtChannel`'s `baseOtFn` (`withHybridBaseOt`). Runs the malicious-secure EC
|
|
138
|
+
* base OT and the committed ML-KEM base OT on the SAME choices, binds both to a common `sid`, and
|
|
139
|
+
* combines their per-slot secrets into the transfer keys. A cheating party is caught by whichever
|
|
140
|
+
* half's check applies (EC proof/point validation, KEM commitment, or the hybrid ciphertext
|
|
141
|
+
* commitment). `tamper` injects malicious behaviour for tests.
|
|
142
|
+
*/
|
|
143
|
+
export declare function hybridBaseOT(messages: Array<[Uint8Array, Uint8Array]>, choices: number[], rng?: OtRandom, tamper?: HybridBaseOtTamper): HybridBaseOtResult;
|
|
144
|
+
/**
|
|
145
|
+
* Wire the HYBRID base OT into the KOS OT-extension: a drop-in for `withKemBaseOt`/`withKemBaseOtMal`
|
|
146
|
+
* that makes `OtChannel` run its κ setup base OTs over the combiner. The extension inherits the
|
|
147
|
+
* combiner's guarantee — malicious security vs a classical receiver, PQ sender-privacy vs a semi-honest
|
|
148
|
+
* quantum one, and PQ choice privacy — with everything above the base OT (IKNP matrix, KOS correlation
|
|
149
|
+
* check, correlation-robust hash) unchanged.
|
|
150
|
+
*
|
|
151
|
+
* const ch = new OtChannel(secureOtRandom(), withHybridBaseOt());
|
|
152
|
+
*/
|
|
153
|
+
export declare function withHybridBaseOt(): ConstructorParameters<typeof OtChannel>[1];
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Hybrid base OT — a SOUND combiner of the malicious-secure EC base OT (`ot.ts`/`ec.ts`) and the
|
|
4
|
+
* post-quantum committed ML-KEM base OT (`kem-ot.ts`), run together so the stack inherits the BEST
|
|
5
|
+
* guarantee of each half instead of the weakest.
|
|
6
|
+
*
|
|
7
|
+
* ─── WHY A COMBINER ────────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
* The two base OTs this package ships have complementary weaknesses:
|
|
9
|
+
* • The EC base OT (`malBaseRandomOT`) is MALICIOUS-secure against both roles — a Schnorr NIZK proof
|
|
10
|
+
* of knowledge of `y` on `S = y·B`, full prime-order-subgroup validation of every transmitted
|
|
11
|
+
* point, and a random-OT core with no sender-chosen ciphertexts — but its hardness is the curve25519
|
|
12
|
+
* discrete log, which a QUANTUM adversary breaks with Shor.
|
|
13
|
+
* • The ML-KEM base OT (`kemBaseOtMalRandom`) is POST-QUANTUM (Module-LWE) and has unconditional,
|
|
14
|
+
* even-against-a-quantum-sender choice privacy, but against an actively-malicious receiver it has an
|
|
15
|
+
* IRREDUCIBLE residue from the opaque primitive: a receiver that runs keygen twice and keeps BOTH
|
|
16
|
+
* secret keys learns both messages, and the decisional-MLWE indistinguishability that gives choice
|
|
17
|
+
* privacy makes that cheat undetectable to the sender (see `kem-ot.ts` header).
|
|
18
|
+
*
|
|
19
|
+
* The combiner runs BOTH base OTs on the SAME choice bit and the SAME session, then derives each
|
|
20
|
+
* transfer key from BOTH halves' shared secrets:
|
|
21
|
+
*
|
|
22
|
+
* k_b = H( DOMAIN_SEP ‖ sid ‖ i ‖ b ‖ ss_ec_b ‖ ss_kem_b ) for b ∈ {0, 1}
|
|
23
|
+
*
|
|
24
|
+
* where `ss_ec_b` is the EC random-OT pad (`malBaseRandomOT`) and `ss_kem_b` the committed ML-KEM
|
|
25
|
+
* random-OT secret (`kemBaseOtMalRandom`, the hardened variant — never the semi-honest one). The sender
|
|
26
|
+
* one-time-pads `m_b` under `k_b` and commits to it (`com_b`); the honest receiver reconstructs only
|
|
27
|
+
* `k_c` (it holds only `ss_ec_c` and `ss_kem_c`) and checks `com_c`.
|
|
28
|
+
*
|
|
29
|
+
* ─── WHAT THIS ACHIEVES (precise) ───────────────────────────────────────────────────────────────────
|
|
30
|
+
* To recover `m_{1−c}` an adversary needs `k_{1−c}`, hence BOTH `ss_ec_{1−c}` AND `ss_kem_{1−c}` (the
|
|
31
|
+
* hash is a random oracle; missing either input leaves the pad pseudorandom). Therefore:
|
|
32
|
+
* • vs a CLASSICAL malicious receiver — MALICIOUS-secure. Even a receiver that defeats the KEM half
|
|
33
|
+
* entirely by keeping both ML-KEM secrets (so it holds `ss_kem_{1−c}`) still cannot obtain
|
|
34
|
+
* `ss_ec_{1−c}`: the EC base OT is malicious-secure and `ss_ec_{1−c} = H(y·R − T)` is a point the
|
|
35
|
+
* receiver cannot compute without the discrete log of `S`. No `ss_ec_{1−c}` ⇒ no `k_{1−c}` ⇒ `m_{1−c}`
|
|
36
|
+
* stays hidden. This is the KILLER property the tests prove.
|
|
37
|
+
* • SENDER PRIVACY vs a SEMI-HONEST QUANTUM receiver — holds via the KEM half (Module-LWE): a quantum
|
|
38
|
+
* receiver that follows the protocol (discards the non-chosen material) cannot get `ss_kem_{1−c}`,
|
|
39
|
+
* so again `k_{1−c}` is unobtainable even though it could break the EC half with Shor.
|
|
40
|
+
* • CHOICE PRIVACY — POST-QUANTUM. The KEM half's choice privacy is unconditional (two i.i.d. real
|
|
41
|
+
* ML-KEM public keys on the wire); the EC half's is information-theoretic in its transcript (`R` is a
|
|
42
|
+
* uniform subgroup element whatever `c` is). Neither leaks `c`, against any adversary.
|
|
43
|
+
* • SESSION BINDING — both halves are bound to a COMMON `sid` derived from BOTH sub-protocols'
|
|
44
|
+
* session material (the KEM `(rn, sn)` nonces and the EC point `S`), and `sid` is hashed into every
|
|
45
|
+
* transfer key and commitment, so secrets from one session/opened-key-set cannot be replayed into
|
|
46
|
+
* another. The KEM half additionally aborts (`KemBaseOtAbort`) if the receiver opens keys other than
|
|
47
|
+
* the ones it committed to; the EC half aborts (`BaseOtAbort`/`PointValidationError`) on a bad proof
|
|
48
|
+
* or an off-/small-order point; and the combiner aborts (`HybridOtAbort`) if a tampered ciphertext
|
|
49
|
+
* fails its commitment.
|
|
50
|
+
*
|
|
51
|
+
* ─── THE HONEST BOUNDARY (NOT faked) ────────────────────────────────────────────────────────────────
|
|
52
|
+
* The one residue this combiner does NOT close is a receiver that is SIMULTANEOUSLY actively-malicious
|
|
53
|
+
* AND quantum: such a receiver could break the classical EC half with Shor (recovering `ss_ec_{1−c}`
|
|
54
|
+
* from the transcript) WHILE keeping both ML-KEM secrets (recovering `ss_kem_{1−c}`), and so assemble
|
|
55
|
+
* `k_{1−c}`. Closing THAT requires a base OT that is BOTH post-quantum AND malicious-secure in one
|
|
56
|
+
* primitive — i.e. an endemic/UC OT from Module-LWE with a CRS, which needs direct lattice module
|
|
57
|
+
* arithmetic (sampling / adding in ML-KEM's structured public-key space to plant a secretless slot).
|
|
58
|
+
* The opaque `ml_kem768` keygen/encaps/decaps interface this package is restricted to does NOT expose
|
|
59
|
+
* that arithmetic, and hand-rolling it is exactly the lattice crypto the module forbids. So this is the
|
|
60
|
+
* honest limit of what a KEM-opaque + EC combiner can reach; it is stated, not papered over.
|
|
61
|
+
*
|
|
62
|
+
* No hand-rolled crypto: the EC half goes through `malBaseRandomOT` (unchanged), the KEM half through
|
|
63
|
+
* `kemBaseOtMalRandom` (unchanged), and this module only hashes (SHA-256 RO) and XORs their outputs.
|
|
64
|
+
*/
|
|
65
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
66
|
+
exports.HybridOtAbort = void 0;
|
|
67
|
+
exports.hybridSessionId = hybridSessionId;
|
|
68
|
+
exports.hybridTransferKey = hybridTransferKey;
|
|
69
|
+
exports.hybridSenderEncrypt = hybridSenderEncrypt;
|
|
70
|
+
exports.hybridReceiverRecover = hybridReceiverRecover;
|
|
71
|
+
exports.hybridBaseOT = hybridBaseOT;
|
|
72
|
+
exports.withHybridBaseOt = withHybridBaseOt;
|
|
73
|
+
const node_crypto_1 = require("node:crypto");
|
|
74
|
+
const ec_1 = require("./ec");
|
|
75
|
+
const ot_1 = require("./ot");
|
|
76
|
+
const kem_ot_1 = require("./kem-ot");
|
|
77
|
+
const csprng_1 = require("./csprng");
|
|
78
|
+
const TAG_HYBRID_SID = new TextEncoder().encode('atlas-pca-mpc/hybrid-ot-sid');
|
|
79
|
+
const TAG_HYBRID_KEY = new TextEncoder().encode('atlas-pca-mpc/hybrid-ot-transfer-key');
|
|
80
|
+
const TAG_HYBRID_COMMIT = new TextEncoder().encode('atlas-pca-mpc/hybrid-ot-msg-commit');
|
|
81
|
+
function sha256(...chunks) {
|
|
82
|
+
const h = (0, node_crypto_1.createHash)('sha256');
|
|
83
|
+
for (const c of chunks)
|
|
84
|
+
h.update(c);
|
|
85
|
+
return new Uint8Array(h.digest());
|
|
86
|
+
}
|
|
87
|
+
function u32(x) {
|
|
88
|
+
const b = new Uint8Array(4);
|
|
89
|
+
b[0] = (x >>> 24) & 0xff;
|
|
90
|
+
b[1] = (x >>> 16) & 0xff;
|
|
91
|
+
b[2] = (x >>> 8) & 0xff;
|
|
92
|
+
b[3] = x & 0xff;
|
|
93
|
+
return b;
|
|
94
|
+
}
|
|
95
|
+
function xorBytes(a, b) {
|
|
96
|
+
if (a.length !== b.length)
|
|
97
|
+
throw new Error('hybrid-ot: XOR length mismatch');
|
|
98
|
+
const out = new Uint8Array(a.length);
|
|
99
|
+
for (let i = 0; i < a.length; i++)
|
|
100
|
+
out[i] = a[i] ^ b[i];
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
function ctEqual(a, b) {
|
|
104
|
+
return a.length === b.length && (0, node_crypto_1.timingSafeEqual)(Buffer.from(a), Buffer.from(b));
|
|
105
|
+
}
|
|
106
|
+
/** Raised when the hybrid combiner aborts: a sender ciphertext inconsistent with its commitment is caught. */
|
|
107
|
+
class HybridOtAbort extends Error {
|
|
108
|
+
constructor(message) {
|
|
109
|
+
super(message);
|
|
110
|
+
this.name = 'HybridOtAbort';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.HybridOtAbort = HybridOtAbort;
|
|
114
|
+
/**
|
|
115
|
+
* The COMMON session id binding both halves: `H(TAG ‖ kemSid ‖ encode(ecS))`. It folds the KEM
|
|
116
|
+
* sub-protocol's two-nonce session id (`rn, sn`) and the EC sub-protocol's point `S`, so neither party
|
|
117
|
+
* alone fixes it and secrets cannot be mixed across sessions. Both parties derive the same value from
|
|
118
|
+
* the public transcript.
|
|
119
|
+
*/
|
|
120
|
+
function hybridSessionId(kemSid, ecS) {
|
|
121
|
+
return sha256(TAG_HYBRID_SID, kemSid, (0, ec_1.encodePoint)(ecS));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The hybrid transfer key for one slot: `H(TAG ‖ sid ‖ i ‖ slot ‖ ss_ec ‖ ss_kem)` expanded (SHA-256
|
|
125
|
+
* counter mode) to `len` bytes. Modelled as a random oracle: the key is pseudorandom unless BOTH
|
|
126
|
+
* `ss_ec` and `ss_kem` (for that exact slot/index/session) are supplied — the soundness crux of the
|
|
127
|
+
* combiner.
|
|
128
|
+
*/
|
|
129
|
+
function hybridTransferKey(sid, index, slot, ssEc, ssKem, len) {
|
|
130
|
+
const out = new Uint8Array(len);
|
|
131
|
+
let produced = 0;
|
|
132
|
+
let ctr = 0;
|
|
133
|
+
while (produced < len) {
|
|
134
|
+
const block = sha256(TAG_HYBRID_KEY, sid, u32(index), Uint8Array.of(slot & 1), u32(ctr), ssEc, ssKem);
|
|
135
|
+
for (let k = 0; k < block.length && produced < len; k++)
|
|
136
|
+
out[produced++] = block[k];
|
|
137
|
+
ctr++;
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
/** Per-message RO commitment `com = H(TAG ‖ sid ‖ i ‖ slot ‖ msg ‖ key)`, bound to the session. */
|
|
142
|
+
function hybridCommit(sid, index, slot, msg, key) {
|
|
143
|
+
return sha256(TAG_HYBRID_COMMIT, sid, u32(index), Uint8Array.of(slot & 1), msg, key);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* SENDER side of the combiner: given BOTH halves' per-slot shared secrets, derive both transfer keys,
|
|
147
|
+
* one-time-pad each message, and commit to each. `ecSecrets[i] = [ss_ec_0, ss_ec_1]` (the EC random-OT
|
|
148
|
+
* `senderPads`), `kemSecrets[i] = [ss_kem_0, ss_kem_1]` (the committed-KEM `senderSecrets`).
|
|
149
|
+
*/
|
|
150
|
+
function hybridSenderEncrypt(sid, messages, ecSecrets, kemSecrets) {
|
|
151
|
+
if (messages.length !== ecSecrets.length || messages.length !== kemSecrets.length) {
|
|
152
|
+
throw new Error('hybridSenderEncrypt: messages/ecSecrets/kemSecrets length mismatch');
|
|
153
|
+
}
|
|
154
|
+
const encrypted = [];
|
|
155
|
+
const commitments = [];
|
|
156
|
+
for (let i = 0; i < messages.length; i++) {
|
|
157
|
+
const [m0, m1] = messages[i];
|
|
158
|
+
if (m0.length !== m1.length) {
|
|
159
|
+
throw new Error(`hybridSenderEncrypt (index ${i}): the two messages must be equal length`);
|
|
160
|
+
}
|
|
161
|
+
const k0 = hybridTransferKey(sid, i, 0, ecSecrets[i][0], kemSecrets[i][0], m0.length);
|
|
162
|
+
const k1 = hybridTransferKey(sid, i, 1, ecSecrets[i][1], kemSecrets[i][1], m1.length);
|
|
163
|
+
encrypted.push([xorBytes(m0, k0), xorBytes(m1, k1)]);
|
|
164
|
+
commitments.push([hybridCommit(sid, i, 0, m0, k0), hybridCommit(sid, i, 1, m1, k1)]);
|
|
165
|
+
}
|
|
166
|
+
return { encrypted, commitments };
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* RECEIVER side of the combiner: reconstruct ONLY the chosen transfer key (from the chosen EC and KEM
|
|
170
|
+
* secrets the receiver legitimately holds), unmask the chosen message, and CHECK the commitment.
|
|
171
|
+
* `ecRecv[i] = ss_ec_{choices[i]}` (the EC `recvPads`), `kemRecv[i] = ss_kem_{choices[i]}` (the
|
|
172
|
+
* committed-KEM `recvSecrets`). Aborts (`HybridOtAbort`) if the recovered message fails its commitment.
|
|
173
|
+
*/
|
|
174
|
+
function hybridReceiverRecover(sid, choices, sender, ecRecv, kemRecv) {
|
|
175
|
+
const { encrypted, commitments } = sender;
|
|
176
|
+
if (encrypted.length !== choices.length ||
|
|
177
|
+
commitments.length !== choices.length ||
|
|
178
|
+
ecRecv.length !== choices.length ||
|
|
179
|
+
kemRecv.length !== choices.length) {
|
|
180
|
+
throw new Error('hybridReceiverRecover: length mismatch');
|
|
181
|
+
}
|
|
182
|
+
const received = [];
|
|
183
|
+
for (let i = 0; i < choices.length; i++) {
|
|
184
|
+
const c = choices[i] & 1;
|
|
185
|
+
const e = encrypted[i][c];
|
|
186
|
+
const key = hybridTransferKey(sid, i, c, ecRecv[i], kemRecv[i], e.length);
|
|
187
|
+
const m = xorBytes(e, key);
|
|
188
|
+
const check = hybridCommit(sid, i, c, m, key);
|
|
189
|
+
if (!ctEqual(check, commitments[i][c])) {
|
|
190
|
+
throw new HybridOtAbort(`hybrid base OT (index ${i}): sender ciphertext inconsistent with its commitment; aborting`);
|
|
191
|
+
}
|
|
192
|
+
received.push(m);
|
|
193
|
+
}
|
|
194
|
+
return received;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* In-process combined simulator (both parties in one call), matching the shape of `baseOT`/`kemBaseOT`
|
|
198
|
+
* so it is a drop-in for `OtChannel`'s `baseOtFn` (`withHybridBaseOt`). Runs the malicious-secure EC
|
|
199
|
+
* base OT and the committed ML-KEM base OT on the SAME choices, binds both to a common `sid`, and
|
|
200
|
+
* combines their per-slot secrets into the transfer keys. A cheating party is caught by whichever
|
|
201
|
+
* half's check applies (EC proof/point validation, KEM commitment, or the hybrid ciphertext
|
|
202
|
+
* commitment). `tamper` injects malicious behaviour for tests.
|
|
203
|
+
*/
|
|
204
|
+
function hybridBaseOT(messages, choices, rng = (0, csprng_1.secureOtRandom)(), tamper) {
|
|
205
|
+
if (messages.length !== choices.length) {
|
|
206
|
+
throw new Error('hybridBaseOT: messages/choices length mismatch');
|
|
207
|
+
}
|
|
208
|
+
// EC half: malicious-secure random OT with ALL checks ON (Schnorr proof + subgroup validation).
|
|
209
|
+
const ec = (0, ot_1.malBaseRandomOT)(choices, rng, tamper?.ec);
|
|
210
|
+
// KEM half: the committed/session-bound random OT (aborts on an opened-key/commitment mismatch).
|
|
211
|
+
const kem = (0, kem_ot_1.kemBaseOtMalRandom)(choices, rng, tamper?.kem);
|
|
212
|
+
const sid = hybridSessionId(kem.sid, ec.S);
|
|
213
|
+
const sender = hybridSenderEncrypt(sid, messages, ec.senderPads, kem.senderSecrets);
|
|
214
|
+
// A malicious hybrid SENDER may corrupt the ciphertexts on the wire (test-injected).
|
|
215
|
+
if (tamper?.tamperEncrypted)
|
|
216
|
+
tamper.tamperEncrypted(sender.encrypted);
|
|
217
|
+
const received = hybridReceiverRecover(sid, choices, sender, ec.recvPads, kem.recvSecrets);
|
|
218
|
+
return {
|
|
219
|
+
received,
|
|
220
|
+
transcript: {
|
|
221
|
+
sid,
|
|
222
|
+
ecS: ec.S,
|
|
223
|
+
kemSid: kem.sid,
|
|
224
|
+
encrypted: sender.encrypted,
|
|
225
|
+
commitments: sender.commitments,
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Wire the HYBRID base OT into the KOS OT-extension: a drop-in for `withKemBaseOt`/`withKemBaseOtMal`
|
|
231
|
+
* that makes `OtChannel` run its κ setup base OTs over the combiner. The extension inherits the
|
|
232
|
+
* combiner's guarantee — malicious security vs a classical receiver, PQ sender-privacy vs a semi-honest
|
|
233
|
+
* quantum one, and PQ choice privacy — with everything above the base OT (IKNP matrix, KOS correlation
|
|
234
|
+
* check, correlation-robust hash) unchanged.
|
|
235
|
+
*
|
|
236
|
+
* const ch = new OtChannel(secureOtRandom(), withHybridBaseOt());
|
|
237
|
+
*/
|
|
238
|
+
function withHybridBaseOt() {
|
|
239
|
+
return { baseOtFn: (m, c, r) => ({ received: hybridBaseOT(m, c, r).received }) };
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=hybrid-ot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hybrid-ot.js","sourceRoot":"","sources":["../src/hybrid-ot.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -40,5 +40,7 @@ __exportStar(require("./gf128"), exports);
|
|
|
40
40
|
__exportStar(require("./csprng"), exports);
|
|
41
41
|
__exportStar(require("./ot"), exports);
|
|
42
42
|
__exportStar(require("./kem-ot"), exports);
|
|
43
|
+
__exportStar(require("./hybrid-ot"), exports);
|
|
43
44
|
__exportStar(require("./mascot"), exports);
|
|
45
|
+
__exportStar(require("./endemic-ot"), exports);
|
|
44
46
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;;;;;;;;;;;;;;AAEH,0CAAwB;AACxB,4CAA0B;AAC1B,2CAAyB;AACzB,gDAA8B;AAC9B,0CAAwB;AACxB,4CAA0B;AAC1B,2CAAyB;AACzB,yCAAuB;AACvB,gDAA8B;AAC9B,uCAAqB;AACrB,0CAAwB;AACxB,2CAAyB;AACzB,uCAAqB;AACrB,2CAAyB;AACzB,2CAAyB"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;;;;;;;;;;;;;;AAEH,0CAAwB;AACxB,4CAA0B;AAC1B,2CAAyB;AACzB,gDAA8B;AAC9B,0CAAwB;AACxB,4CAA0B;AAC1B,2CAAyB;AACzB,yCAAuB;AACvB,gDAA8B;AAC9B,uCAAqB;AACrB,0CAAwB;AACxB,2CAAyB;AACzB,uCAAqB;AACrB,2CAAyB;AACzB,8CAA4B;AAC5B,2CAAyB;AACzB,+CAA6B"}
|
package/dist/kem-ot.d.ts
CHANGED
|
@@ -43,18 +43,41 @@
|
|
|
43
43
|
* rests on Module-LWE (ML-KEM-768, NIST security level ~3) and a hash in the ROM — no classical
|
|
44
44
|
* discrete-log anywhere, which is the entire point of P3.
|
|
45
45
|
*
|
|
46
|
-
* ───
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
46
|
+
* ─── MALICIOUS-RECEIVER HARDENING (`kemBaseOtMal`, below) ──────────────────────────────────────────
|
|
47
|
+
* The semi-honest base OT above leaves two malicious-receiver surfaces. The committed variant
|
|
48
|
+
* `kemBaseOtMal` closes the one that CAN be closed from an opaque KEM, and documents — precisely,
|
|
49
|
+
* rather than faking a defence — the one that cannot:
|
|
50
|
+
*
|
|
51
|
+
* CLOSED (by an RO-commitment + session binding, mirroring the EC `baseOT`'s commit round):
|
|
52
|
+
* • ADAPTIVE / MALLEABLE / REPLAYED key material. The receiver first sends only a RO-commitment
|
|
53
|
+
* `com = H(rn ‖ pk_0 ‖ pk_1)` to its ordered public keys (and a receiver nonce `rn`); the sender
|
|
54
|
+
* replies with a fresh nonce `sn`; the parties set `sid = H(rn ‖ sn)`; only THEN does the receiver
|
|
55
|
+
* open `(pk_0, pk_1)` and the sender encapsulate, binding every pad to `(sid, com)`. The sender
|
|
56
|
+
* recomputes `com` from the opened keys and ABORTS on any mismatch. So the receiver must fix its
|
|
57
|
+
* keys BEFORE it learns `sn` (no adaptive/precomputed/related-key choice, no cross-session replay),
|
|
58
|
+
* and cannot open keys other than the ones it committed to — exactly the non-malleability the EC
|
|
59
|
+
* base OT gets from `R` being a single committed group element.
|
|
60
|
+
*
|
|
61
|
+
* NOT CLOSABLE from an OPAQUE ML-KEM, and WHY (an impossibility, not a shortcut):
|
|
62
|
+
* • A receiver that simply runs keygen TWICE and keeps BOTH secret keys learns both messages. To
|
|
63
|
+
* stop it the protocol would have to force one slot to a public key whose secret NOBODY knows
|
|
64
|
+
* (the Masny–Rindal "endemic OT" idea: `pk_{1−c}` derived in the public-key space from a random
|
|
65
|
+
* oracle / a common reference). That needs either (i) sampling / adding in ML-KEM's structured
|
|
66
|
+
* public-key space — reaching into the Module-LWE internals this module forbids (no hand-rolled
|
|
67
|
+
* lattice crypto), or (ii) a trusted setup / CRS. Crucially, the SAME decisional-Module-LWE
|
|
68
|
+
* indistinguishability that makes choice privacy hold (a real `pk` ≈ a uniform one) is what makes
|
|
69
|
+
* a cheating "both-keys-real" receiver UNDETECTABLE to the sender: it cannot tell a secretless
|
|
70
|
+
* slot from a real one. So this residue is intrinsic to the opaque primitive.
|
|
71
|
+
*
|
|
72
|
+
* ─── LIFTING THE RESIDUE (how this plugs into the stack) ───────────────────────────────────────────
|
|
73
|
+
* The residue is closed exactly where the EC stack closes its analogue: the KOS OT-extension (`ot.ts`).
|
|
74
|
+
* KOS runs the base OTs with ROLES INVERTED (the extension *sender* is the base *receiver*, holding the
|
|
75
|
+
* secret selector s) and adds the correlation check over sacrifice rows. A base receiver keeping both
|
|
76
|
+
* secrets maps to the extension *sender* learning the extension *receiver*'s seed pairs; the KOS
|
|
77
|
+
* consistency machinery + correlation-robust hash constrain that, raising the overall extension to
|
|
78
|
+
* malicious-with-abort against a cheating extension receiver — the attack IKNP is vulnerable to. So
|
|
79
|
+
* `kemBaseOtMal` is the right base layer: it hardens everything the opaque KEM allows (commitment /
|
|
80
|
+
* binding), and defers only the intrinsic residue to the layer that already handles it.
|
|
58
81
|
*
|
|
59
82
|
* No hand-rolled lattice crypto: all PQ operations go through `ml_kem768` (keygen/encapsulate/
|
|
60
83
|
* decapsulate) as a black box. Randomness follows the module policy — a CSPRNG (`secureOtRandom`) by
|
|
@@ -135,3 +158,135 @@ export declare function kemBaseOT(messages: Array<[Uint8Array, Uint8Array]>, cho
|
|
|
135
158
|
* const ch = new OtChannel(secureOtRandom(), withKemBaseOt());
|
|
136
159
|
*/
|
|
137
160
|
export declare function withKemBaseOt(): ConstructorParameters<typeof OtChannel>[1];
|
|
161
|
+
/** Raised when the committed KEM base OT aborts: a cheating receiver (opened keys ≠ commitment) is caught. */
|
|
162
|
+
export declare class KemBaseOtAbort extends Error {
|
|
163
|
+
constructor(message: string);
|
|
164
|
+
}
|
|
165
|
+
/** Receiver round-1 message: a nonce + the per-transfer commitments (public keys NOT yet revealed). */
|
|
166
|
+
export interface KemMalCommitMessage {
|
|
167
|
+
/** Receiver nonce `rn`, bound into every commitment and into `sid`. */
|
|
168
|
+
rn: Uint8Array;
|
|
169
|
+
/** Per-transfer RO-commitment `com_i = H(rn ‖ i ‖ pk_0 ‖ pk_1)`. */
|
|
170
|
+
commitments: Uint8Array[];
|
|
171
|
+
}
|
|
172
|
+
/** Receiver round-3 message: the opening of the committed public keys. */
|
|
173
|
+
export interface KemMalOpenMessage {
|
|
174
|
+
/** For each transfer i, the ordered public keys `[pk_0, pk_1]` the sender encapsulates under. */
|
|
175
|
+
publicKeys: Array<[Uint8Array, Uint8Array]>;
|
|
176
|
+
}
|
|
177
|
+
/** Sender round-2 message: the sender nonce `sn` completing the session id. */
|
|
178
|
+
export interface KemMalSenderNonce {
|
|
179
|
+
sn: Uint8Array;
|
|
180
|
+
}
|
|
181
|
+
/** Sender round-4 message: ciphertexts + `(sid, com)`-bound padded messages. */
|
|
182
|
+
export interface KemMalSenderMessage {
|
|
183
|
+
ciphertexts: Array<[Uint8Array, Uint8Array]>;
|
|
184
|
+
encrypted: Array<[Uint8Array, Uint8Array]>;
|
|
185
|
+
}
|
|
186
|
+
/** The receiver's retained state across the committed flow. */
|
|
187
|
+
export interface KemMalReceiverState {
|
|
188
|
+
choices: number[];
|
|
189
|
+
/** The chosen secret key per transfer (`sk_{choices[i]}`); the non-chosen secret is discarded. */
|
|
190
|
+
secretKeys: Uint8Array[];
|
|
191
|
+
/** The ordered public keys (opened in round 3). */
|
|
192
|
+
publicKeys: Array<[Uint8Array, Uint8Array]>;
|
|
193
|
+
rn: Uint8Array;
|
|
194
|
+
}
|
|
195
|
+
/** A full committed KEM base-OT transcript, exposed for inspection/tests. */
|
|
196
|
+
export interface KemMalTranscript extends KemMalCommitMessage, KemMalOpenMessage, KemMalSenderNonce, KemMalSenderMessage {
|
|
197
|
+
}
|
|
198
|
+
export interface KemBaseOtMalResult {
|
|
199
|
+
received: Uint8Array[];
|
|
200
|
+
transcript: KemMalTranscript;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* TEST-ONLY injection points modelling a malicious RECEIVER in the committed flow. Each hook mutates a
|
|
204
|
+
* value exactly where it crosses the wire, so the honest sender's checks run against the adversarial
|
|
205
|
+
* value.
|
|
206
|
+
*/
|
|
207
|
+
export interface KemMalTamper {
|
|
208
|
+
/** Malicious receiver: open public keys DIFFERENT from the committed ones (→ sender abort). */
|
|
209
|
+
tamperOpenedKeys?: (publicKeys: Array<[Uint8Array, Uint8Array]>) => void;
|
|
210
|
+
/** Malicious receiver/MITM: tamper the sender nonce the receiver binds `sid` with (→ pad mismatch). */
|
|
211
|
+
tamperReceiverSid?: (sid: Uint8Array) => Uint8Array;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* RECEIVER round 1: generate the keypairs (keeping only the chosen secret), draw a nonce `rn`, and send
|
|
215
|
+
* only the COMMITMENTS to the ordered public keys. Returns the round-1 wire message plus the retained
|
|
216
|
+
* state (which holds the not-yet-revealed public keys for the round-3 opening).
|
|
217
|
+
*/
|
|
218
|
+
export declare function kemBaseOtMalCommit(choices: number[], rng?: OtRandom): {
|
|
219
|
+
message: KemMalCommitMessage;
|
|
220
|
+
state: KemMalReceiverState;
|
|
221
|
+
};
|
|
222
|
+
/** SENDER round 2: draw the sender nonce `sn` completing the session id. */
|
|
223
|
+
export declare function kemBaseOtMalSenderNonce(rng?: OtRandom): KemMalSenderNonce;
|
|
224
|
+
/** RECEIVER round 3: open the committed public keys (the sender will re-check them against `com`). */
|
|
225
|
+
export declare function kemBaseOtMalOpen(state: KemMalReceiverState): KemMalOpenMessage;
|
|
226
|
+
/**
|
|
227
|
+
* SENDER round 4: VERIFY the opened keys against the round-1 commitments (ABORT on mismatch), then
|
|
228
|
+
* encapsulate under each and one-time-pad each message with a `(sid, com)`-bound pad.
|
|
229
|
+
*/
|
|
230
|
+
export declare function kemBaseOtMalSend(messages: Array<[Uint8Array, Uint8Array]>, commit: KemMalCommitMessage, senderNonce: KemMalSenderNonce, opening: KemMalOpenMessage, rng?: OtRandom): KemMalSenderMessage;
|
|
231
|
+
/**
|
|
232
|
+
* RECEIVER round 5: decapsulate ONLY the chosen ciphertext and unmask with the `(sid, com)`-bound pad.
|
|
233
|
+
*/
|
|
234
|
+
export declare function kemBaseOtMalRecover(state: KemMalReceiverState, senderNonce: KemMalSenderNonce, senderMessage: KemMalSenderMessage, tamper?: KemMalTamper): Uint8Array[];
|
|
235
|
+
/**
|
|
236
|
+
* In-process combined simulator for the MALICIOUS-HARDENED committed KEM base OT, matching the shape of
|
|
237
|
+
* `kemBaseOT` so it is a drop-in for `OtChannel`'s `baseOtFn` (`withKemBaseOtMal`). Runs all five
|
|
238
|
+
* rounds; a cheating receiver (opened keys ≠ commitment) makes the sender throw `KemBaseOtAbort`. The
|
|
239
|
+
* `tamper` hook injects malicious-receiver behaviour for tests.
|
|
240
|
+
*/
|
|
241
|
+
export declare function kemBaseOtMal(messages: Array<[Uint8Array, Uint8Array]>, choices: number[], rng?: OtRandom, tamper?: KemMalTamper): KemBaseOtMalResult;
|
|
242
|
+
/**
|
|
243
|
+
* Wire the MALICIOUS-HARDENED committed ML-KEM base OT into the KOS OT-extension — the recommended
|
|
244
|
+
* post-quantum base. Like `withKemBaseOt`, but the κ setup base OTs run the committed/session-bound
|
|
245
|
+
* protocol (and abort on a cheating receiver). Everything above the base OT is unchanged.
|
|
246
|
+
*
|
|
247
|
+
* const ch = new OtChannel(secureOtRandom(), withKemBaseOtMal());
|
|
248
|
+
*/
|
|
249
|
+
export declare function withKemBaseOtMal(): ConstructorParameters<typeof OtChannel>[1];
|
|
250
|
+
/** Byte length of a derived committed-KEM random-OT shared secret. */
|
|
251
|
+
export declare const KEM_OT_SECRET_BYTES = 32;
|
|
252
|
+
/** Sender round-4 message of the committed KEM RANDOM OT: ciphertexts (wire) + both per-slot secrets (sender-local). */
|
|
253
|
+
export interface KemMalRandomSenderMessage {
|
|
254
|
+
/** For each transfer i, the ML-KEM ciphertexts `[ct_0, ct_1]` (the only part that crosses the wire). */
|
|
255
|
+
ciphertexts: Array<[Uint8Array, Uint8Array]>;
|
|
256
|
+
/** Sender-LOCAL: the two session-bound shared secrets `[ss_0, ss_1]` per transfer (never sent). */
|
|
257
|
+
senderSecrets: Array<[Uint8Array, Uint8Array]>;
|
|
258
|
+
}
|
|
259
|
+
/** A full committed KEM RANDOM-OT transcript + the two parties' shared-secret outputs. */
|
|
260
|
+
export interface KemBaseOtMalRandomResult {
|
|
261
|
+
/** Sender's per-slot shared secrets `[ss_0, ss_1]` per transfer (the sender learns BOTH). */
|
|
262
|
+
senderSecrets: Array<[Uint8Array, Uint8Array]>;
|
|
263
|
+
/** Receiver's chosen shared secret `ss_{choices[i]}` per transfer (the honest receiver learns ONE). */
|
|
264
|
+
recvSecrets: Uint8Array[];
|
|
265
|
+
/** The session id `sid = H(rn ‖ sn)` the secrets are bound to (public; both parties derive it). */
|
|
266
|
+
sid: Uint8Array;
|
|
267
|
+
transcript: {
|
|
268
|
+
rn: Uint8Array;
|
|
269
|
+
commitments: Uint8Array[];
|
|
270
|
+
sn: Uint8Array;
|
|
271
|
+
publicKeys: Array<[Uint8Array, Uint8Array]>;
|
|
272
|
+
ciphertexts: Array<[Uint8Array, Uint8Array]>;
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* SENDER round 4 (RANDOM variant): VERIFY the opened keys against the round-1 commitments (ABORT on
|
|
277
|
+
* mismatch), encapsulate under each, and derive BOTH per-slot session-bound shared secrets. No messages
|
|
278
|
+
* are padded — the two secrets per transfer ARE the random output.
|
|
279
|
+
*/
|
|
280
|
+
export declare function kemBaseOtMalRandomSend(commit: KemMalCommitMessage, senderNonce: KemMalSenderNonce, opening: KemMalOpenMessage, rng?: OtRandom): KemMalRandomSenderMessage;
|
|
281
|
+
/**
|
|
282
|
+
* RECEIVER round 5 (RANDOM variant): decapsulate ONLY the chosen ciphertext and derive the chosen
|
|
283
|
+
* session-bound shared secret. The receiver never touches the non-chosen slot (it kept no secret for it).
|
|
284
|
+
*/
|
|
285
|
+
export declare function kemBaseOtMalRandomRecover(state: KemMalReceiverState, senderNonce: KemMalSenderNonce, senderMessage: KemMalRandomSenderMessage, tamper?: KemMalTamper): Uint8Array[];
|
|
286
|
+
/**
|
|
287
|
+
* In-process combined simulator for the committed KEM RANDOM OT. Runs the same five rounds as
|
|
288
|
+
* `kemBaseOtMal` (commit → sender nonce → open → verify+encapsulate → recover) but outputs per-slot
|
|
289
|
+
* shared secrets instead of padded messages. A cheating receiver (opened keys ≠ commitment) makes the
|
|
290
|
+
* sender throw `KemBaseOtAbort`. The `tamper` hook injects malicious-receiver behaviour for tests.
|
|
291
|
+
*/
|
|
292
|
+
export declare function kemBaseOtMalRandom(choices: number[], rng?: OtRandom, tamper?: KemMalTamper): KemBaseOtMalRandomResult;
|