UNPKG

crystals-kyber-js

Version:

An ML-KEM/CRYSTALS-KYBER implementation written in TypeScript for various JavaScript runtimes

1,072 lines (1,071 loc) 43.9 kB
(function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports", "./deps.js", "./consts.js", "./utils.js"], factory); } })(function (require, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MlKemBase = void 0; /** * This implementation is based on https://github.com/antontutoveanu/crystals-kyber-javascript, * which was deveploped under the MIT licence below: * https://github.com/antontutoveanu/crystals-kyber-javascript/blob/main/LICENSE */ const deps_js_1 = require("./deps.js"); const consts_js_1 = require("./consts.js"); const utils_js_1 = require("./utils.js"); /** * Represents the base class for the ML-KEM key encapsulation mechanism. * * This class provides the base implementation for the ML-KEM key encapsulation mechanism. * * @remarks * * This class is not intended to be used directly. Instead, use one of the subclasses: * * @example * * ```ts * // Using jsr: * import { MlKemBase } from "@dajiaji/mlkem"; * // Using npm: * // import { MlKemBase } from "mlkem"; // or "crystals-kyber-js" * * class MlKem768 extends MlKemBase { * protected _k = 3; * protected _du = 10; * protected _dv = 4; * protected _eta1 = 2; * protected _eta2 = 2; * * constructor() { * super(); * this._skSize = 12 * this._k * N / 8; * this._pkSize = this._skSize + 32; * this._compressedUSize = this._k * this._du * N / 8; * this._compressedVSize = this._dv * N / 8; * } * } * * const kyber = new MlKem768(); * ``` */ class MlKemBase { /** * Creates a new instance of the MlKemBase class. */ constructor() { Object.defineProperty(this, "_api", { enumerable: true, configurable: true, writable: true, value: undefined }); Object.defineProperty(this, "_k", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_du", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_dv", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_eta1", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_eta2", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_skSize", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_pkSize", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_compressedUSize", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_compressedVSize", { enumerable: true, configurable: true, writable: true, value: 0 }); // Keccak pool instances (reset before each use) Object.defineProperty(this, "_poolG", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_poolH", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_poolKdf", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_poolXof", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_poolPrf1", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_poolPrf2", { enumerable: true, configurable: true, writable: true, value: void 0 }); // Pre-allocated output buffers Object.defineProperty(this, "_bufG", { enumerable: true, configurable: true, writable: true, value: new Uint8Array(64) }); Object.defineProperty(this, "_bufH", { enumerable: true, configurable: true, writable: true, value: new Uint8Array(32) }); Object.defineProperty(this, "_bufKdf", { enumerable: true, configurable: true, writable: true, value: new Uint8Array(32) }); Object.defineProperty(this, "_bufXof", { enumerable: true, configurable: true, writable: true, value: new Uint8Array(672) }); Object.defineProperty(this, "_bufPrf1", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_bufPrf2", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_nonceBuf", { enumerable: true, configurable: true, writable: true, value: new Uint8Array(1) }); Object.defineProperty(this, "_xofSeed", { enumerable: true, configurable: true, writable: true, value: new Uint8Array(34) }); Object.defineProperty(this, "_kBuf", { enumerable: true, configurable: true, writable: true, value: void 0 }); // Pre-allocated matrix A and noise polyvec slots Object.defineProperty(this, "_matrixA", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_noiseVecs", { enumerable: true, configurable: true, writable: true, value: void 0 }); // Pre-allocated polyvec for polyFromBytes (shared by _encap tHat / _polyvecFromBytes) Object.defineProperty(this, "_polyVec", { enumerable: true, configurable: true, writable: true, value: void 0 }); // Pre-allocated pk validation and ciphertext buffers Object.defineProperty(this, "_bufPkCheck", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_bufCt", { enumerable: true, configurable: true, writable: true, value: void 0 }); } _initPool() { // sha3_512: blockLen=72, suffix=0x06, outputLen=64 this._poolG = new deps_js_1.Keccak(72, 0x06, 64); // sha3_256: blockLen=136, suffix=0x06, outputLen=32 this._poolH = new deps_js_1.Keccak(136, 0x06, 32); // shake256: blockLen=136, suffix=0x1f, dkLen=32, enableXOF this._poolKdf = new deps_js_1.Keccak(136, 0x1f, 32, true); // shake128: blockLen=168, suffix=0x1f, dkLen=672, enableXOF this._poolXof = new deps_js_1.Keccak(168, 0x1f, 672, true); // shake256 for prf with eta1 const prf1Len = this._eta1 * consts_js_1.N / 4; this._poolPrf1 = new deps_js_1.Keccak(136, 0x1f, prf1Len, true); this._bufPrf1 = new Uint8Array(prf1Len); // shake256 for prf with eta2 const prf2Len = this._eta2 * consts_js_1.N / 4; this._poolPrf2 = new deps_js_1.Keccak(136, 0x1f, prf2Len, true); this._bufPrf2 = new Uint8Array(prf2Len); // constant buffer for k parameter this._kBuf = new Uint8Array([this._k]); // Matrix A: k × k pre-allocated slots this._matrixA = new Array(this._k); for (let i = 0; i < this._k; i++) { this._matrixA[i] = new Array(this._k); for (let j = 0; j < this._k; j++) { this._matrixA[i][j] = new Int16Array(consts_js_1.N); } } // Noise polyvec slots: max 2*k+1 (for encap: r=k, e1=k, e2=1) const maxNoise = 2 * this._k + 1; this._noiseVecs = new Array(maxNoise); for (let i = 0; i < maxNoise; i++) { this._noiseVecs[i] = new Int16Array(consts_js_1.N); } // Polyvec for polyFromBytes (shared by _encap tHat / _polyvecFromBytes) this._polyVec = new Array(this._k); for (let i = 0; i < this._k; i++) { this._polyVec[i] = new Int16Array(consts_js_1.N); } // pkCheck and ciphertext buffers this._bufPkCheck = new Uint8Array(384 * this._k); this._bufCt = new Uint8Array(this._compressedUSize + this._compressedVSize); } _zeroPool() { this._bufG.fill(0); this._bufH.fill(0); this._bufKdf.fill(0); this._bufXof.fill(0); this._bufPrf1.fill(0); this._bufPrf2.fill(0); this._nonceBuf[0] = 0; this._xofSeed.fill(0); for (let i = 0; i < this._k; i++) { for (let j = 0; j < this._k; j++) { this._matrixA[i][j].fill(0); } } for (let i = 0; i < this._noiseVecs.length; i++) { this._noiseVecs[i].fill(0); } for (let i = 0; i < this._k; i++) { this._polyVec[i].fill(0); } this._bufPkCheck.fill(0); this._bufCt.fill(0); // reset() zeros state and resets flags; no need for destroy() this._poolG.reset(); this._poolH.reset(); this._poolKdf.reset(); this._poolXof.reset(); this._poolPrf1.reset(); this._poolPrf2.reset(); } // Serialize polynomial into byte buffer at offset (eliminates intermediate Uint8Array(384)) _polyToBytes(out, outOffset, a) { let t0, t1; for (let i = 0; i < consts_js_1.N / 2; i++) { // inline subtractQ: a - q if a >= q, else a t0 = a[2 * i] - consts_js_1.Q; t0 += (t0 >> 31) & consts_js_1.Q; t1 = a[2 * i + 1] - consts_js_1.Q; t1 += (t1 >> 31) & consts_js_1.Q; out[outOffset + 3 * i + 0] = (0, utils_js_1.byte)(t0); out[outOffset + 3 * i + 1] = (0, utils_js_1.byte)(t0 >> 8) | (0, utils_js_1.byte)(t1 << 4); out[outOffset + 3 * i + 2] = (0, utils_js_1.byte)(t1 >> 4); } } // Deserialize bytes into polynomial (eliminates intermediate Int16Array(N)) _polyFromBytes(out, a, aOffset) { for (let i = 0; i < consts_js_1.N / 2; i++) { out[2 * i] = (0, utils_js_1.int16)((((0, utils_js_1.uint16)(a[aOffset + 3 * i + 0]) >> 0) | ((0, utils_js_1.uint16)(a[aOffset + 3 * i + 1]) << 8)) & 0xFFF); out[2 * i + 1] = (0, utils_js_1.int16)((((0, utils_js_1.uint16)(a[aOffset + 3 * i + 1]) >> 4) | ((0, utils_js_1.uint16)(a[aOffset + 3 * i + 2]) << 4)) & 0xFFF); } } // Hash G: SHA3-512 _g(a, b) { this._poolG.reset(); this._poolG.updateUnsafe(a); if (b !== undefined) this._poolG.updateUnsafe(b); this._poolG.writeIntoUnsafe(this._bufG); return [this._bufG.subarray(0, 32), this._bufG.subarray(32, 64)]; } // Hash H: SHA3-256 _h(msg) { this._poolH.reset(); this._poolH.updateUnsafe(msg).writeIntoUnsafe(this._bufH); return this._bufH; } // KDF: SHAKE256(dkLen=32) _kdf(a, b) { this._poolKdf.reset(); this._poolKdf.updateUnsafe(a); if (b !== undefined) this._poolKdf.updateUnsafe(b); this._poolKdf.writeIntoUnsafe(this._bufKdf); return this._bufKdf; } // XOF: SHAKE128(dkLen=672) _xof(seed) { this._poolXof.reset(); this._poolXof.updateUnsafe(seed).writeIntoUnsafe(this._bufXof); return this._bufXof; } // PRF for eta1 noise sampling: SHAKE256(dkLen=eta1*N/4) _prf1(sigma, nonce) { this._nonceBuf[0] = nonce; this._poolPrf1.reset(); this._poolPrf1.updateUnsafe(sigma).updateUnsafe(this._nonceBuf) .writeIntoUnsafe(this._bufPrf1); return this._bufPrf1; } // PRF for eta2 noise sampling: SHAKE256(dkLen=eta2*N/4) _prf2(sigma, nonce) { this._nonceBuf[0] = nonce; this._poolPrf2.reset(); this._poolPrf2.updateUnsafe(sigma).updateUnsafe(this._nonceBuf) .writeIntoUnsafe(this._bufPrf2); return this._bufPrf2; } _generateKeyPairCore() { try { const rnd = new Uint8Array(64); this._api.getRandomValues(rnd); return this._deriveKeyPair(rnd); } finally { this._zeroPool(); } } _deriveKeyPairCore(seed) { try { if (seed.byteLength !== 64) { throw new Error("seed must be 64 bytes in length"); } return this._deriveKeyPair(seed); } finally { this._zeroPool(); } } _encapCore(pk, seed) { try { // validate key type; the modulo is checked in `_encap`. if (pk.length !== 384 * this._k + 32) { throw new Error("invalid encapsulation key"); } const m = this._getSeed(seed); const [k, r] = this._g(m, this._h(pk)); this._encap(pk, m, r); return [this._bufCt.slice(), k.slice()]; } finally { this._zeroPool(); } } _decapCore(ct, sk) { try { // ciphertext type check if (ct.byteLength !== this._compressedUSize + this._compressedVSize) { throw new Error("Invalid ct size"); } // decapsulation key type check if (sk.length !== 768 * this._k + 96) { throw new Error("Invalid decapsulation key"); } const sk2 = sk.subarray(0, this._skSize); const pk = sk.subarray(this._skSize, this._skSize + this._pkSize); const hpk = sk.subarray(this._skSize + this._pkSize, this._skSize + this._pkSize + 32); const z = sk.subarray(this._skSize + this._pkSize + 32, this._skSize + this._pkSize + 64); const m2 = this._decap(ct, sk2); const [k2, r2] = this._g(m2, hpk); const kBar = this._kdf(z, ct); this._encap(pk, m2, r2); return (0, utils_js_1.constantTimeCompare)(ct, this._bufCt) === 1 ? k2.slice() : kBar.slice(); } finally { this._zeroPool(); } } /** * Sets up the MlKemBase instance by loading the necessary crypto library. * If the crypto library is already loaded, this method does nothing. * @returns {Promise<void>} A promise that resolves when the setup is complete. */ async _setup() { if (this._api !== undefined) { return; } this._api = await (0, utils_js_1.loadCrypto)(); } /** * Returns a Uint8Array seed for cryptographic operations. * If no seed is provided, a random seed of length 32 bytes is generated. * If a seed is provided, it must be exactly 32 bytes in length. * * @param seed - Optional seed for cryptographic operations. * @returns A Uint8Array seed. * @throws Error if the provided seed is not 32 bytes in length. */ _getSeed(seed) { if (seed == undefined) { const s = new Uint8Array(32); this._api.getRandomValues(s); return s; } if (seed.byteLength !== 32) { throw new Error("seed must be 32 bytes in length"); } return seed; } /** * Derives a key pair from a given seed. * * @param seed - The seed used for key derivation. * @returns An array containing the public key and secret key. */ _deriveKeyPair(seed) { const cpaSeed = seed.subarray(0, 32); const z = seed.subarray(32, 64); const [pk, skBody] = this._deriveCpaKeyPair(cpaSeed); const pkh = this._h(pk); const sk = new Uint8Array(this._skSize + this._pkSize + 64); sk.set(skBody, 0); sk.set(pk, this._skSize); sk.set(pkh, this._skSize + this._pkSize); sk.set(z, this._skSize + this._pkSize + 32); return [pk, sk]; } // indcpaKeyGen generates public and private keys for the CPA-secure // public-key encryption scheme underlying ML-KEM. /** * Derives a CPA key pair using the provided CPA seed. * * @param cpaSeed - The CPA seed used for key derivation. * @returns An array containing the public key and private key. */ _deriveCpaKeyPair(cpaSeed) { const [publicSeed, noiseSeed] = this._g(cpaSeed, this._kBuf); const a = this._sampleMatrix(publicSeed, false); const s = this._sampleNoise1(noiseSeed, 0, this._k); const e = this._sampleNoise1(noiseSeed, this._k, this._k); // perform number theoretic transform on secret s for (let i = 0; i < this._k; i++) { s[i] = ntt(s[i]); s[i] = reduce(s[i]); e[i] = ntt(e[i]); } // KEY COMPUTATION // pk = A*s + e const pk = new Array(this._k); for (let i = 0; i < this._k; i++) { pk[i] = polyToMont(multiply(a[i], s)); pk[i] = add(pk[i], e[i]); pk[i] = reduce(pk[i]); } // PUBLIC KEY // turn polynomials into byte arrays const pubKey = new Uint8Array(this._pkSize); for (let i = 0; i < this._k; i++) { this._polyToBytes(pubKey, i * 384, pk[i]); } // append public seed pubKey.set(publicSeed, this._skSize); // PRIVATE KEY // turn polynomials into byte arrays const privKey = new Uint8Array(this._skSize); for (let i = 0; i < this._k; i++) { this._polyToBytes(privKey, i * 384, s[i]); } return [pubKey, privKey]; } // _encap is the encapsulation function of the CPA-secure // public-key encryption scheme underlying ML-KEM. /** * Encapsulates a message using the ML-KEM encryption scheme. * * @param pk - The public key. * @param msg - The message to be encapsulated. * @param seed - The seed used for generating random values. * @returns The encapsulated message as a Uint8Array. */ _encap(pk, msg, seed) { const tHat = this._polyVec; const pkCheck = this._bufPkCheck; // to validate the pk modulo (see input validation at NIST draft 6.2) for (let i = 0; i < this._k; i++) { this._polyFromBytes(tHat[i], pk, i * 384); this._polyToBytes(pkCheck, i * 384, tHat[i]); } if (!(0, utils_js_1.equalUint8Array)(pk.subarray(0, pkCheck.length), pkCheck)) { throw new Error("invalid encapsulation key"); } const rho = pk.subarray(this._skSize); const a = this._sampleMatrix(rho, true); const r = this._sampleNoise1(seed, 0, this._k); const e1 = this._sampleNoise2(seed, this._k, this._k); const e2 = this._sampleNoise2(seed, this._k * 2, 1)[0]; // perform number theoretic transform on random vector r for (let i = 0; i < this._k; i++) { r[i] = ntt(r[i]); r[i] = reduce(r[i]); } // u = A*r + e1 const u = new Array(this._k); for (let i = 0; i < this._k; i++) { u[i] = multiply(a[i], r); u[i] = nttInverse(u[i]); u[i] = add(u[i], e1[i]); u[i] = reduce(u[i]); } // v = tHat*r + e2 + m const m = polyFromMsg(msg); let v = multiply(tHat, r); v = nttInverse(v); v = add(v, e2); v = add(v, m); v = reduce(v); // compress into pre-allocated ciphertext buffer this._compressU(this._bufCt.subarray(0, this._compressedUSize), u); this._compressV(this._bufCt.subarray(this._compressedUSize), v); return this._bufCt; } // indcpaDecrypt is the decryption function of the CPA-secure // public-key encryption scheme underlying ML-KEM. /** * Decapsulates the ciphertext using the provided secret key. * * @param ct - The ciphertext to be decapsulated. * @param sk - The secret key used for decapsulation. * @returns The decapsulated message as a Uint8Array. */ _decap(ct, sk) { // extract ciphertext const u = this._decompressU(ct.subarray(0, this._compressedUSize)); const v = this._decompressV(ct.subarray(this._compressedUSize)); const privateKeyPolyvec = this._polyvecFromBytes(sk); for (let i = 0; i < this._k; i++) { u[i] = ntt(u[i]); } let mp = multiply(privateKeyPolyvec, u); mp = nttInverse(mp); mp = subtract(v, mp); mp = reduce(mp); return polyToMsg(mp); } // generateMatrixA deterministically generates a matrix `A` (or the transpose of `A`) // from a seed. Entries of the matrix are polynomials that look uniformly random. // Performs rejection sampling on the output of an extendable-output function (XOF). /** * Generates a sample matrix based on the provided seed and transposition flag. * * @param seed - The seed used for generating the matrix. * @param transposed - A flag indicating whether the matrix should be transposed or not. * @returns The generated sample matrix. */ _sampleMatrix(seed, transposed) { const a = this._matrixA; this._xofSeed.set(seed); for (let ctr = 0, i = 0; i < this._k; i++) { for (let j = 0; j < this._k; j++) { // set if transposed matrix or not if (transposed) { this._xofSeed[seed.length] = i; this._xofSeed[seed.length + 1] = j; } else { this._xofSeed[seed.length] = j; this._xofSeed[seed.length + 1] = i; } const output = this._xof(this._xofSeed); // run rejection sampling directly into pre-allocated matrix slot ctr = indcpaRejUniform(a[i][j], 0, output.subarray(0, 504), 504, consts_js_1.N); while (ctr < consts_js_1.N) { // if the polynomial hasnt been filled yet with mod q entries const outputn = output.subarray(504, 672); // take last 168 bytes of byte array from xof ctr += indcpaRejUniform(a[i][j], ctr, outputn, 168, consts_js_1.N - ctr); } } } return a; } /** * Generates a 2D array of noise samples. * * @param sigma - The noise parameter. * @param offset - The offset value. * @param size - The size of the array. * @returns The generated 2D array of noise samples. */ _sampleNoise1(sigma, offset, size) { const r = new Array(size); for (let i = 0; i < size; i++) { r[i] = this._noiseVecs[offset + i]; byteopsCbd(r[i], this._prf1(sigma, offset + i), this._eta1); } return r; } /** * Generates a 2-dimensional array of noise samples. * * @param sigma - The noise parameter. * @param offset - The offset value. * @param size - The size of the array. * @returns The generated 2-dimensional array of noise samples. */ _sampleNoise2(sigma, offset, size) { const r = new Array(size); for (let i = 0; i < size; i++) { r[i] = this._noiseVecs[offset + i]; byteopsCbd(r[i], this._prf2(sigma, offset + i), this._eta2); } return r; } // polyvecFromBytes deserializes a vector of polynomials. /** * Converts a Uint8Array to a 2D array of numbers representing a polynomial vector. * Each element in the resulting array represents a polynomial. * @param a The Uint8Array to convert. * @returns The 2D array of numbers representing the polynomial vector. */ _polyvecFromBytes(a) { const r = this._polyVec; for (let i = 0; i < this._k; i++) { this._polyFromBytes(r[i], a, i * 384); } return r; } // compressU lossily compresses and serializes a vector of polynomials. /** * Compresses the given array of coefficients into a Uint8Array. * * @param r - The output Uint8Array. * @param u - The array of coefficients. * @returns The compressed Uint8Array. */ _compressU(r, u) { const t = new Array(4); for (let rr = 0, i = 0; i < this._k; i++) { for (let j = 0; j < consts_js_1.N / 4; j++) { for (let k = 0; k < 4; k++) { // parse {0,...,3328} to {0,...,1023} t[k] = (((u[i][4 * j + k] << 10) + consts_js_1.Q / 2) / consts_js_1.Q) & 0b1111111111; } // converts 4 12-bit coefficients {0,...,3328} to 5 8-bit bytes {0,...,255} // 48 bits down to 40 bits per block r[rr++] = (0, utils_js_1.byte)(t[0] >> 0); r[rr++] = (0, utils_js_1.byte)((t[0] >> 8) | (t[1] << 2)); r[rr++] = (0, utils_js_1.byte)((t[1] >> 6) | (t[2] << 4)); r[rr++] = (0, utils_js_1.byte)((t[2] >> 4) | (t[3] << 6)); r[rr++] = (0, utils_js_1.byte)(t[3] >> 2); } } return r; } // compressV lossily compresses and subsequently serializes a polynomial. /** * Compresses the given array of numbers into a Uint8Array. * * @param r - The Uint8Array to store the compressed values. * @param v - The array of numbers to compress. * @returns The compressed Uint8Array. */ _compressV(r, v) { // const r = new Uint8Array(128); const t = new Uint8Array(8); for (let rr = 0, i = 0; i < consts_js_1.N / 8; i++) { for (let j = 0; j < 8; j++) { t[j] = (0, utils_js_1.byte)(((v[8 * i + j] << 4) + consts_js_1.Q / 2) / consts_js_1.Q) & 0b1111; } r[rr++] = t[0] | (t[1] << 4); r[rr++] = t[2] | (t[3] << 4); r[rr++] = t[4] | (t[5] << 4); r[rr++] = t[6] | (t[7] << 4); } return r; } // decompressU de-serializes and decompresses a vector of polynomials and // represents the approximate inverse of compress1. Since compression is lossy, // the results of decompression will may not match the original vector of polynomials. /** * Decompresses a Uint8Array into a two-dimensional array of numbers. * * @param a The Uint8Array to decompress. * @returns The decompressed two-dimensional array. */ _decompressU(a) { const r = new Array(this._k); for (let i = 0; i < this._k; i++) { r[i] = new Int16Array(consts_js_1.N); } const t = new Array(4); for (let aa = 0, i = 0; i < this._k; i++) { for (let j = 0; j < consts_js_1.N / 4; j++) { t[0] = ((0, utils_js_1.uint16)(a[aa + 0]) >> 0) | ((0, utils_js_1.uint16)(a[aa + 1]) << 8); t[1] = ((0, utils_js_1.uint16)(a[aa + 1]) >> 2) | ((0, utils_js_1.uint16)(a[aa + 2]) << 6); t[2] = ((0, utils_js_1.uint16)(a[aa + 2]) >> 4) | ((0, utils_js_1.uint16)(a[aa + 3]) << 4); t[3] = ((0, utils_js_1.uint16)(a[aa + 3]) >> 6) | ((0, utils_js_1.uint16)(a[aa + 4]) << 2); aa = aa + 5; for (let k = 0; k < 4; k++) { r[i][4 * j + k] = (0, utils_js_1.int16)((((t[k] & 0x3FF) * consts_js_1.Q) + 512) >> 10); } } } return r; } // decompressV de-serializes and subsequently decompresses a polynomial, // representing the approximate inverse of compress2. // Note that compression is lossy, and thus decompression will not match the // original input. /** * Decompresses a Uint8Array into an array of numbers. * * @param a - The Uint8Array to decompress. * @returns An array of numbers. */ _decompressV(a) { const r = new Int16Array(consts_js_1.N); for (let aa = 0, i = 0; i < consts_js_1.N / 2; i++, aa++) { r[2 * i + 0] = (0, utils_js_1.int16)((((a[aa] & 15) * consts_js_1.Q) + 8) >> 4); r[2 * i + 1] = (0, utils_js_1.int16)((((a[aa] >> 4) * consts_js_1.Q) + 8) >> 4); } return r; } } exports.MlKemBase = MlKemBase; // polyToMsg converts a polynomial to a 32-byte message // and represents the inverse of polyFromMsg. /** * Converts a polynomial to a message represented as a Uint8Array. * @param a - The polynomial to convert. * @returns The message as a Uint8Array. */ function polyToMsg(a) { const msg = new Uint8Array(32); let t, v; for (let i = 0; i < consts_js_1.N / 8; i++) { for (let j = 0; j < 8; j++) { // inline subtractQ: a - q if a >= q, else a v = a[8 * i + j] - consts_js_1.Q; v += (v >> 31) & consts_js_1.Q; t = ((((0, utils_js_1.uint16)(v) << 1) + (0, utils_js_1.uint16)(consts_js_1.Q / 2)) / (0, utils_js_1.uint16)(consts_js_1.Q)) & 1; msg[i] |= (0, utils_js_1.byte)(t << j); } } return msg; } // polyFromMsg converts a 32-byte message to a polynomial. /** * Converts a Uint8Array message to an array of numbers representing a polynomial. * Each element in the array is an int16 (0-65535). * * @param msg - The Uint8Array message to convert. * @returns An array of numbers representing the polynomial. */ function polyFromMsg(msg) { const r = new Int16Array(consts_js_1.N); // each element is int16 (0-65535) let mask; // int16 for (let i = 0; i < consts_js_1.N / 8; i++) { for (let j = 0; j < 8; j++) { mask = -1 * (0, utils_js_1.int16)((msg[i] >> j) & 1); r[8 * i + j] = mask & (0, utils_js_1.int16)((consts_js_1.Q + 1) / 2); } } return r; } // indcpaRejUniform runs rejection sampling on uniform random bytes // to generate uniform random integers modulo `Q`. /** * Generates an array of random numbers from a given buffer, rejecting values greater than a specified threshold. * * @param buf - The input buffer containing random bytes. * @param bufl - The length of the input buffer. * @param len - The desired length of the output array. * @returns An array of random numbers and the actual length of the output array. */ function indcpaRejUniform(out, outOffset, buf, bufl, len) { let ctr = 0; let val0, val1; // d1, d2 in kyber documentation for (let pos = 0; ctr < len && pos + 3 <= bufl;) { // compute d1 and d2 val0 = ((0, utils_js_1.uint16)((buf[pos]) >> 0) | ((0, utils_js_1.uint16)(buf[pos + 1]) << 8)) & 0xFFF; val1 = ((0, utils_js_1.uint16)((buf[pos + 1]) >> 4) | ((0, utils_js_1.uint16)(buf[pos + 2]) << 4)) & 0xFFF; // increment input buffer index by 3 pos = pos + 3; // if d1 is less than 3329 if (val0 < consts_js_1.Q) { out[outOffset + ctr] = val0; ctr = ctr + 1; } if (ctr < len && val1 < consts_js_1.Q) { out[outOffset + ctr] = val1; ctr = ctr + 1; } } return ctr; } // byteopsCbd computes a polynomial with coefficients distributed // according to a centered binomial distribution with parameter PARAMS_ETA, // given an array of uniformly random bytes. /** * Converts a Uint8Array buffer to an array of numbers using the CBD operation. * @param buf - The input Uint8Array buffer. * @param eta - The value used in the CBD operation. * @returns An array of numbers obtained from the CBD operation. */ function byteopsCbd(out, buf, eta) { let t, d; let a, b; for (let i = 0; i < consts_js_1.N / 8; i++) { t = (0, utils_js_1.byteopsLoad32)(buf, 4 * i); d = t & 0x55555555; d = d + ((t >> 1) & 0x55555555); for (let j = 0; j < 8; j++) { a = (0, utils_js_1.int16)((d >> (4 * j + 0)) & 0x3); b = (0, utils_js_1.int16)((d >> (4 * j + eta)) & 0x3); out[8 * i + j] = a - b; } } } // ntt performs an inplace number-theoretic transform (NTT) in `Rq`. // The input is in standard order, the output is in bit-reversed order. /** * Performs the Number Theoretic Transform (NTT) on an array of numbers. * * @param r - The input array of numbers. * @returns The transformed array of numbers. */ function ntt(r) { // 128, 64, 32, 16, 8, 4, 2 for (let j = 0, k = 1, l = 128; l >= 2; l >>= 1) { // 0, for (let start = 0; start < 256; start = j + l) { const zeta = consts_js_1.NTT_ZETAS[k]; k = k + 1; // for each element in the subsections (128, 64, 32, 16, 8, 4, 2) starting at an offset for (j = start; j < start + l; j++) { // compute the modular multiplication of the zeta and each element in the subsection const t = nttFqMul(zeta, r[j + l]); // t is mod q // overwrite each element in the subsection as the opposite subsection element minus t r[j + l] = r[j] - t; // add t back again to the opposite subsection r[j] = r[j] + t; } } } return r; } // nttFqMul performs multiplication followed by Montgomery reduction // and returns a 16-bit integer congruent to `a*b*R^{-1} mod Q`. /** * Performs an NTT (Number Theoretic Transform) multiplication on two numbers in Fq. * @param a The first number. * @param b The second number. * @returns The result of the NTT multiplication. */ function nttFqMul(a, b) { const ab = a * b; const u = (Math.imul(ab, consts_js_1.Q_INV) << 16) >> 16; return (ab - u * consts_js_1.Q) >> 16; } // reduce applies Barrett reduction to all coefficients of a polynomial. /** * Reduces each element in the given array using the barrett function. * * @param r - The array to be reduced. * @returns The reduced array. */ function reduce(r) { for (let i = 0; i < consts_js_1.N; i++) { r[i] = barrett(r[i]); } return r; } // barrett computes a Barrett reduction; given // a integer `a`, returns a integer congruent to // `a mod Q` in {0,...,Q}. /** * Performs the Barrett reduction algorithm on the given number. * * @param a - The number to be reduced. * @returns The result of the reduction. */ const BARRETT_V = ((1 << 24) + consts_js_1.Q / 2) / consts_js_1.Q; function barrett(a) { let t = BARRETT_V * a >> 24; t = t * consts_js_1.Q; return a - t; } // polyToMont performs the in-place conversion of all coefficients // of a polynomial from the normal domain to the Montgomery domain. /** * Converts a polynomial to the Montgomery domain. * * @param r - The polynomial to be converted. * @returns The polynomial in the Montgomery domain. */ function polyToMont(r) { // let f = int16(((uint64(1) << 32)) % uint64(Q)); const f = 1353; // if Q changes then this needs to be updated for (let i = 0; i < consts_js_1.N; i++) { const a = r[i] * f; const u = (Math.imul(a, consts_js_1.Q_INV) << 16) >> 16; r[i] = (a - u * consts_js_1.Q) >> 16; } return r; } // pointwise-multiplies elements of polynomial-vectors // `a` and `b`, accumulates the results into `r`, and then multiplies by `2^-16`. /** * Multiplies two matrices element-wise and returns the result. * @param a - The first matrix. * @param b - The second matrix. * @returns The resulting matrix after element-wise multiplication. */ function multiply(a, b) { let r = polyBaseMulMontgomery(a[0], b[0]); let t; for (let i = 1; i < a.length; i++) { t = polyBaseMulMontgomery(a[i], b[i]); r = add(r, t); } return reduce(r); } // polyBaseMulMontgomery performs the multiplication of two polynomials // in the number-theoretic transform (NTT) domain. /** * Performs polynomial base multiplication in Montgomery domain. * @param a - The first polynomial array. * @param b - The second polynomial array. * @returns The result of the polynomial base multiplication. */ function polyBaseMulMontgomery(a, b) { for (let i = 0; i < consts_js_1.N / 4; i++) { const idx = 4 * i; const a0 = a[idx], a1 = a[idx + 1], a2 = a[idx + 2], a3 = a[idx + 3]; const b0 = b[idx], b1 = b[idx + 1], b2 = b[idx + 2], b3 = b[idx + 3]; const zeta = consts_js_1.NTT_ZETAS[64 + i]; a[idx] = nttFqMul(nttFqMul(a1, b1), zeta) + nttFqMul(a0, b0); a[idx + 1] = nttFqMul(a0, b1) + nttFqMul(a1, b0); a[idx + 2] = nttFqMul(nttFqMul(a3, b3), -zeta) + nttFqMul(a2, b2); a[idx + 3] = nttFqMul(a2, b3) + nttFqMul(a3, b2); } return a; } // adds two polynomials. /** * Adds two arrays element-wise. * @param a - The first array. * @param b - The second array. * @returns The resulting array after element-wise addition. */ function add(a, b) { for (let i = 0; i < consts_js_1.N; i++) { a[i] += b[i]; } return a; } // subtracts two polynomials. /** * Subtracts the elements of array b from array a. * * @param a - The array from which to subtract. * @param b - The array to subtract. * @returns The resulting array after subtraction. */ function subtract(a, b) { for (let i = 0; i < consts_js_1.N; i++) { a[i] -= b[i]; } return a; } // nttInverse performs an inplace inverse number-theoretic transform (NTT) // in `Rq` and multiplication by Montgomery factor 2^16. // The input is in bit-reversed order, the output is in standard order. /** * Performs the inverse Number Theoretic Transform (NTT) on the given array. * * @param r - The input array to perform the inverse NTT on. * @returns The array after performing the inverse NTT. */ function nttInverse(r) { let j = 0; for (let k = 0, l = 2; l <= 128; l <<= 1) { for (let start = 0; start < 256; start = j + l) { const zeta = consts_js_1.NTT_ZETAS_INV[k]; k = k + 1; for (j = start; j < start + l; j++) { const t = r[j]; r[j] = barrett(t + r[j + l]); r[j + l] = t - r[j + l]; r[j + l] = nttFqMul(zeta, r[j + l]); } } } for (j = 0; j < 256; j++) { r[j] = nttFqMul(r[j], consts_js_1.NTT_ZETAS_INV[127]); } return r; } });