UNPKG

crystals-kyber-ts

Version:

KYBER is an IND-CCA2-secure key encapsulation mechanism (KEM).

600 lines 20.8 kB
import { Utilities } from "./utilities"; import { Buffer } from "buffer"; import { SHAKE } from "sha3"; import { ByteOps } from "./byte-ops"; import { KyberService } from "../services/kyber.service"; export class Poly { constructor(paramsK) { this.paramsK = paramsK; this.byteOps = new ByteOps(this.paramsK); } /** * Applies the inverse number-theoretic transform (NTT) to all elements of a * vector of polynomials and multiplies by Montgomery factor 2^16 * @param r */ polyVectorInvNTTMont(r) { for (let i = 0; i < this.paramsK; i++) { r[i] = this.polyInvNTTMont(r[i]); } return r; } /** * Applies Barrett reduction to each coefficient of each element of a vector * of polynomials. * * @param r * @return */ polyVectorReduce(r) { for (let i = 0; i < this.paramsK; i++) { r[i] = this.polyReduce(r[i]); } return r; } /** * Computes an in-place inverse of a negacyclic number-theoretic transform * (NTT) of a polynomial * * Input is assumed bit-revered order * * Output is assumed normal order * * @param r * @return */ polyInvNTTMont(r) { return this.invNTT(r); } /** * Applies forward number-theoretic transforms (NTT) to all elements of a * vector of polynomial * * @param r * @return */ polyVectorNTT(r) { for (let i = 0; i < this.paramsK; i++) { r[i] = this.ntt(r[i]); } return r; } /** * Deserialize a byte array into a polynomial vector * * @param a * @return */ polyVectorFromBytes(a) { let r = []; let start; let end; for (let i = 0; i < this.paramsK; i++) { start = (i * KyberService.paramsPolyBytes); end = (i + 1) * KyberService.paramsPolyBytes; r[i] = this.polyFromBytes(a.slice(start, end)); } return r; } /** * Serialize a polynomial in to an array of bytes * * @param a * @return */ polyToBytes(a) { let t0, t1; let r = []; let a2 = this.polyConditionalSubQ(a); for (let i = 0; i < KyberService.paramsN / 2; i++) { t0 = Utilities.uint16(a2[2 * i]); t1 = Utilities.uint16(a2[2 * i + 1]); r[3 * i + 0] = Utilities.byte(t0 >> 0); r[3 * i + 1] = Utilities.byte(t0 >> 8) | Utilities.byte(t1 << 4); r[3 * i + 2] = Utilities.byte(t1 >> 4); } return r; } /** * Check the 0xFFF * @param a */ polyFromBytes(a) { let r = []; for (let i = 0; i < KyberService.paramsPolyBytes; i++) { r[i] = 0; } for (let i = 0; i < KyberService.paramsN / 2; i++) { r[2 * i] = Utilities.int16(((Utilities.uint16(a[3 * i + 0]) >> 0) | (Utilities.uint16(a[3 * i + 1]) << 8)) & 0xFFF); r[2 * i + 1] = Utilities.int16(((Utilities.uint16(a[3 * i + 1]) >> 4) | (Utilities.uint16(a[3 * i + 2]) << 4)) & 0xFFF); } return r; } /** * Convert a polynomial to a 32-byte message * * @param a * @return */ polyToMsg(a) { const msg = []; // 32 let t; const a2 = this.polyConditionalSubQ(a); for (let i = 0; i < KyberService.paramsN / 8; i++) { msg[i] = 0; for (let j = 0; j < 8; j++) { t = (((Utilities.uint16(a2[8 * i + j]) << 1) + Utilities.uint16(KyberService.paramsQ / 2)) / Utilities.uint16(KyberService.paramsQ)) & 1; msg[i] |= Utilities.byte(t << j); } } return msg; } /** * Convert a 32-byte message to a polynomial * * @param msg * @return */ polyFromData(msg) { let r = []; for (let i = 0; i < KyberService.paramsPolyBytes; ++i) { r[i] = 0; } let mask; for (let i = 0; i < KyberService.paramsN / 8; i++) { for (let j = 0; j < 8; j++) { mask = -1 * Utilities.int16((msg[i] >> j) & 1); r[8 * i + j] = mask & Utilities.int16((KyberService.paramsQ + 1) / 2); } } return r; } /** * Generate a deterministic noise polynomial from a seed and nonce * * The polynomial output will be close to a centered binomial distribution * * @param seed * @param nonce * @param paramsK * @return */ getNoisePoly(seed, nonce, paramsK) { let l; switch (paramsK) { case 2: l = KyberService.paramsETAK512 * KyberService.paramsN / 4; break; default: l = KyberService.paramsETAK768K1024 * KyberService.paramsN / 4; } let p = this.generatePRFByteArray(l, seed, nonce); return this.byteOps.generateCBDPoly(p, paramsK); } /** * Pseudo-random function to derive a deterministic array of random bytes * from the supplied secret key object and other parameters. * * @param l * @param key * @param nonce * @return */ generatePRFByteArray(l, key, nonce) { const nonce_arr = []; // 1 nonce_arr[0] = nonce; const hash = new SHAKE(256); hash.reset(); const buffer1 = Buffer.from(key); const buffer2 = Buffer.from(nonce_arr); hash.update(buffer1).update(buffer2); const bufString = hash.digest({ format: "binary", buffer: Buffer.alloc(l) }); // 128 long byte array const buf = Buffer.alloc(bufString.length); for (let i = 0; i < bufString.length; ++i) { buf[i] = +bufString[i]; } return buf; } /** * Perform an in-place number-theoretic transform (NTT) * * Input is in standard order * * Output is in bit-reversed order * * @param r * @return */ ntt(r) { let j = 0; let k = 1; let zeta; let t; for (let l = 128; l >= 2; l >>= 1) { // 0, for (let start = 0; start < 256; start = j + l) { zeta = KyberService.nttZetas[k]; k++; for (j = start; j < start + l; j++) { t = this.byteOps.modQMulMont(zeta, r[j + l]); // t is mod q r[j + l] = Utilities.int16(r[j] - t); r[j] = Utilities.int16(r[j] + t); } } } return r; } /** * Apply Barrett reduction to all coefficients of this polynomial * * @param r * @return */ polyReduce(r) { for (let i = 0; i < KyberService.paramsN; i++) { r[i] = this.byteOps.barrettReduce(r[i]); } return r; } /** * Performs an in-place conversion of all coefficients of a polynomial from * the normal domain to the Montgomery domain * * @param polyR * @return */ polyToMont(r) { for (let i = 0; i < KyberService.paramsN; i++) { r[i] = this.byteOps.byteopsMontgomeryReduce(Utilities.int32(r[i]) * Utilities.int32(1353)); } return r; } /** * Pointwise-multiplies elements of the given polynomial-vectors , * accumulates the results , and then multiplies by 2^-16 * * @param a * @param b * @return */ polyVectorPointWiseAccMont(a, b) { let r = this.polyBaseMulMont(a[0], b[0]); let t; for (let i = 1; i < this.paramsK; i++) { t = this.polyBaseMulMont(a[i], b[i]); r = this.polyAdd(r, t); } return this.polyReduce(r); } /** * Multiply two polynomials in the number-theoretic transform (NTT) domain * * @param a * @param b * @return */ polyBaseMulMont(a, b) { let rx, ry; for (let i = 0; i < KyberService.paramsN / 4; i++) { rx = this.nttBaseMuliplier(a[4 * i + 0], a[4 * i + 1], b[4 * i + 0], b[4 * i + 1], KyberService.nttZetas[64 + i]); ry = this.nttBaseMuliplier(a[4 * i + 2], a[4 * i + 3], b[4 * i + 2], b[4 * i + 3], -KyberService.nttZetas[64 + i]); a[4 * i + 0] = rx[0]; a[4 * i + 1] = rx[1]; a[4 * i + 2] = ry[0]; a[4 * i + 3] = ry[1]; } return a; } /** * Performs the multiplication of polynomials * * @param a0 * @param a1 * @param b0 * @param b1 * @param zeta * @return */ nttBaseMuliplier(a0, a1, b0, b1, zeta) { let r = []; // 2 r[0] = this.byteOps.modQMulMont(a1, b1); r[0] = this.byteOps.modQMulMont(r[0], zeta); r[0] = r[0] + this.byteOps.modQMulMont(a0, b0); r[1] = this.byteOps.modQMulMont(a0, b1); r[1] = r[1] + this.byteOps.modQMulMont(a1, b0); return r; } /** * Add two polynomial vectors * * @param a * @param b * @return */ polyVectorAdd(a, b) { for (let i = 0; i < this.paramsK; i++) { a[i] = this.polyAdd(a[i], b[i]); } return a; } /** * Add two polynomials * * @param a * @param b * @return */ polyAdd(a, b) { let c = []; // needs to be 384 for (let i = 0; i < a.length; ++i) { c[i] = 0; } for (let i = 0; i < KyberService.paramsN; i++) { c[i] = a[i] + b[i]; } return c; } /** * Subtract two polynomials * * @param a * @param b * @return */ subtract(a, b) { for (let i = 0; i < KyberService.paramsN; i++) { a[i] = a[i] - b[i]; } return a; } /** * Perform an in-place inverse number-theoretic transform (NTT) * * Input is in bit-reversed order * * Output is in standard order * * @param r * @return */ invNTT(r) { let j = 0; let k = 0; let zeta; let t; for (let l = 2; l <= 128; l <<= 1) { for (let start = 0; start < 256; start = j + l) { zeta = KyberService.nttZetasInv[k]; k = k + 1; for (j = start; j < start + l; j++) { t = r[j]; r[j] = this.byteOps.barrettReduce(t + r[j + l]); r[j + l] = t - r[j + l]; r[j + l] = this.byteOps.modQMulMont(zeta, r[j + l]); } } } for (j = 0; j < 256; j++) { r[j] = this.byteOps.modQMulMont(r[j], KyberService.nttZetasInv[127]); } return r; } /** * Perform a lossly compression and serialization of a vector of polynomials * * @param a * @param paramsK * @return */ compressPolyVector(a) { a = this.polyVectorCSubQ(a); let rr = 0; let r = []; let t = []; switch (this.paramsK) { case 2: case 3: for (let i = 0; i < this.paramsK; i++) { for (let j = 0; j < KyberService.paramsN / 4; j++) { for (let k = 0; k < 4; k++) { t[k] = (((a[i][4 * j + k] << 10) + KyberService.paramsQ / 2) / KyberService.paramsQ) & 0b1111111111; } r[rr + 0] = Utilities.byte(t[0] >> 0); r[rr + 1] = Utilities.byte(Utilities.byte(t[0] >> 8) | Utilities.byte(t[1] << 2)); r[rr + 2] = Utilities.byte(Utilities.byte(t[1] >> 6) | Utilities.byte(t[2] << 4)); r[rr + 3] = Utilities.byte(Utilities.byte(t[2] >> 4) | Utilities.byte(t[3] << 6)); r[rr + 4] = Utilities.byte((t[3] >> 2)); rr = rr + 5; } } break; default: for (let i = 0; i < this.paramsK; i++) { for (let j = 0; j < KyberService.paramsN / 8; j++) { for (let k = 0; k < 8; k++) { t[k] = Utilities.int32((((Utilities.int32(a[i][8 * j + k]) << 11) + Utilities.int32(KyberService.paramsQ / 2)) / Utilities.int32(KyberService.paramsQ)) & 0x7ff); } r[rr + 0] = Utilities.byte((t[0] >> 0)); r[rr + 1] = Utilities.byte((t[0] >> 8) | (t[1] << 3)); r[rr + 2] = Utilities.byte((t[1] >> 5) | (t[2] << 6)); r[rr + 3] = Utilities.byte((t[2] >> 2)); r[rr + 4] = Utilities.byte((t[2] >> 10) | (t[3] << 1)); r[rr + 5] = Utilities.byte((t[3] >> 7) | (t[4] << 4)); r[rr + 6] = Utilities.byte((t[4] >> 4) | (t[5] << 7)); r[rr + 7] = Utilities.byte((t[5] >> 1)); r[rr + 8] = Utilities.byte((t[5] >> 9) | (t[6] << 2)); r[rr + 9] = Utilities.byte((t[6] >> 6) | (t[7] << 5)); r[rr + 10] = Utilities.byte((t[7] >> 3)); rr = rr + 11; } } } return r; } /** * Performs lossy compression and serialization of a polynomial * * @param polyA * @return */ compressPoly(polyA) { let rr = 0; let r = []; let t = []; // 8 const qDiv2 = (KyberService.paramsQ / 2); switch (this.paramsK) { case 2: case 3: for (let i = 0; i < KyberService.paramsN / 8; i++) { for (let j = 0; j < 8; j++) { const step1 = Utilities.int32((polyA[8 * i + j]) << 4); const step2 = Utilities.int32((step1 + qDiv2) / (KyberService.paramsQ)); t[j] = Utilities.intToByte(step2 & 15); } r[rr + 0] = Utilities.intToByte(t[0] | (t[1] << 4)); r[rr + 1] = Utilities.intToByte(t[2] | (t[3] << 4)); r[rr + 2] = Utilities.intToByte(t[4] | (t[5] << 4)); r[rr + 3] = Utilities.intToByte(t[6] | (t[7] << 4)); rr = rr + 4; } break; default: for (let i = 0; i < KyberService.paramsN / 8; i++) { for (let j = 0; j < 8; j++) { const step1 = Utilities.int32((polyA[(8 * i) + j] << 5)); const step2 = Utilities.int32((step1 + qDiv2) / (KyberService.paramsQ)); t[j] = Utilities.intToByte(step2 & 31); } r[rr + 0] = Utilities.intToByte((t[0] >> 0) | (t[1] << 5)); r[rr + 1] = Utilities.intToByte((t[1] >> 3) | (t[2] << 2) | (t[3] << 7)); r[rr + 2] = Utilities.intToByte((t[3] >> 1) | (t[4] << 4)); r[rr + 3] = Utilities.intToByte((t[4] >> 4) | (t[5] << 1) | (t[6] << 6)); r[rr + 4] = Utilities.intToByte((t[6] >> 2) | (t[7] << 3)); rr = rr + 5; } } return r; } /** * De-serialize and decompress a vector of polynomials * * Since the compress is lossy, the results will not be exactly the same as * the original vector of polynomials * * @param a * @return */ decompressPolyVector(a) { const r = []; // this.paramsK for (let i = 0; i < this.paramsK; i++) { r[i] = []; } let aa = 0; const t = []; // 8 switch (this.paramsK) { //TESTED case 2: case 3: let ctr = 0; for (let i = 0; i < this.paramsK; i++) { for (let j = 0; j < (KyberService.paramsN / 4); j++) { t[0] = (Utilities.uint16(a[aa + 0]) >> 0) | (Utilities.uint16(a[aa + 1]) << 8); t[1] = (Utilities.uint16(a[aa + 1]) >> 2) | (Utilities.uint16(a[aa + 2]) << 6); t[2] = (Utilities.uint16(a[aa + 2]) >> 4) | (Utilities.uint16(a[aa + 3]) << 4); t[3] = (Utilities.uint16(a[aa + 3]) >> 6) | (Utilities.uint16(a[aa + 4]) << 2); aa = aa + 5; ++ctr; for (let k = 0; k < 4; k++) { r[i][4 * j + k] = (Utilities.uint32(t[k] & 0x3FF) * KyberService.paramsQ + 512) >> 10; } } } break; default: for (let i = 0; i < this.paramsK; i++) { for (let j = 0; j < KyberService.paramsN / 8; j++) { t[0] = (Utilities.uint16(a[aa + 0]) >> 0) | (Utilities.uint16(a[aa + 1]) << 8); t[1] = (Utilities.uint16(a[aa + 1]) >> 3) | (Utilities.uint16(a[aa + 2]) << 5); t[2] = (Utilities.uint16(a[aa + 2]) >> 6) | (Utilities.uint16(a[aa + 3]) << 2) | (Utilities.uint16(a[aa + 4]) << 10); t[3] = (Utilities.uint16(a[aa + 4]) >> 1) | (Utilities.uint16(a[aa + 5]) << 7); t[4] = (Utilities.uint16(a[aa + 5]) >> 4) | (Utilities.uint16(a[aa + 6]) << 4); t[5] = (Utilities.uint16(a[aa + 6]) >> 7) | (Utilities.uint16(a[aa + 7]) << 1) | (Utilities.uint16(a[aa + 8]) << 9); t[6] = (Utilities.uint16(a[aa + 8]) >> 2) | (Utilities.uint16(a[aa + 9]) << 6); t[7] = (Utilities.uint16(a[aa + 9]) >> 5) | (Utilities.uint16(a[aa + 10]) << 3); aa = aa + 11; for (let k = 0; k < 8; k++) { r[i][8 * j + k] = (Utilities.uint32(t[k] & 0x7FF) * KyberService.paramsQ + 1024) >> 11; } } } } return r; } /** * Applies the conditional subtraction of Q (KyberParams) to each coefficient of * each element of a vector of polynomials. */ polyVectorCSubQ(r) { for (let i = 0; i < this.paramsK; i++) { r[i] = this.polyConditionalSubQ(r[i]); } return r; } /** * Apply the conditional subtraction of Q (KyberParams) to each coefficient of a * polynomial * * @param r * @return */ polyConditionalSubQ(r) { for (let i = 0; i < KyberService.paramsN; i++) { r[i] = r[i] - KyberService.paramsQ; r[i] = r[i] + ((r[i] >> 31) & KyberService.paramsQ); } return r; } /** * De-serialize and decompress a vector of polynomials * * Since the compress is lossy, the results will not be exactly the same as * the original vector of polynomials * * @param a * @return */ decompressPoly(a) { let r = []; // 384 let t = []; // 8 let aa = 0; switch (this.paramsK) { case 2: case 3: // TESTED for (let i = 0; i < KyberService.paramsN / 2; i++) { r[2 * i + 0] = Utilities.int16((((Utilities.byte(a[aa]) & 15) * Utilities.uint32(KyberService.paramsQ)) + 8) >> 4); r[2 * i + 1] = Utilities.int16((((Utilities.byte(a[aa]) >> 4) * Utilities.uint32(KyberService.paramsQ)) + 8) >> 4); aa = aa + 1; } break; default: for (let i = 0; i < KyberService.paramsN / 8; i++) { t[0] = (a[aa + 0] >> 0); t[1] = Utilities.byte(a[aa + 0] >> 5) | Utilities.byte((a[aa + 1] << 3)); t[2] = (a[aa + 1] >> 2); t[3] = Utilities.byte((a[aa + 1] >> 7)) | Utilities.byte((a[aa + 2] << 1)); t[4] = Utilities.byte((a[aa + 2] >> 4)) | Utilities.byte((a[aa + 3] << 4)); t[5] = (a[aa + 3] >> 1); t[6] = Utilities.byte((a[aa + 3] >> 6)) | Utilities.byte((a[aa + 4] << 2)); t[7] = (a[aa + 4] >> 3); aa = aa + 5; for (let j = 0; j < 8; j++) { r[8 * i + j] = Utilities.int16(((Utilities.byte(t[j] & 31) * Utilities.uint32(KyberService.paramsQ)) + 16) >> 5); } } } return r; } } //# sourceMappingURL=poly.js.map