444 lines
20 KiB
JavaScript
444 lines
20 KiB
JavaScript
/**
|
||
* ML-KEM: Module Lattice-based Key Encapsulation Mechanism from
|
||
* [FIPS-203](https://csrc.nist.gov/pubs/fips/203/ipd). A.k.a. CRYSTALS-Kyber.
|
||
*
|
||
* Key encapsulation is similar to DH / ECDH (think X25519), with important differences:
|
||
* * Unlike in ECDH, we can't verify if it was "Bob" who've sent the shared secret
|
||
* * Unlike ECDH, it is probabalistic and relies on quality of randomness (CSPRNG).
|
||
* * Decapsulation never throws an error, even when shared secret was
|
||
* encrypted by a different public key. It will just return a different shared secret.
|
||
*
|
||
* There are some concerns with regards to security: see
|
||
* [djb blog](https://blog.cr.yp.to/20231003-countcorrectly.html) and
|
||
* [mailing list](https://groups.google.com/a/list.nist.gov/g/pqc-forum/c/W2VOzy0wz_E).
|
||
*
|
||
* Has similar internals to ML-DSA, but their keys and params are different.
|
||
*
|
||
* Check out [official site](https://www.pq-crystals.org/kyber/resources.shtml),
|
||
* [repo](https://github.com/pq-crystals/kyber),
|
||
* [spec](https://datatracker.ietf.org/doc/draft-cfrg-schwabe-kyber/).
|
||
* @module
|
||
*/
|
||
/*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
|
||
import { sha3_256, sha3_512, shake256 } from '@noble/hashes/sha3.js';
|
||
import { swap32IfBE, u32 } from '@noble/hashes/utils.js';
|
||
import { genCrystals, XOF128 } from "./_crystals.js";
|
||
import { abytes, cleanBytes, copyBytes, equalBytes, getMask, randomBytes, splitCoder, vecCoder, } from "./utils.js";
|
||
/** Key encapsulation mechanism interface */
|
||
const N = 256; // Kyber (not FIPS-203) supports different lengths, but all std modes were using 256
|
||
const Q = 3329; // 13*(2**8)+1, modulo prime
|
||
const F = 3303; // 3303 ≡ 128**(−1) mod q (FIPS-203)
|
||
const ROOT_OF_UNITY = 17; // ζ = 17 ∈ Zq is a primitive 256-th root of unity modulo Q. ζ**128 ≡−1
|
||
// treeshake: keep genCrystals behind the object so PARAMS-only bundles can drop it entirely.
|
||
// Shared CRYSTALS helper in the ML-KEM branch: Kyber mode, 7-bit bit-reversal,
|
||
// and Uint16Array polys because current coefficients stay reduced modulo q.
|
||
const crystals = /* @__PURE__ */ genCrystals({
|
||
N,
|
||
Q,
|
||
F,
|
||
ROOT_OF_UNITY,
|
||
newPoly: (n) => new Uint16Array(n),
|
||
brvBits: 7,
|
||
isKyber: true,
|
||
});
|
||
/** Internal params of ML-KEM versions */
|
||
// prettier-ignore
|
||
/** Built-in ML-KEM parameter presets keyed by the public export names
|
||
* `ml_kem512` / `ml_kem768` / `ml_kem1024`.
|
||
* `RBGstrength` is Table 2's required randomness-source strength in bits,
|
||
* not a generic security label.
|
||
*/
|
||
export const PARAMS = /* @__PURE__ */ (() => Object.freeze({
|
||
512: Object.freeze({ N, Q, K: 2, ETA1: 3, ETA2: 2, du: 10, dv: 4, RBGstrength: 128 }),
|
||
768: Object.freeze({ N, Q, K: 3, ETA1: 2, ETA2: 2, du: 10, dv: 4, RBGstrength: 192 }),
|
||
1024: Object.freeze({ N, Q, K: 4, ETA1: 2, ETA2: 2, du: 11, dv: 5, RBGstrength: 256 }),
|
||
}))();
|
||
// FIPS-203: compress/decompress
|
||
const compress = (d) => {
|
||
// d=12 is the ByteEncode12/ByteDecode12 path, not lossy compression.
|
||
// ByteDecode12 interprets each 12-bit word modulo q; without that reduction the public-key
|
||
// modulus check in encapsulate() becomes a no-op for malformed coefficients like 4095.
|
||
if (d >= 12)
|
||
return { encode: (i) => i, decode: (i) => (i >= Q ? i - Q : i) };
|
||
// Comments map to python implementation in RFC (draft-cfrg-schwabe-kyber)
|
||
// const round = (i: number) => Math.floor(i + 0.5) | 0;
|
||
const a = 2 ** (d - 1);
|
||
return {
|
||
// This only matches standalone Compress_d after bitsCoder masks the result into Z_(2^d).
|
||
encode: (i) => ((i << d) + Q / 2) / Q,
|
||
// const decompress = (i: number) => round((Q / 2 ** d) * i);
|
||
decode: (i) => (i * Q + a) >>> d,
|
||
};
|
||
};
|
||
// Raw ByteEncode_d / ByteDecode_d from FIPS 203 operate on d-bit words directly.
|
||
// That differs from `polyCoder(d)` for d<12, where noble folds packing together with the lossy
|
||
// ciphertext compression step used by u/v. Tests that exercise the spec's raw packing surface need
|
||
// this exact non-lossy variant instead.
|
||
const byteCoder = (d) => crystals.bitsCoder(d, d === 12
|
||
? { encode: (i) => i, decode: (i) => (i >= Q ? i - Q : i) }
|
||
: { encode: (i) => i, decode: (i) => i });
|
||
// NOTE: we merge encoding and compress because it is faster, also both require same d param
|
||
// d=12 is the ByteEncode12/ByteDecode12 path rather than compression, and caller-side
|
||
// public-key modulus checks route through this helper's decode/encode roundtrip.
|
||
// Converts between bytes and d-bits compressed representation.
|
||
// Kinda like convertRadix2 from @scure/base.
|
||
// decode(encode(t)) == t, but there is loss of information on encode(decode(t))
|
||
const polyCoder = (d) => (d === 12 ? byteCoder(12) : crystals.bitsCoder(d, compress(d)));
|
||
function polyAdd(a_, b_) {
|
||
const a = a_;
|
||
const b = b_;
|
||
// Mutates `a` in place; callers must pass two N=256 polynomials.
|
||
for (let i = 0; i < N; i++)
|
||
a[i] = crystals.mod(a[i] + b[i]); // a += b
|
||
}
|
||
function polySub(a_, b_) {
|
||
const a = a_;
|
||
const b = b_;
|
||
// Mutates `a` in place; callers must pass two N=256 polynomials.
|
||
for (let i = 0; i < N; i++)
|
||
a[i] = crystals.mod(a[i] - b[i]); // a -= b
|
||
}
|
||
// FIPS-203: Computes the product of two degree-one polynomials with respect to a quadratic modulus
|
||
function BaseCaseMultiply(a0, a1, b0, b1, zeta) {
|
||
// `zeta` here is Algorithm 11's γ = ζ^(2BitRev_7(i)+1).
|
||
const c0 = crystals.mod(a1 * b1 * zeta + a0 * b0);
|
||
const c1 = crystals.mod(a0 * b1 + a1 * b0);
|
||
return { c0, c1 };
|
||
}
|
||
// FIPS-203: Computes the product (in the ring Tq) of two NTT representations.
|
||
// Works in place on `f`; `g` is read-only and both inputs must already be in NTT form.
|
||
function MultiplyNTTs(f_, g_) {
|
||
const f = f_;
|
||
const g = g_;
|
||
for (let i = 0; i < N / 2; i++) {
|
||
let z = crystals.nttZetas[64 + (i >> 1)];
|
||
if (i & 1)
|
||
z = -z;
|
||
const { c0, c1 } = BaseCaseMultiply(f[2 * i + 0], f[2 * i + 1], g[2 * i + 0], g[2 * i + 1], z);
|
||
f[2 * i + 0] = c0;
|
||
f[2 * i + 1] = c1;
|
||
}
|
||
return f;
|
||
}
|
||
// Return poly in NTT representation
|
||
function SampleNTT(xof_) {
|
||
const xof = xof_;
|
||
// The reader must already bind the Algorithm 7 seed||j||i bytes
|
||
// and return block lengths divisible by 3.
|
||
const r = new Uint16Array(N);
|
||
for (let j = 0; j < N;) {
|
||
const b = xof();
|
||
if (b.length % 3)
|
||
throw new Error('SampleNTT: unaligned block');
|
||
for (let i = 0; j < N && i + 3 <= b.length; i += 3) {
|
||
const d1 = ((b[i + 0] >> 0) | (b[i + 1] << 8)) & 0xfff;
|
||
const d2 = ((b[i + 1] >> 4) | (b[i + 2] << 4)) & 0xfff;
|
||
if (d1 < Q)
|
||
r[j++] = d1;
|
||
if (j < N && d2 < Q)
|
||
r[j++] = d2;
|
||
}
|
||
}
|
||
return r;
|
||
}
|
||
// Sampling from the centered binomial distribution
|
||
// Returns poly with small coefficients (noise/errors) stored modulo q in ordinary coefficient form.
|
||
// Current callers only use Table 2 eta values {2,3} and PRF outputs of exactly 64*eta bytes.
|
||
const sampleCBDBytes = (buf, eta) => {
|
||
const r = new Uint16Array(N);
|
||
// CBD consumes the PRF bitstream in little-endian byte order; normalize the word view on BE,
|
||
// then swap it back so callers still observe `buf` as read-only.
|
||
const b32 = u32(buf);
|
||
swap32IfBE(b32);
|
||
let len = 0;
|
||
for (let i = 0, p = 0, bb = 0, t0 = 0; i < b32.length; i++) {
|
||
let b = b32[i];
|
||
for (let j = 0; j < 32; j++) {
|
||
bb += b & 1;
|
||
b >>= 1;
|
||
len += 1;
|
||
if (len === eta) {
|
||
t0 = bb;
|
||
bb = 0;
|
||
}
|
||
else if (len === 2 * eta) {
|
||
r[p++] = crystals.mod(t0 - bb);
|
||
bb = 0;
|
||
len = 0;
|
||
}
|
||
}
|
||
}
|
||
swap32IfBE(b32);
|
||
if (len)
|
||
throw new Error(`sampleCBD: leftover bits: ${len}`);
|
||
return r;
|
||
};
|
||
function sampleCBD(PRF_, seed, nonce, eta) {
|
||
const PRF = PRF_;
|
||
return sampleCBDBytes(PRF((eta * N) / 4, seed, nonce), eta);
|
||
}
|
||
// K-PKE
|
||
// Internal ML-KEM subroutine only: exact 32-byte `seed` / `msg` inputs
|
||
// come from Algorithms 13-15, and the helper mutates decoded temporary
|
||
// polynomials in place while leaving caller byte arrays unchanged.
|
||
const genKPKE = (opts_) => {
|
||
const opts = opts_;
|
||
const { K, PRF, XOF, HASH512, ETA1, ETA2, du, dv } = opts;
|
||
const poly1 = polyCoder(1);
|
||
const polyV = polyCoder(dv);
|
||
const polyU = polyCoder(du);
|
||
const publicCoder = splitCoder('publicKey', vecCoder(polyCoder(12), K), 32);
|
||
const secretCoder = vecCoder(polyCoder(12), K);
|
||
const cipherCoder = splitCoder('ciphertext', vecCoder(polyU, K), polyV);
|
||
const seedCoder = splitCoder('seed', 32, 32);
|
||
return {
|
||
secretCoder,
|
||
lengths: {
|
||
secretKey: secretCoder.bytesLen,
|
||
publicKey: publicCoder.bytesLen,
|
||
cipherText: cipherCoder.bytesLen,
|
||
},
|
||
keygen: (seed) => {
|
||
abytes(seed, 32, 'seed');
|
||
const seedDst = new Uint8Array(33);
|
||
seedDst.set(seed);
|
||
// FIPS 203 Algorithm 13 appends the parameter-set byte `k`
|
||
// before `G(d || k)`, so expanding the same 32-byte seed
|
||
// under a different ML-KEM parameter set yields unrelated keys.
|
||
seedDst[32] = K;
|
||
const seedHash = HASH512(seedDst);
|
||
const [rho, sigma] = seedCoder.decode(seedHash);
|
||
const sHat = [];
|
||
const tHat = [];
|
||
for (let i = 0; i < K; i++)
|
||
sHat.push(crystals.NTT.encode(sampleCBD(PRF, sigma, i, ETA1)));
|
||
const x = XOF(rho);
|
||
for (let i = 0; i < K; i++) {
|
||
const e = crystals.NTT.encode(sampleCBD(PRF, sigma, K + i, ETA1));
|
||
for (let j = 0; j < K; j++) {
|
||
const aji = SampleNTT(x.get(j, i)); // A[i][j], inplace
|
||
polyAdd(e, MultiplyNTTs(aji, sHat[j]));
|
||
}
|
||
tHat.push(e); // t ← A ◦ s + e
|
||
}
|
||
x.clean();
|
||
const res = {
|
||
publicKey: publicCoder.encode([tHat, rho]),
|
||
secretKey: secretCoder.encode(sHat),
|
||
};
|
||
cleanBytes(rho, sigma, sHat, tHat, seedDst, seedHash);
|
||
return res;
|
||
},
|
||
encrypt: (publicKey, msg, seed) => {
|
||
const [tHat, rho] = publicCoder.decode(publicKey);
|
||
const rHat = [];
|
||
for (let i = 0; i < K; i++)
|
||
rHat.push(crystals.NTT.encode(sampleCBD(PRF, seed, i, ETA1)));
|
||
const x = XOF(rho);
|
||
const tmp2 = new Uint16Array(N);
|
||
const u = [];
|
||
for (let i = 0; i < K; i++) {
|
||
const e1 = sampleCBD(PRF, seed, K + i, ETA2);
|
||
const tmp = new Uint16Array(N);
|
||
for (let j = 0; j < K; j++) {
|
||
const aij = SampleNTT(x.get(i, j)); // A[j][i], inplace transpose access
|
||
polyAdd(tmp, MultiplyNTTs(aij, rHat[j])); // t += aij * rHat[j]
|
||
}
|
||
polyAdd(e1, crystals.NTT.decode(tmp)); // e1 += tmp
|
||
u.push(e1);
|
||
polyAdd(tmp2, MultiplyNTTs(tHat[i], rHat[i])); // t2 += tHat[i] * rHat[i]
|
||
cleanBytes(tmp);
|
||
}
|
||
x.clean();
|
||
const e2 = sampleCBD(PRF, seed, 2 * K, ETA2);
|
||
polyAdd(e2, crystals.NTT.decode(tmp2)); // e2 += tmp2
|
||
const v = poly1.decode(msg); // encode plaintext m into polynomial v
|
||
polyAdd(v, e2); // v += e2
|
||
cleanBytes(tHat, rHat, tmp2, e2);
|
||
return cipherCoder.encode([u, v]);
|
||
},
|
||
decrypt: (cipherText, privateKey) => {
|
||
const [u, v] = cipherCoder.decode(cipherText);
|
||
const sk = secretCoder.decode(privateKey); // s ← ByteDecode_12(dkPKE)
|
||
const tmp = new Uint16Array(N);
|
||
// tmp += sk[i] * u[i]
|
||
for (let i = 0; i < K; i++)
|
||
polyAdd(tmp, MultiplyNTTs(sk[i], crystals.NTT.encode(u[i])));
|
||
polySub(v, crystals.NTT.decode(tmp)); // w = v' - tmp
|
||
cleanBytes(tmp, sk, u);
|
||
return poly1.encode(v);
|
||
},
|
||
};
|
||
};
|
||
/**
|
||
* Public ML-KEM wrapper over the internal K-PKE subroutine.
|
||
* `keygen(seed)` and `encapsulate(publicKey, msg)` are deterministic/test-oriented hooks that map
|
||
* more directly to Algorithms 16-17 than to the pure no-input / random-internal Algorithms 19-20.
|
||
* decapsulate() tries to follow the Algorithms 18/21 implicit-reject structure as closely as
|
||
* practical here by re-encrypting, comparing ciphertexts, returning `Khat` on match or `Kbar` on
|
||
* mismatch, and zeroizing the non-returned shared-secret candidate; JS/JIT still provides no
|
||
* constant-time guarantees for that path.
|
||
*/
|
||
function createKyber(opts) {
|
||
const rawOpts = opts;
|
||
const KPKE = genKPKE(rawOpts);
|
||
const { HASH256, HASH512, KDF } = rawOpts;
|
||
const { secretCoder: KPKESecretCoder, lengths } = KPKE;
|
||
const secretCoder = splitCoder('secretKey', lengths.secretKey, lengths.publicKey, 32, 32);
|
||
const msgLen = 32;
|
||
const seedLen = 64;
|
||
const kemLengths = Object.freeze({
|
||
...lengths,
|
||
seed: 64,
|
||
msg: msgLen,
|
||
msgRand: msgLen,
|
||
secretKey: secretCoder.bytesLen,
|
||
});
|
||
return Object.freeze({
|
||
info: Object.freeze({ type: 'ml-kem' }),
|
||
lengths: kemLengths,
|
||
keygen: (seed = randomBytes(seedLen)) => {
|
||
abytes(seed, seedLen, 'seed');
|
||
const { publicKey, secretKey: sk } = KPKE.keygen(seed.subarray(0, 32));
|
||
const publicKeyHash = HASH256(publicKey);
|
||
// (dkPKE||ek||H(ek)||z)
|
||
const secretKey = secretCoder.encode([sk, publicKey, publicKeyHash, seed.subarray(32)]);
|
||
cleanBytes(sk, publicKeyHash);
|
||
return {
|
||
publicKey: publicKey,
|
||
secretKey: secretKey,
|
||
};
|
||
},
|
||
getPublicKey: (secretKey) => {
|
||
const [_sk, publicKey, _publicKeyHash, _z] = secretCoder.decode(secretKey);
|
||
return Uint8Array.from(publicKey);
|
||
},
|
||
encapsulate: (publicKey, msg = randomBytes(msgLen)) => {
|
||
abytes(publicKey, lengths.publicKey, 'publicKey');
|
||
abytes(msg, msgLen, 'message');
|
||
// FIPS-203 includes additional verification check for modulus
|
||
const eke = publicKey.subarray(0, 384 * opts.K);
|
||
// Copy because of inplace encoding
|
||
const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(copyBytes(eke)));
|
||
// (Modulus check.) Perform the computation ek ← ByteEncode12(ByteDecode12(eke)).
|
||
// If ek = ̸ eke, the input is invalid. (See Section 4.2.1.)
|
||
if (!equalBytes(ek, eke)) {
|
||
cleanBytes(ek);
|
||
throw new Error('ML-KEM.encapsulate: wrong publicKey modulus');
|
||
}
|
||
cleanBytes(ek);
|
||
// derive randomness
|
||
const kr = HASH512.create().update(msg).update(HASH256(publicKey)).digest();
|
||
const cipherText = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64));
|
||
cleanBytes(kr.subarray(32));
|
||
return {
|
||
cipherText: cipherText,
|
||
sharedSecret: kr.subarray(0, 32),
|
||
};
|
||
},
|
||
decapsulate: (cipherText, secretKey) => {
|
||
abytes(secretKey, secretCoder.bytesLen, 'secretKey'); // 768*k + 96
|
||
abytes(cipherText, lengths.cipherText, 'cipherText'); // 32(du*k + dv)
|
||
// test ← H(dk[384𝑘 ∶ 768𝑘 + 32])) .
|
||
const k768 = secretCoder.bytesLen - 96;
|
||
const start = k768 + 32;
|
||
const test = HASH256(secretKey.subarray(k768 / 2, start));
|
||
// If test ≠ dk[768𝑘 + 32 ∶ 768𝑘 + 64], then input checking has failed.
|
||
if (!equalBytes(test, secretKey.subarray(start, start + 32)))
|
||
throw new Error('invalid secretKey: hash check failed');
|
||
const [sk, publicKey, publicKeyHash, z] = secretCoder.decode(secretKey);
|
||
const msg = KPKE.decrypt(cipherText, sk);
|
||
// derive randomness, Khat, rHat = G(mHat || h)
|
||
const kr = HASH512.create().update(msg).update(publicKeyHash).digest();
|
||
const Khat = kr.subarray(0, 32);
|
||
// re-encrypt using the derived randomness
|
||
const cipherText2 = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64));
|
||
// if ciphertexts do not match, “implicitly reject”
|
||
const isValid = equalBytes(cipherText, cipherText2);
|
||
const Kbar = KDF.create({ dkLen: 32 }).update(z).update(cipherText).digest();
|
||
cleanBytes(msg, cipherText2, !isValid ? Khat : Kbar);
|
||
return (isValid ? Khat : Kbar);
|
||
},
|
||
});
|
||
}
|
||
// FIPS 203's PRF_eta binding: current callers use only 32-byte keys, one-byte nonces,
|
||
// and dkLen values {128, 192}; out-of-range nonce numbers still wrap modulo 256 here.
|
||
function shakePRF(dkLen, key, nonce) {
|
||
return shake256
|
||
.create({ dkLen })
|
||
.update(key)
|
||
.update(new Uint8Array([nonce]))
|
||
.digest();
|
||
}
|
||
// Fixed ML-KEM hash/XOF bindings. `KDF` here is the spec's fixed 32-byte `J` call,
|
||
// and swapping any field changes the scheme rather than tuning an internal dependency.
|
||
const opts = /* @__PURE__ */ (() => ({
|
||
HASH256: sha3_256,
|
||
HASH512: sha3_512,
|
||
KDF: shake256,
|
||
XOF: XOF128,
|
||
PRF: shakePRF,
|
||
}))();
|
||
// Parameter-set instantiation step for the spec's "ML-KEM-x" names; current correctness relies
|
||
// on the internal PARAMS rows rather than local validation of arbitrary KEMParam objects.
|
||
const mk = (params) => createKyber({
|
||
...opts,
|
||
...params,
|
||
});
|
||
/**
|
||
* ML-KEM-512: Table 2 row `k=2, η1=3, η2=2, du=10, dv=4`; Table 3 sizes `800/1632/768/32`.
|
||
* The ASD lifecycle note here is external policy guidance, not a FIPS 203 requirement.
|
||
*/
|
||
export const ml_kem512 = /* @__PURE__ */ (() => mk(PARAMS[512]))();
|
||
/**
|
||
* ML-KEM-768: Table 2 row `k=3, η1=2, η2=2, du=10, dv=4`; Table 3 sizes `1184/2400/1088/32`.
|
||
* The ASD lifecycle note here is external policy guidance, not a FIPS 203 requirement.
|
||
*/
|
||
export const ml_kem768 = /* @__PURE__ */ (() => mk(PARAMS[768]))();
|
||
/**
|
||
* ML-KEM-1024: Table 2 row `k=4, η1=2, η2=2, du=11, dv=5`; Table 3 sizes `1568/3168/1568/32`.
|
||
* The ASD lifecycle note here is external policy guidance, not a FIPS 203 requirement.
|
||
*/
|
||
export const ml_kem1024 = /* @__PURE__ */ (() => mk(PARAMS[1024]))();
|
||
// NOTE: for tests only, don't use. This keeps the exact internal ML-KEM math surfaces available
|
||
// without re-implementing them in separate test code.
|
||
export const __tests = /* @__PURE__ */ (() => Object.freeze({
|
||
Compress_d: (x, d) => {
|
||
if (d < 1 || d > 11)
|
||
throw new Error(`Compress_d: expected d in [1..11], got ${d}`);
|
||
return compress(d).encode(x) & getMask(d);
|
||
},
|
||
Decompress_d: (y, d) => {
|
||
if (d < 1 || d > 11)
|
||
throw new Error(`Decompress_d: expected d in [1..11], got ${d}`);
|
||
return compress(d).decode(y);
|
||
},
|
||
ByteEncode_d: (F, d) => {
|
||
if (d < 1 || d > 12)
|
||
throw new Error(`ByteEncode_d: expected d in [1..12], got ${d}`);
|
||
return byteCoder(d).encode(F);
|
||
},
|
||
ByteDecode_d: (B, d) => {
|
||
if (d < 1 || d > 12)
|
||
throw new Error(`ByteDecode_d: expected d in [1..12], got ${d}`);
|
||
return byteCoder(d).decode(B);
|
||
},
|
||
NTT: (f) => crystals.NTT.encode(Uint16Array.from(f)),
|
||
NTT_inv: (fHat) => crystals.NTT.decode(Uint16Array.from(fHat)),
|
||
MultiplyNTTs: (fHat, gHat) => MultiplyNTTs(Uint16Array.from(fHat), Uint16Array.from(gHat)),
|
||
SamplePolyCBD: (B, eta) => {
|
||
abytes(B, 64 * eta, 'B');
|
||
return sampleCBDBytes(B, eta);
|
||
},
|
||
SampleNTT: (B) => {
|
||
abytes(B, 34, 'B');
|
||
const xof = XOF128(B.subarray(0, 32));
|
||
try {
|
||
return SampleNTT(xof.get(B[32], B[33]));
|
||
}
|
||
finally {
|
||
xof.clean();
|
||
}
|
||
},
|
||
}))();
|
||
//# sourceMappingURL=ml-kem.js.map
|