crystals-kyber-js
Version:
An ML-KEM/CRYSTALS-KYBER implementation written in TypeScript for various JavaScript runtimes
618 lines (617 loc) • 23.1 kB
JavaScript
/**
* This file is based on noble-hashes (https://github.com/paulmillr/noble-hashes).
*
* noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com)
*
* The original file is located at:
* https://github.com/paulmillr/noble-hashes/blob/4e358a46d682adfb005ae6314ec999f2513086b9/src/sha3.ts
*/
(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", "./_u64.js", "./utils.js"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.shake256 = exports.shake128 = exports.keccak_512 = exports.keccak_384 = exports.keccak_256 = exports.keccak_224 = exports.sha3_512 = exports.sha3_256 = exports.Keccak = void 0;
exports.keccakP = keccakP;
/**
* SHA3 (keccak) hash function, based on a new "Sponge function" design.
* Different from older hashes, the internal state is bigger than output size.
*
* Check out [FIPS-202](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf),
* [Website](https://keccak.team/keccak.html),
* [the differences between SHA-3 and Keccak](https://crypto.stackexchange.com/questions/15727/what-are-the-key-differences-between-the-draft-sha-3-standard-and-the-keccak-sub).
*
* Check out `sha3-addons` module for cSHAKE, k12, and others.
* @module
*/
const _u64_js_1 = require("./_u64.js");
// prettier-ignore
const utils_js_1 = require("./utils.js");
// No __PURE__ annotations in sha3 header:
// EVERYTHING is in fact used on every export.
// Various per round constants calculations
const _0n = 0n;
const _1n = 1n;
const _2n = 2n;
const _7n = 7n;
const _256n = 256n;
const _0x71n = 0x71n;
const SHA3_PI = [];
const SHA3_ROTL = [];
const _SHA3_IOTA = []; // no pure annotation: var is always used
for (let round = 0, R = _1n, x = 1, y = 0; round < 24; round++) {
// Pi
[x, y] = [y, (2 * x + 3 * y) % 5];
SHA3_PI.push(2 * (5 * y + x));
// Rotational
SHA3_ROTL.push((((round + 1) * (round + 2)) / 2) % 64);
// Iota
let t = _0n;
for (let j = 0; j < 7; j++) {
R = ((R << _1n) ^ ((R >> _7n) * _0x71n)) % _256n;
if (R & _2n)
t ^= _1n << ((_1n << BigInt(j)) - _1n);
}
_SHA3_IOTA.push(t);
}
const IOTAS = (0, _u64_js_1.split)(_SHA3_IOTA, true);
const SHA3_IOTA_H = IOTAS[0];
const SHA3_IOTA_L = IOTAS[1];
// Left rotation (without 0, 32, 64)
const rotlH = (h, l, s) => (s > 32 ? (0, _u64_js_1.rotlBH)(h, l, s) : (0, _u64_js_1.rotlSH)(h, l, s));
const rotlL = (h, l, s) => (s > 32 ? (0, _u64_js_1.rotlBL)(h, l, s) : (0, _u64_js_1.rotlSL)(h, l, s));
/** `keccakf1600` internal function, additionally allows to adjust round count. */
function keccakP(s, rounds = 24, B) {
if (!B)
B = new Uint32Array(10);
// NOTE: all indices are x2 since we store state as u32 instead of u64 (bigints to slow in js)
for (let round = 24 - rounds; round < 24; round++) {
// Theta θ
for (let x = 0; x < 10; x++) {
B[x] = s[x] ^ s[x + 10] ^ s[x + 20] ^ s[x + 30] ^ s[x + 40];
}
// for (let x = 0; x < 10; x += 2) {
// const idx1 = (x + 8) % 10;
// const idx0 = (x + 2) % 10;
// const B0 = B[idx0];
// const B1 = B[idx0 + 1];
// const Th = rotlH(B0, B1, 1) ^ B[idx1];
// const Tl = rotlL(B0, B1, 1) ^ B[idx1 + 1];
// for (let y = 0; y < 50; y += 10) {
// s[x + y] ^= Th;
// s[x + y + 1] ^= Tl;
// }
// }
{ // x=0: idx0=2, idx1=8
const Th = rotlH(B[2], B[3], 1) ^ B[8];
const Tl = rotlL(B[2], B[3], 1) ^ B[9];
s[0] ^= Th;
s[1] ^= Tl;
s[10] ^= Th;
s[11] ^= Tl;
s[20] ^= Th;
s[21] ^= Tl;
s[30] ^= Th;
s[31] ^= Tl;
s[40] ^= Th;
s[41] ^= Tl;
}
{ // x=2: idx0=4, idx1=0
const Th = rotlH(B[4], B[5], 1) ^ B[0];
const Tl = rotlL(B[4], B[5], 1) ^ B[1];
s[2] ^= Th;
s[3] ^= Tl;
s[12] ^= Th;
s[13] ^= Tl;
s[22] ^= Th;
s[23] ^= Tl;
s[32] ^= Th;
s[33] ^= Tl;
s[42] ^= Th;
s[43] ^= Tl;
}
{ // x=4: idx0=6, idx1=2
const Th = rotlH(B[6], B[7], 1) ^ B[2];
const Tl = rotlL(B[6], B[7], 1) ^ B[3];
s[4] ^= Th;
s[5] ^= Tl;
s[14] ^= Th;
s[15] ^= Tl;
s[24] ^= Th;
s[25] ^= Tl;
s[34] ^= Th;
s[35] ^= Tl;
s[44] ^= Th;
s[45] ^= Tl;
}
{ // x=6: idx0=8, idx1=4
const Th = rotlH(B[8], B[9], 1) ^ B[4];
const Tl = rotlL(B[8], B[9], 1) ^ B[5];
s[6] ^= Th;
s[7] ^= Tl;
s[16] ^= Th;
s[17] ^= Tl;
s[26] ^= Th;
s[27] ^= Tl;
s[36] ^= Th;
s[37] ^= Tl;
s[46] ^= Th;
s[47] ^= Tl;
}
{ // x=8: idx0=0, idx1=6
const Th = rotlH(B[0], B[1], 1) ^ B[6];
const Tl = rotlL(B[0], B[1], 1) ^ B[7];
s[8] ^= Th;
s[9] ^= Tl;
s[18] ^= Th;
s[19] ^= Tl;
s[28] ^= Th;
s[29] ^= Tl;
s[38] ^= Th;
s[39] ^= Tl;
s[48] ^= Th;
s[49] ^= Tl;
}
// Rho (ρ) and Pi (π) — fully unrolled
let curH = s[2];
let curL = s[3];
// for (let t = 0; t < 24; t++) {
// const shift = SHA3_ROTL[t];
// const Th = rotlH(curH, curL, shift);
// const Tl = rotlL(curH, curL, shift);
// const PI = SHA3_PI[t];
// curH = s[PI];
// curL = s[PI + 1];
// s[PI] = Th;
// s[PI + 1] = Tl;
// }
let Th, Tl;
// t=0: shift=1(S), PI=20
Th = (0, _u64_js_1.rotlSH)(curH, curL, 1);
Tl = (0, _u64_js_1.rotlSL)(curH, curL, 1);
curH = s[20];
curL = s[21];
s[20] = Th;
s[21] = Tl;
// t=1: shift=3(S), PI=14
Th = (0, _u64_js_1.rotlSH)(curH, curL, 3);
Tl = (0, _u64_js_1.rotlSL)(curH, curL, 3);
curH = s[14];
curL = s[15];
s[14] = Th;
s[15] = Tl;
// t=2: shift=6(S), PI=22
Th = (0, _u64_js_1.rotlSH)(curH, curL, 6);
Tl = (0, _u64_js_1.rotlSL)(curH, curL, 6);
curH = s[22];
curL = s[23];
s[22] = Th;
s[23] = Tl;
// t=3: shift=10(S), PI=34
Th = (0, _u64_js_1.rotlSH)(curH, curL, 10);
Tl = (0, _u64_js_1.rotlSL)(curH, curL, 10);
curH = s[34];
curL = s[35];
s[34] = Th;
s[35] = Tl;
// t=4: shift=15(S), PI=36
Th = (0, _u64_js_1.rotlSH)(curH, curL, 15);
Tl = (0, _u64_js_1.rotlSL)(curH, curL, 15);
curH = s[36];
curL = s[37];
s[36] = Th;
s[37] = Tl;
// t=5: shift=21(S), PI=6
Th = (0, _u64_js_1.rotlSH)(curH, curL, 21);
Tl = (0, _u64_js_1.rotlSL)(curH, curL, 21);
curH = s[6];
curL = s[7];
s[6] = Th;
s[7] = Tl;
// t=6: shift=28(S), PI=10
Th = (0, _u64_js_1.rotlSH)(curH, curL, 28);
Tl = (0, _u64_js_1.rotlSL)(curH, curL, 28);
curH = s[10];
curL = s[11];
s[10] = Th;
s[11] = Tl;
// t=7: shift=36(B), PI=32
Th = (0, _u64_js_1.rotlBH)(curH, curL, 36);
Tl = (0, _u64_js_1.rotlBL)(curH, curL, 36);
curH = s[32];
curL = s[33];
s[32] = Th;
s[33] = Tl;
// t=8: shift=45(B), PI=16
Th = (0, _u64_js_1.rotlBH)(curH, curL, 45);
Tl = (0, _u64_js_1.rotlBL)(curH, curL, 45);
curH = s[16];
curL = s[17];
s[16] = Th;
s[17] = Tl;
// t=9: shift=55(B), PI=42
Th = (0, _u64_js_1.rotlBH)(curH, curL, 55);
Tl = (0, _u64_js_1.rotlBL)(curH, curL, 55);
curH = s[42];
curL = s[43];
s[42] = Th;
s[43] = Tl;
// t=10: shift=2(S), PI=48
Th = (0, _u64_js_1.rotlSH)(curH, curL, 2);
Tl = (0, _u64_js_1.rotlSL)(curH, curL, 2);
curH = s[48];
curL = s[49];
s[48] = Th;
s[49] = Tl;
// t=11: shift=14(S), PI=8
Th = (0, _u64_js_1.rotlSH)(curH, curL, 14);
Tl = (0, _u64_js_1.rotlSL)(curH, curL, 14);
curH = s[8];
curL = s[9];
s[8] = Th;
s[9] = Tl;
// t=12: shift=27(S), PI=30
Th = (0, _u64_js_1.rotlSH)(curH, curL, 27);
Tl = (0, _u64_js_1.rotlSL)(curH, curL, 27);
curH = s[30];
curL = s[31];
s[30] = Th;
s[31] = Tl;
// t=13: shift=41(B), PI=46
Th = (0, _u64_js_1.rotlBH)(curH, curL, 41);
Tl = (0, _u64_js_1.rotlBL)(curH, curL, 41);
curH = s[46];
curL = s[47];
s[46] = Th;
s[47] = Tl;
// t=14: shift=56(B), PI=38
Th = (0, _u64_js_1.rotlBH)(curH, curL, 56);
Tl = (0, _u64_js_1.rotlBL)(curH, curL, 56);
curH = s[38];
curL = s[39];
s[38] = Th;
s[39] = Tl;
// t=15: shift=8(S), PI=26
Th = (0, _u64_js_1.rotlSH)(curH, curL, 8);
Tl = (0, _u64_js_1.rotlSL)(curH, curL, 8);
curH = s[26];
curL = s[27];
s[26] = Th;
s[27] = Tl;
// t=16: shift=25(S), PI=24
Th = (0, _u64_js_1.rotlSH)(curH, curL, 25);
Tl = (0, _u64_js_1.rotlSL)(curH, curL, 25);
curH = s[24];
curL = s[25];
s[24] = Th;
s[25] = Tl;
// t=17: shift=43(B), PI=4
Th = (0, _u64_js_1.rotlBH)(curH, curL, 43);
Tl = (0, _u64_js_1.rotlBL)(curH, curL, 43);
curH = s[4];
curL = s[5];
s[4] = Th;
s[5] = Tl;
// t=18: shift=62(B), PI=40
Th = (0, _u64_js_1.rotlBH)(curH, curL, 62);
Tl = (0, _u64_js_1.rotlBL)(curH, curL, 62);
curH = s[40];
curL = s[41];
s[40] = Th;
s[41] = Tl;
// t=19: shift=18(S), PI=28
Th = (0, _u64_js_1.rotlSH)(curH, curL, 18);
Tl = (0, _u64_js_1.rotlSL)(curH, curL, 18);
curH = s[28];
curL = s[29];
s[28] = Th;
s[29] = Tl;
// t=20: shift=39(B), PI=44
Th = (0, _u64_js_1.rotlBH)(curH, curL, 39);
Tl = (0, _u64_js_1.rotlBL)(curH, curL, 39);
curH = s[44];
curL = s[45];
s[44] = Th;
s[45] = Tl;
// t=21: shift=61(B), PI=18
Th = (0, _u64_js_1.rotlBH)(curH, curL, 61);
Tl = (0, _u64_js_1.rotlBL)(curH, curL, 61);
curH = s[18];
curL = s[19];
s[18] = Th;
s[19] = Tl;
// t=22: shift=20(S), PI=12
Th = (0, _u64_js_1.rotlSH)(curH, curL, 20);
Tl = (0, _u64_js_1.rotlSL)(curH, curL, 20);
curH = s[12];
curL = s[13];
s[12] = Th;
s[13] = Tl;
// t=23: shift=44(B), PI=2
Th = (0, _u64_js_1.rotlBH)(curH, curL, 44);
Tl = (0, _u64_js_1.rotlBL)(curH, curL, 44);
s[2] = Th;
s[3] = Tl;
// Chi (χ)
for (let y = 0; y < 50; y += 10) {
B[0] = s[y];
B[1] = s[y + 1];
B[2] = s[y + 2];
B[3] = s[y + 3];
B[4] = s[y + 4];
B[5] = s[y + 5];
B[6] = s[y + 6];
B[7] = s[y + 7];
B[8] = s[y + 8];
B[9] = s[y + 9];
s[y + 0] ^= ~B[2] & B[4];
s[y + 1] ^= ~B[3] & B[5];
s[y + 2] ^= ~B[4] & B[6];
s[y + 3] ^= ~B[5] & B[7];
s[y + 4] ^= ~B[6] & B[8];
s[y + 5] ^= ~B[7] & B[9];
s[y + 6] ^= ~B[8] & B[0];
s[y + 7] ^= ~B[9] & B[1];
s[y + 8] ^= ~B[0] & B[2];
s[y + 9] ^= ~B[1] & B[3];
}
// Iota (ι)
s[0] ^= SHA3_IOTA_H[round];
s[1] ^= SHA3_IOTA_L[round];
}
}
/** Keccak sponge function. */
class Keccak {
// NOTE: we accept arguments in bytes instead of bits here.
constructor(blockLen, suffix, outputLen, enableXOF = false, rounds = 24) {
Object.defineProperty(this, "state", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "pos", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "posOut", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "finished", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "state32", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "destroyed", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "_B", {
enumerable: true,
configurable: true,
writable: true,
value: new Uint32Array(10)
});
Object.defineProperty(this, "blockLen", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "suffix", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "outputLen", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "enableXOF", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "rounds", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.blockLen = blockLen;
this.suffix = suffix;
this.outputLen = outputLen;
this.enableXOF = enableXOF;
this.rounds = rounds;
// Can be passed from user as dkLen
(0, utils_js_1.anumber)(outputLen, "outputLen");
// 1600 = 5x5 matrix of 64bit. 1600 bits === 200 bytes
// 0 < blockLen < 200
if (!(0 < blockLen && blockLen < 200)) {
throw new Error("only keccak-f1600 function is supported");
}
this.state = new Uint8Array(200);
this.state32 = (0, utils_js_1.u32)(this.state);
}
clone() {
return this._cloneInto();
}
/** Resets instance to initial (empty) state for reuse. */
reset() {
this.state.fill(0);
this.pos = 0;
this.posOut = 0;
this.finished = false;
this.destroyed = false;
}
keccak() {
(0, utils_js_1.swap32IfBE)(this.state32);
keccakP(this.state32, this.rounds, this._B);
(0, utils_js_1.swap32IfBE)(this.state32);
this.posOut = 0;
this.pos = 0;
}
update(data) {
(0, utils_js_1.aexists)(this);
(0, utils_js_1.abytes)(data);
return this.updateUnsafe(data);
}
/** Like update(), but skips validation. Caller must ensure valid state and input. */
updateUnsafe(data) {
const { blockLen, state } = this;
const len = data.length;
for (let pos = 0; pos < len;) {
const take = Math.min(blockLen - this.pos, len - pos);
for (let i = 0; i < take; i++)
state[this.pos++] ^= data[pos++];
if (this.pos === blockLen)
this.keccak();
}
return this;
}
finish() {
if (this.finished)
return;
this.finished = true;
const { state, suffix, pos, blockLen } = this;
// Do the padding
state[pos] ^= suffix;
if ((suffix & 0x80) !== 0 && pos === blockLen - 1)
this.keccak();
state[blockLen - 1] ^= 0x80;
this.keccak();
}
writeInto(out) {
(0, utils_js_1.aexists)(this, false);
(0, utils_js_1.abytes)(out);
return this.writeIntoUnsafe(out);
}
/** Like writeInto(), but skips validation. Caller must ensure valid state and output. */
writeIntoUnsafe(out) {
this.finish();
const bufferOut = this.state;
const { blockLen } = this;
for (let pos = 0, len = out.length; pos < len;) {
if (this.posOut >= blockLen)
this.keccak();
const take = Math.min(blockLen - this.posOut, len - pos);
out.set(bufferOut.subarray(this.posOut, this.posOut + take), pos);
this.posOut += take;
pos += take;
}
return out;
}
xofInto(out) {
// Sha3/Keccak usage with XOF is probably mistake, only SHAKE instances can do XOF
if (!this.enableXOF) {
throw new Error("XOF is not possible for this instance");
}
return this.writeInto(out);
}
xof(bytes) {
(0, utils_js_1.anumber)(bytes);
return this.xofInto(new Uint8Array(bytes));
}
digestInto(out) {
(0, utils_js_1.aoutput)(out, this);
if (this.finished)
throw new Error("digest() was already called");
this.writeInto(out);
this.destroy();
return out;
}
digest() {
return this.digestInto(new Uint8Array(this.outputLen));
}
destroy() {
this.destroyed = true;
(0, utils_js_1.clean)(this.state);
}
_cloneInto(to) {
const { blockLen, suffix, outputLen, rounds, enableXOF } = this;
to ||= new Keccak(blockLen, suffix, outputLen, enableXOF, rounds);
to.state32.set(this.state32);
to.pos = this.pos;
to.posOut = this.posOut;
to.finished = this.finished;
to.rounds = rounds;
// Suffix can change in cSHAKE
to.suffix = suffix;
to.outputLen = outputLen;
to.enableXOF = enableXOF;
to.destroyed = this.destroyed;
return to;
}
}
exports.Keccak = Keccak;
const genKeccak = (suffix, blockLen, outputLen, info = {}) => (0, utils_js_1.createHasher)(() => new Keccak(blockLen, suffix, outputLen), info);
// /** SHA3-224 hash function. */
// export const sha3_224: CHash = /* @__PURE__ */ genKeccak(
// 0x06,
// 144,
// 28,
// /* @__PURE__ */ oidNist(0x07),
// );
/** SHA3-256 hash function. Different from keccak-256. */
exports.sha3_256 = genKeccak(0x06, 136, 32,
/* @__PURE__ */ (0, utils_js_1.oidNist)(0x08));
// /** SHA3-384 hash function. */
// export const sha3_384: CHash = /* @__PURE__ */ genKeccak(
// 0x06,
// 104,
// 48,
// /* @__PURE__ */ oidNist(0x09),
// );
/** SHA3-512 hash function. */
exports.sha3_512 = genKeccak(0x06, 72, 64,
/* @__PURE__ */ (0, utils_js_1.oidNist)(0x0a));
/** keccak-224 hash function. */
exports.keccak_224 = genKeccak(0x01, 144, 28);
/** keccak-256 hash function. Different from SHA3-256. */
exports.keccak_256 = genKeccak(0x01, 136, 32);
/** keccak-384 hash function. */
exports.keccak_384 = genKeccak(0x01, 104, 48);
/** keccak-512 hash function. */
exports.keccak_512 = genKeccak(0x01, 72, 64);
const genShake = (suffix, blockLen, outputLen, info = {}) => (0, utils_js_1.createHasher)((opts = {}) => new Keccak(blockLen, suffix, opts.dkLen === undefined ? outputLen : opts.dkLen, true), info);
/** SHAKE128 XOF with 128-bit security. */
exports.shake128 =
/* @__PURE__ */
genShake(0x1f, 168, 16, /* @__PURE__ */ (0, utils_js_1.oidNist)(0x0b));
/** SHAKE256 XOF with 256-bit security. */
exports.shake256 =
/* @__PURE__ */
genShake(0x1f, 136, 32, /* @__PURE__ */ (0, utils_js_1.oidNist)(0x0c));
});
// /** SHAKE128 XOF with 256-bit output (NIST version). */
// export const shake128_32: CHashXOF<Keccak, ShakeOpts> =
// /* @__PURE__ */
// genShake(0x1f, 168, 32, /* @__PURE__ */ oidNist(0x0b));
// /** SHAKE256 XOF with 512-bit output (NIST version). */
// export const shake256_64: CHashXOF<Keccak, ShakeOpts> =
// /* @__PURE__ */
// genShake(0x1f, 136, 64, /* @__PURE__ */ oidNist(0x0c));