crystals-kyber-ts
Version:
KYBER is an IND-CCA2-secure key encapsulation mechanism (KEM).
284 lines • 11.4 kB
JavaScript
import { Buffer } from "buffer";
import { Utilities } from "./utilities";
import { SHA3, SHAKE } from "sha3";
import { Poly } from "./poly";
import { KyberService } from "../services/kyber.service";
export class Indcpa {
constructor(paramsK) {
this.paramsK = paramsK;
this.poly = new Poly(this.paramsK);
}
/**
* Generates public and private keys for the CPA-secure public-key
* encryption scheme underlying Kyber.
*/
indcpaKeyGen() {
// random bytes for seed
const rnd = Buffer.alloc(KyberService.paramsSymBytes);
for (let i = 0; i < KyberService.paramsSymBytes; i++) {
rnd[i] = Utilities.nextInt(256);
}
// hash rnd with SHA3-512
const buffer1 = Buffer.from(rnd);
const hash1 = new SHA3(512);
hash1.update(buffer1);
const seed = hash1.digest();
const publicSeedBuf = seed.slice(0, KyberService.paramsSymBytes);
const noiseSeedBuf = seed.slice(KyberService.paramsSymBytes, (KyberService.paramsSymBytes * 2));
const publicSeed = [];
const noiseSeed = [];
for (const num of publicSeedBuf) {
publicSeed.push(num);
}
for (const num of noiseSeedBuf) {
noiseSeed.push(num);
}
// generate public matrix A (already in NTT form)
const a = this.generateMatrix(publicSeed, false);
const s = []; //this.paramsK
const e = []; // this.paramsK
for (let i = 0; i < this.paramsK; i++) {
s[i] = this.poly.getNoisePoly(noiseSeed, i, this.paramsK);
e[i] = this.poly.getNoisePoly(noiseSeed, (i + this.paramsK), this.paramsK);
}
for (let i = 0; i < this.paramsK; i++) {
s[i] = this.poly.ntt(s[i]);
}
for (let i = 0; i < this.paramsK; i++) {
e[i] = this.poly.ntt(e[i]);
}
for (let i = 0; i < this.paramsK; i++) {
s[i] = this.poly.polyReduce(s[i]);
}
const pk = []; // this.paramsK
for (let i = 0; i < this.paramsK; i++) {
pk[i] = this.poly.polyToMont(this.poly.polyVectorPointWiseAccMont(a[i], s));
}
for (let i = 0; i < this.paramsK; i++) {
pk[i] = this.poly.polyAdd(pk[i], e[i]);
}
for (let i = 0; i < this.paramsK; i++) {
pk[i] = this.poly.polyReduce(pk[i]);
}
// ENCODE KEYS
const keys = []; // 2
// PUBLIC KEY
// turn polynomials into byte arrays
keys[0] = [];
let bytes = [];
for (let i = 0; i < this.paramsK; i++) {
bytes = this.poly.polyToBytes(pk[i]);
for (let j = 0; j < bytes.length; j++) {
keys[0].push(bytes[j]);
}
}
// append public seed
for (let i = 0; i < publicSeed.length; i++) {
keys[0].push(publicSeed[i]);
}
// PRIVATE KEY
keys[1] = [];
bytes = [];
for (let i = 0; i < this.paramsK; i++) {
bytes = this.poly.polyToBytes(s[i]);
for (let j = 0; j < bytes.length; j++) {
keys[1].push(bytes[j]);
}
}
return keys;
}
/**
* Encrypt the given message using the Kyber public-key encryption scheme
*
* @param publicKey
* @param msg
* @param coins
* @return
*/
indcpaEncrypt(publicKey, msg, coins) {
const pk = [];
let start;
let end;
// decode message m
let k = this.poly.polyFromData(msg);
for (let i = 0; i < this.paramsK; i++) {
start = (i * KyberService.paramsPolyBytes);
end = (i + 1) * KyberService.paramsPolyBytes;
pk[i] = this.poly.polyFromBytes(publicKey.slice(start, end));
}
let seed;
switch (this.paramsK) {
case 2:
seed = publicKey.slice(KyberService.paramsPolyvecBytesK512, KyberService.paramsIndcpaPublicKeyBytesK512);
break;
case 3:
seed = publicKey.slice(KyberService.paramsPolyvecBytesK768, KyberService.paramsIndcpaPublicKeyBytesK768);
break;
default:
seed = publicKey.slice(KyberService.paramsPolyvecBytesK1024, KyberService.paramsIndcpaPublicKeyBytesK1024);
}
const at = this.generateMatrix(seed, true);
const sp = []; // this.paramsK
const ep = []; // this.paramsK
for (let i = 0; i < this.paramsK; i++) {
sp[i] = this.poly.getNoisePoly(coins, i, this.paramsK);
ep[i] = this.poly.getNoisePoly(coins, i + this.paramsK, 3);
}
let epp = this.poly.getNoisePoly(coins, (this.paramsK * 2), 3);
for (let i = 0; i < this.paramsK; i++) {
sp[i] = this.poly.ntt(sp[i]);
}
for (let i = 0; i < this.paramsK; i++) {
sp[i] = this.poly.polyReduce(sp[i]);
}
let bp = []; // this.paramsK
for (let i = 0; i < this.paramsK; i++) {
bp[i] = this.poly.polyVectorPointWiseAccMont(at[i], sp);
}
let v = this.poly.polyVectorPointWiseAccMont(pk, sp);
bp = this.poly.polyVectorInvNTTMont(bp);
v = this.poly.invNTT(v);
bp = this.poly.polyVectorAdd(bp, ep);
v = this.poly.polyAdd(v, epp);
v = this.poly.polyAdd(v, k);
bp = this.poly.polyVectorReduce(bp);
v = this.poly.polyReduce(v);
const bCompress = this.poly.compressPolyVector(bp);
const vCompress = this.poly.compressPoly(v);
const c3 = [];
for (let i = 0; i < bCompress.length; ++i) {
c3[i] = bCompress[i];
}
for (let i = 0; i < vCompress.length; ++i) {
c3[i + bCompress.length] = vCompress[i];
}
return c3;
}
/**
* Decrypt the given byte array using the Kyber public-key encryption scheme
*
* @param packedCipherText
* @param privateKey
* @return
*/
indcpaDecrypt(packedCipherText, privateKey) {
let bpEndIndex;
let vEndIndex;
switch (this.paramsK) {
case 2:
bpEndIndex = KyberService.paramsPolyvecCompressedBytesK512;
vEndIndex = bpEndIndex + KyberService.paramsPolyCompressedBytesK512;
break;
case 3:
bpEndIndex = KyberService.paramsPolyvecCompressedBytesK768;
vEndIndex = bpEndIndex + KyberService.paramsPolyCompressedBytesK768;
break;
default:
bpEndIndex = KyberService.paramsPolyvecCompressedBytesK1024;
vEndIndex = bpEndIndex + KyberService.paramsPolyCompressedBytesK1024;
}
let bp = this.poly.decompressPolyVector(packedCipherText.slice(0, bpEndIndex));
const v = this.poly.decompressPoly(packedCipherText.slice(bpEndIndex, vEndIndex));
const privateKeyPolyvec = this.poly.polyVectorFromBytes(privateKey);
bp = this.poly.polyVectorNTT(bp);
let mp = this.poly.polyVectorPointWiseAccMont(privateKeyPolyvec, bp);
mp = this.poly.invNTT(mp);
mp = this.poly.subtract(v, mp);
mp = this.poly.polyReduce(mp);
return this.poly.polyToMsg(mp);
}
/**
* Generate a polynomial vector matrix from the given seed
*
* @param seed
* @param transposed
* @return
*/
generateMatrix(seed, transposed) {
let a = []; //this.paramsK)
const xof = new SHAKE(128);
let ctr = 0;
for (let i = 0; i < this.paramsK; i++) {
a[i] = []; // this.paramsK
let transpose = []; // 2
for (let j = 0; j < this.paramsK; j++) {
// set if transposed matrix or not
transpose[0] = j;
transpose[1] = i;
if (transposed) {
transpose[0] = i;
transpose[1] = j;
}
// obtain xof of (seed+i+j) or (seed+j+i) depending on above code
// output is 672 bytes in length
xof.reset();
const buffer1 = Buffer.from(seed);
const buffer2 = Buffer.from(transpose);
xof.update(buffer1).update(buffer2);
let outputString = xof.digest({ format: "binary", buffer: Buffer.alloc(672) });
let output = Buffer.alloc(outputString.length);
output.fill(outputString);
// run rejection sampling on the output from above
let outputlen = 3 * 168; // 504
let result = []; // 2
result = this.generateUniform(output.slice(0, 504), outputlen, KyberService.paramsN);
a[i][j] = result[0]; // the result here is an NTT-representation
ctr = result[1]; // keeps track of index of output array from sampling function
while (ctr < KyberService.paramsN) { // if the polynomial hasnt been filled yet with mod q entries
const outputn = output.slice(504, 672); // take last 168 bytes of byte array from xof
let result1 = []; //2
result1 = this.generateUniform(outputn, 168, KyberService.paramsN - ctr); // run sampling function again
let missing = result1[0]; // here is additional mod q polynomial coefficients
let ctrn = result1[1]; // how many coefficients were accepted and are in the output
// starting at last position of output array from first sampling function until 256 is reached
for (let k = ctr; k < KyberService.paramsN; k++) {
a[i][j][k] = missing[k - ctr]; // fill rest of array with the additional coefficients until full
}
ctr = ctr + ctrn; // update index
}
}
}
return a;
}
/**
* Runs rejection sampling on uniform random bytes to generate uniform
* random integers modulo `Q`
*
* @param buf
* @param bufl
* @param len
* @return
*/
generateUniform(buf, bufl, len) {
let uniformR = [];
for (let i = 0; i < KyberService.paramsPolyBytes; ++i) {
uniformR[i] = 0;
}
let d1, d2;
let j = 0;
let uniformI = 0;
while ((uniformI < len) && ((j + 3) <= bufl)) {
// compute d1 and d2
d1 = (Utilities.uint16((buf[j]) >> 0) | (Utilities.uint16(buf[j + 1]) << 8)) & 0xFFF;
d2 = (Utilities.uint16((buf[j + 1]) >> 4) | (Utilities.uint16(buf[j + 2]) << 4)) & 0xFFF;
// increment input buffer index by 3
j = j + 3;
// if d1 is less than 3329
if (d1 < KyberService.paramsQ) {
// assign to d1
uniformR[uniformI] = d1;
// increment position of output array
++uniformI;
}
if (uniformI < len && d2 < KyberService.paramsQ) {
uniformR[uniformI] = d2;
++uniformI;
}
}
let result = []; // 2
result[0] = uniformR; // returns polynomial NTT representation
result[1] = uniformI; // ideally should return 256
return result;
}
}
//# sourceMappingURL=indcpa.js.map