mirror of
https://github.com/antontutoveanu/crystals-kyber-javascript.git
synced 2026-05-09 03:00:24 -04:00
1326 lines
40 KiB
JavaScript
1326 lines
40 KiB
JavaScript
|
|
const { SHA3 } = require('sha3');
|
|
const { SHAKE } = require('sha3');
|
|
|
|
const nttZetas = [
|
|
2285, 2571, 2970, 1812, 1493, 1422, 287, 202, 3158, 622, 1577, 182, 962,
|
|
2127, 1855, 1468, 573, 2004, 264, 383, 2500, 1458, 1727, 3199, 2648, 1017,
|
|
732, 608, 1787, 411, 3124, 1758, 1223, 652, 2777, 1015, 2036, 1491, 3047,
|
|
1785, 516, 3321, 3009, 2663, 1711, 2167, 126, 1469, 2476, 3239, 3058, 830,
|
|
107, 1908, 3082, 2378, 2931, 961, 1821, 2604, 448, 2264, 677, 2054, 2226,
|
|
430, 555, 843, 2078, 871, 1550, 105, 422, 587, 177, 3094, 3038, 2869, 1574,
|
|
1653, 3083, 778, 1159, 3182, 2552, 1483, 2727, 1119, 1739, 644, 2457, 349,
|
|
418, 329, 3173, 3254, 817, 1097, 603, 610, 1322, 2044, 1864, 384, 2114, 3193,
|
|
1218, 1994, 2455, 220, 2142, 1670, 2144, 1799, 2051, 794, 1819, 2475, 2459,
|
|
478, 3221, 3021, 996, 991, 958, 1869, 1522, 1628];
|
|
|
|
const nttZetasInv = [
|
|
1701, 1807, 1460, 2371, 2338, 2333, 308, 108, 2851, 870, 854, 1510, 2535,
|
|
1278, 1530, 1185, 1659, 1187, 3109, 874, 1335, 2111, 136, 1215, 2945, 1465,
|
|
1285, 2007, 2719, 2726, 2232, 2512, 75, 156, 3000, 2911, 2980, 872, 2685,
|
|
1590, 2210, 602, 1846, 777, 147, 2170, 2551, 246, 1676, 1755, 460, 291, 235,
|
|
3152, 2742, 2907, 3224, 1779, 2458, 1251, 2486, 2774, 2899, 1103, 1275, 2652,
|
|
1065, 2881, 725, 1508, 2368, 398, 951, 247, 1421, 3222, 2499, 271, 90, 853,
|
|
1860, 3203, 1162, 1618, 666, 320, 8, 2813, 1544, 282, 1838, 1293, 2314, 552,
|
|
2677, 2106, 1571, 205, 2918, 1542, 2721, 2597, 2312, 681, 130, 1602, 1871,
|
|
829, 2946, 3065, 1325, 2756, 1861, 1474, 1202, 2367, 3147, 1752, 2707, 171,
|
|
3127, 3042, 1907, 1836, 1517, 359, 758, 1441];
|
|
|
|
const paramsK = 3;
|
|
const paramsN = 256;
|
|
const paramsQ = 3329;
|
|
const paramsQinv = 62209;
|
|
const paramsETA = 2;
|
|
const paramsPolyBytes = 384;
|
|
const Kyber768SKBytes = 1152 + ((1152 + 32) + 2 * 32);
|
|
const paramsPolyCompressedBytesK768 = 128;
|
|
const paramsPolyvecCompressedBytesK768 = 3 * 320; // 960
|
|
|
|
// ----------------------------------------------------------------------------------------------
|
|
// From http://baagoe.com/en/RandomMusings/javascript/
|
|
// Johannes Baagøe <baagoe@baagoe.com>, 2010
|
|
// ----------------------------------------------------------------------------------------------
|
|
// From: https://github.com/FuKyuToTo/lattice-based-cryptography
|
|
// ----------------------------------------------------------------------------------------------
|
|
function Mash() {
|
|
var n = 0xefc8249d;
|
|
|
|
var mash = function (data) {
|
|
data = data.toString();
|
|
for (var i = 0; i < data.length; i++) {
|
|
n += data.charCodeAt(i);
|
|
var h = 0.02519603282416938 * n;
|
|
n = h >>> 0;
|
|
h -= n;
|
|
h *= n;
|
|
n = h >>> 0;
|
|
h -= n;
|
|
n += h * 0x100000000; // 2^32
|
|
}
|
|
return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
|
|
};
|
|
mash.version = "Mash 0.9";
|
|
return mash;
|
|
}
|
|
function Alea() {
|
|
return (function (args) {
|
|
var s0 = 0;
|
|
var s1 = 0;
|
|
var s2 = 0;
|
|
var c = 1;
|
|
|
|
if (args.length === 0) {
|
|
args = [+new Date()];
|
|
}
|
|
var mash = Mash();
|
|
s0 = mash(" ");
|
|
s1 = mash(" ");
|
|
s2 = mash(" ");
|
|
|
|
for (var i = 0; i < args.length; i++) {
|
|
s0 -= mash(args[i]);
|
|
if (s0 < 0) {
|
|
s0 += 1;
|
|
}
|
|
s1 -= mash(args[i]);
|
|
if (s1 < 0) {
|
|
s1 += 1;
|
|
}
|
|
s2 -= mash(args[i]);
|
|
if (s2 < 0) {
|
|
s2 += 1;
|
|
}
|
|
}
|
|
mash = null;
|
|
|
|
var random = function () {
|
|
var t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32
|
|
s0 = s1;
|
|
s1 = s2;
|
|
return (s2 = t - (c = t | 0));
|
|
};
|
|
random.uint32 = function () {
|
|
return random() * 0x100000000; // 2^32
|
|
};
|
|
random.fract53 = function () {
|
|
return random() + ((random() * 0x200000) | 0) * 1.1102230246251565e-16; // 2^-53
|
|
};
|
|
random.version = "Alea 0.9";
|
|
random.args = args;
|
|
return random;
|
|
})(Array.prototype.slice.call(arguments));
|
|
}
|
|
|
|
//prng
|
|
var random = Alea();
|
|
var seed = random.args;
|
|
random = Alea(seed);
|
|
|
|
// Returns the next pseudorandom, uniformly distributed integer between 0(inclusive) and q-1(inclusive)
|
|
function nextInt(n) {
|
|
return Math.floor(random() * n); //prng.js -> random()
|
|
}
|
|
|
|
function hexToDec(hexString) {
|
|
return parseInt(hexString, 16);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------------
|
|
// Translated to javascript from: https://github.com/symbolicsoft/kyber-k2so
|
|
// ----------------------------------------------------------------------------------------------
|
|
|
|
export function KeyGen768() {
|
|
|
|
var indcpakeys = indcpaKeypair(paramsK);
|
|
|
|
var indcpaPublicKey = indcpakeys[0];
|
|
var indcpaPrivateKey = indcpakeys[1];
|
|
|
|
// get hash of indcpapublickey
|
|
const buffer1 = Buffer.from(indcpaPublicKey);
|
|
const hash1 = new SHA3(256);
|
|
hash1.update(buffer1);
|
|
var buf_str = hash1.digest('hex');
|
|
// convert hex string to array
|
|
var pkh = new Array(32);
|
|
for (i = 0; i < 32; i++) {
|
|
pkh[i] = hexToDec(buf_str[2 * i] + buf_str[2 * i + 1]);
|
|
}
|
|
|
|
// read 32 random values (0-255) into a 32 byte array
|
|
var rnd = new Array(32);
|
|
for (var i = 0; i < 32; i++) {
|
|
rnd[i] = nextInt(256);
|
|
}
|
|
|
|
var privateKey = indcpaPrivateKey;
|
|
for (var i = 0; i < indcpaPublicKey.length; i++) {
|
|
privateKey.push(indcpaPublicKey[i]);
|
|
}
|
|
for (var i = 0; i < pkh.length; i++) {
|
|
privateKey.push(pkh[i]);
|
|
}
|
|
for (var i = 0; i < rnd.length; i++) {
|
|
privateKey.push(rnd[i]);
|
|
}
|
|
|
|
var keys = new Array(2);
|
|
keys[0] = indcpaPublicKey;
|
|
keys[1] = privateKey;
|
|
return keys;
|
|
}
|
|
|
|
export function Encrypt768(pk) {
|
|
// generate (c, ss) from pk (pk is a 1184 byte array)
|
|
// send c to server
|
|
var publicKey = pk;
|
|
|
|
// make 32 byte array
|
|
var sharedSecret = new Array(32);
|
|
|
|
// make a 64 byte buffer array
|
|
var buf = new Array(64);
|
|
|
|
// read 32 random values (0-255) into the 64 byte array
|
|
for (var i = 0; i < 32; i++) {
|
|
buf[i] = nextInt(256);
|
|
}
|
|
|
|
// buf_tmp = buf[:32]
|
|
var buf_tmp = buf.slice(0, 32);
|
|
const buffer1 = Buffer.from(buf_tmp);
|
|
|
|
// buf1 = sha3.sum256 of buf1
|
|
const hash1 = new SHA3(256);
|
|
hash1.update(buffer1);
|
|
buf_tmp = hash1.digest('hex');
|
|
// convert hex string to array
|
|
var buf1 = new Array(32);
|
|
for (i = 0; i < 32; i++) {
|
|
buf1[i] = hexToDec(buf_tmp[2 * i] + buf_tmp[2 * i + 1]);
|
|
}
|
|
|
|
// buf2 = sha3.sum256 of publicKey[0:1184]
|
|
const buffer2 = Buffer.from(publicKey);
|
|
const hash2 = new SHA3(256);
|
|
hash2.update(buffer2);
|
|
buf_tmp = hash2.digest('hex');
|
|
// convert hex string to array
|
|
var buf2 = new Array(32);
|
|
for (i = 0; i < 32; i++) {
|
|
buf2[i] = hexToDec(buf_tmp[2 * i] + buf_tmp[2 * i + 1]);
|
|
}
|
|
|
|
// kr = sha3.sum512 of (buf1 + buf2) concatenate
|
|
const buffer3 = Buffer.from(buf1);
|
|
const buffer4 = Buffer.from(buf2);
|
|
const hash3 = new SHA3(512);
|
|
hash3.update(buffer3).update(buffer4);
|
|
var kr_str = hash3.digest('hex');
|
|
// convert hex string to array
|
|
var kr = new Array(32);
|
|
for (i = 0; i < 64; i++) {
|
|
kr[i] = hexToDec(kr_str[2 * i] + kr_str[2 * i + 1]);
|
|
}
|
|
var kr1 = kr.slice(0, 32);
|
|
var kr2 = kr.slice(32, 64);
|
|
|
|
// c = indcpaEncrypt(buf1, publicKey, kr[32:], paramsK)
|
|
var ciphertext = new Array(1088);
|
|
ciphertext = indcpaEncrypt(buf1, publicKey, kr2, paramsK);
|
|
|
|
// krc = sha3.Sum256(ciphertext)
|
|
const buffer5 = Buffer.from(ciphertext);
|
|
var krc = new Array(32);
|
|
const hash4 = new SHA3(256);
|
|
hash4.update(buffer5);
|
|
var krc_str = hash4.digest('hex');
|
|
// convert hex string to array
|
|
for (i = 0; i < 32; i++) {
|
|
krc[i] = hexToDec(krc_str[2 * i] + krc_str[2 * i + 1]);
|
|
}
|
|
|
|
// sha3.ShakeSum256(sharedSecret, append(kr[:paramsSymBytes], krc[:]...))
|
|
const buffer6 = Buffer.from(kr1);
|
|
const buffer7 = Buffer.from(krc);
|
|
const hash5 = new SHAKE(256);
|
|
hash5.update(buffer6).update(buffer7);
|
|
var ss_str = hash5.digest('hex');
|
|
// convert hex string to array
|
|
for (i = 0; i < 32; i++) {
|
|
sharedSecret[i] = hexToDec(ss_str[2 * i] + ss_str[2 * i + 1]);
|
|
}
|
|
|
|
var result = new Array(2);
|
|
result[0] = ciphertext;
|
|
result[1] = sharedSecret;
|
|
|
|
return result;
|
|
}
|
|
|
|
// Decrypts the ciphertext to obtain the shared secret (symmetric key)
|
|
export function Decrypt768(c, sk) {
|
|
// c is the ciphertext (1088 bytes)
|
|
// sk is the secret key (2400 bytes)
|
|
var privateKey = sk;
|
|
|
|
// make 32 byte array
|
|
var sharedSecret = new Array(32);
|
|
|
|
var indcpaPrivateKey = sk.slice(0, 3 * 384);
|
|
|
|
var pki = 3 * 384 + 3 * 384 + 32;
|
|
|
|
var publicKey = sk.slice(1152, pki);
|
|
|
|
var buf = indcpaDecrypt(c, indcpaPrivateKey, paramsK);
|
|
|
|
var ski = (1152 + ((1152 + 32) + 2 * 32)) - 2 * 32;
|
|
|
|
// kr = sha3.Sum512(append(buf, privateKey[ski:ski+paramsSymBytes]...))
|
|
const buffer1 = Buffer.from(buf);
|
|
const buffer2 = Buffer.from(privateKey.slice(ski, ski + 32));
|
|
const hash1 = new SHA3(512);
|
|
hash1.update(buffer1).update(buffer2);
|
|
var kr_str = hash1.digest('hex');
|
|
// convert hex string to array
|
|
var kr = new Array(32);
|
|
for (i = 0; i < 64; i++) {
|
|
kr[i] = hexToDec(kr_str[2 * i] + kr_str[2 * i + 1]);
|
|
}
|
|
|
|
var cmp = indcpaEncrypt(buf, publicKey, kr.slice(32, 64), paramsK);
|
|
|
|
var fail = byte(ArrayCompare(c, cmp) - 1);
|
|
|
|
// krh = sha3.Sum256(c);
|
|
const buffer3 = Buffer.from(c);
|
|
var krh = new Array(32);
|
|
const hash2 = new SHA3(256);
|
|
hash2.update(buffer3);
|
|
var krh_str = hash2.digest('hex');
|
|
// convert hex string to array
|
|
for (i = 0; i < 32; i++) {
|
|
krh[i] = hexToDec(krh_str[2 * i] + krh_str[2 * i + 1]);
|
|
}
|
|
|
|
var skx;
|
|
for (var i = 0; i < 32; i++) {
|
|
skx = privateKey.slice(0, Kyber768SKBytes - 32 + i);
|
|
kr[i] = kr[i] ^ (fail & (kr[i] ^ skx[i]));
|
|
}
|
|
|
|
// sha3.ShakeSum256(sharedSecret, append(kr[:paramsSymBytes], krh[:]...))
|
|
const buffer4 = Buffer.from(kr.slice(0, 32));
|
|
const buffer5 = Buffer.from(krh);
|
|
const hash3 = new SHAKE(256);
|
|
hash3.update(buffer4).update(buffer5);
|
|
var ss_str = hash3.digest('hex');
|
|
// convert hex string to array
|
|
for (i = 0; i < 32; i++) {
|
|
sharedSecret[i] = hexToDec(ss_str[2 * i] + ss_str[2 * i + 1]);
|
|
}
|
|
|
|
var ss = sharedSecret;
|
|
|
|
return ss;
|
|
}
|
|
|
|
// indcpaEncrypt is the encryption function of the CPA-secure
|
|
// public-key encryption scheme underlying Kyber.
|
|
function indcpaEncrypt(m, publicKey, coins, paramsK) {
|
|
|
|
var ciphertext = new Array(1088);
|
|
|
|
// initialise
|
|
var sp = polyvecNew(paramsK);
|
|
var ep = polyvecNew(paramsK);
|
|
var bp = polyvecNew(paramsK);
|
|
|
|
var result = indcpaUnpackPublicKey(publicKey, paramsK);
|
|
|
|
var publicKeyPolyvec = result[0];
|
|
var seed = result[1];
|
|
|
|
var k = polyFromMsg(m);
|
|
|
|
var at = indcpaGenMatrix(seed, true, paramsK);
|
|
|
|
for (var i = 0; i < paramsK; i++) {
|
|
sp[i] = polyGetNoise(coins, i);
|
|
ep[i] = polyGetNoise(coins, i + paramsK);
|
|
}
|
|
|
|
var epp = polyGetNoise(coins, paramsK * 2);
|
|
|
|
sp = polyvecNtt(sp, paramsK);
|
|
|
|
sp = polyvecReduce(sp, paramsK);
|
|
|
|
|
|
for (i = 0; i < paramsK; i++) {
|
|
bp[i] = polyvecPointWiseAccMontgomery(at[i], sp, paramsK);
|
|
}
|
|
|
|
var v = polyvecPointWiseAccMontgomery(publicKeyPolyvec, sp, paramsK);
|
|
|
|
bp = polyvecInvNttToMont(bp, paramsK);
|
|
|
|
v = polyInvNttToMont(v);
|
|
|
|
var bp1 = polyvecAdd(bp, ep, paramsK);
|
|
|
|
v = polyAdd(polyAdd(v, epp), k);
|
|
|
|
var bp3 = polyvecReduce(bp1, paramsK);
|
|
|
|
ciphertext = indcpaPackCiphertext(bp3, polyReduce(v), paramsK);
|
|
return ciphertext;
|
|
}
|
|
|
|
// indcpaDecrypt is the decryption function of the CPA-secure
|
|
// public-key encryption scheme underlying Kyber.
|
|
function indcpaDecrypt(c, privateKey, paramsK) {
|
|
|
|
var result = indcpaUnpackCiphertext(c, paramsK);
|
|
|
|
var bp = result[0];
|
|
var v = result[1];
|
|
|
|
var privateKeyPolyvec = indcpaUnpackPrivateKey(privateKey, paramsK);
|
|
|
|
var bp2 = polyvecNtt(bp, paramsK);
|
|
|
|
var mp = polyvecPointWiseAccMontgomery(privateKeyPolyvec, bp2, paramsK);
|
|
|
|
var mp2 = polyInvNttToMont(mp);
|
|
|
|
var mp3 = polySub(v, mp2);
|
|
|
|
var mp4 = polyReduce(mp3);
|
|
|
|
return polyToMsg(mp4);
|
|
}
|
|
|
|
// polyvecNew instantiates a new vector of polynomials.
|
|
function polyvecNew(paramsK) {
|
|
// make array containing 3 elements of type poly
|
|
var pv = new Array(paramsK);
|
|
for (var i = 0; i < paramsK; i++) {
|
|
pv[i] = new Array(384);
|
|
}
|
|
return pv;
|
|
}
|
|
|
|
// indcpaPackPublicKey serializes the public key as a concatenation of the
|
|
// serialized vector of polynomials of the public key, and the public seed
|
|
// used to generate the matrix `A`.
|
|
function indcpaPackPublicKey(publicKey, seed, paramsK) {
|
|
var array = polyvecToBytes(publicKey, paramsK);
|
|
for (var i = 0; i < seed.length; i++) {
|
|
array.push(seed[i]);
|
|
}
|
|
return array;
|
|
}
|
|
|
|
// indcpaUnpackPublicKey de-serializes the public key from a byte array
|
|
// and represents the approximate inverse of indcpaPackPublicKey.
|
|
function indcpaUnpackPublicKey(packedPublicKey, paramsK) {
|
|
var publicKeyPolyvec = polyvecFromBytes(packedPublicKey, paramsK);
|
|
var seed = packedPublicKey.slice(1152, 1184);
|
|
|
|
// return values
|
|
var result = new Array(2);
|
|
result[0] = publicKeyPolyvec;
|
|
result[1] = seed;
|
|
return result;
|
|
}
|
|
|
|
// indcpaPackPrivateKey serializes the private key.
|
|
function indcpaPackPrivateKey(privateKey, paramsK) {
|
|
return polyvecToBytes(privateKey, paramsK);
|
|
}
|
|
|
|
// indcpaUnpackPrivateKey de-serializes the private key and represents
|
|
// the inverse of indcpaPackPrivateKey.
|
|
function indcpaUnpackPrivateKey(packedPrivateKey, paramsK) {
|
|
return polyvecFromBytes(packedPrivateKey, paramsK);
|
|
}
|
|
|
|
// polyvecToBytes serializes a vector of polynomials.
|
|
function polyvecToBytes(a, paramsK) {
|
|
var r = [];
|
|
var tmp = [];
|
|
for (var i = 0; i < paramsK; i++) {
|
|
tmp = polyToBytes(a[i]);
|
|
for (var j = 0; j < tmp.length; j++) {
|
|
r.push(tmp[j]);
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// polyvecFromBytes deserializes a vector of polynomials.
|
|
function polyvecFromBytes(a, paramsK) {
|
|
var r = polyvecNew(paramsK);
|
|
var start;
|
|
var end;
|
|
for (var i = 0; i < paramsK; i++) {
|
|
start = (i * paramsPolyBytes);
|
|
end = (i + 1) * paramsPolyBytes;
|
|
r[i] = polyFromBytes(a.slice(start, end));
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// polyToBytes serializes a polynomial into an array of bytes.
|
|
function polyToBytes(a) {
|
|
var t0, t1;
|
|
var r = new Array(384);
|
|
var a2 = polyCSubQ(a); // Returns: a - q if a >= q, else a (each coefficient of the polynomial)
|
|
// for 0-127
|
|
for (var i = 0; i < paramsN / 2; i++) {
|
|
// get two coefficient entries in the polynomial
|
|
t0 = uint16(a2[2 * i]);
|
|
t1 = uint16(a2[2 * i + 1]);
|
|
|
|
// convert the 2 coefficient into 3 bytes
|
|
r[3 * i + 0] = byte(t0 >> 0); // byte() does mod 256 of the input (output value 0-255)
|
|
r[3 * i + 1] = byte(t0 >> 8) | byte(t1 << 4);
|
|
r[3 * i + 2] = byte(t1 >> 4);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// polyFromBytes de-serialises an array of bytes into a polynomial,
|
|
// and represents the inverse of polyToBytes.
|
|
function polyFromBytes(a) {
|
|
var r = new Array(384).fill(0); // each element is int16 (0-65535)
|
|
for (var i = 0; i < paramsN / 2; i++) {
|
|
r[2 * i] = int16(((uint16(a[3 * i + 0]) >> 0) | (uint16(a[3 * i + 1]) << 8)) & 0xFFF);
|
|
r[2 * i + 1] = int16(((uint16(a[3 * i + 1]) >> 4) | (uint16(a[3 * i + 2]) << 4)) & 0xFFF);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// polyToMsg converts a polynomial to a 32-byte message
|
|
// and represents the inverse of polyFromMsg.
|
|
function polyToMsg(a) {
|
|
var msg = new Array(32);
|
|
var t;
|
|
var a2 = polyCSubQ(a);
|
|
for (var i = 0; i < paramsN / 8; i++) {
|
|
msg[i] = 0;
|
|
for (var j = 0; j < 8; j++) {
|
|
t = (((uint16(a2[8 * i + j]) << 1) + uint16(paramsQ / 2)) / uint16(paramsQ)) & 1;
|
|
msg[i] |= byte(t << j);
|
|
}
|
|
}
|
|
return msg;
|
|
}
|
|
|
|
// polyFromMsg converts a 32-byte message to a polynomial.
|
|
function polyFromMsg(msg) {
|
|
var r = new Array(384).fill(0); // each element is int16 (0-65535)
|
|
var mask; // int16
|
|
for (var i = 0; i < paramsN / 8; i++) {
|
|
for (var j = 0; j < 8; j++) {
|
|
mask = -1 * int16((msg[i] >> j) & 1);
|
|
r[8 * i + j] = mask & int16((paramsQ + 1) / 2);
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// indcpaKeypair generates public and private keys for the CPA-secure
|
|
// public-key encryption scheme underlying Kyber.
|
|
function indcpaKeypair(paramsK) {
|
|
|
|
var skpv = polyvecNew(paramsK);
|
|
var pkpv = polyvecNew(paramsK);
|
|
var e = polyvecNew(paramsK);
|
|
|
|
// make a 64 byte buffer array
|
|
var buf = new Array(64);
|
|
|
|
// read 32 random values (0-255) into the 64 byte array
|
|
for (var i = 0; i < 32; i++) {
|
|
buf[i] = nextInt(256);
|
|
}
|
|
|
|
// take the first 32 bytes and hash it
|
|
var buf_tmp = buf.slice(0, 32);
|
|
const buffer1 = Buffer.from(buf_tmp);
|
|
const hash1 = new SHA3(512);
|
|
hash1.update(buffer1);
|
|
var buf_str = hash1.digest('hex');
|
|
// convert hex string to array
|
|
var buf1 = new Array(64);
|
|
for (i = 0; i < 64; i++) {
|
|
buf1[i] = hexToDec(buf_str[2 * i] + buf_str[2 * i + 1]);
|
|
}
|
|
|
|
var publicSeed = buf1.slice(0, 32);
|
|
var noiseSeed = buf1.slice(32, 64);
|
|
|
|
|
|
// generate public matrix A
|
|
var a = indcpaGenMatrix(publicSeed, false, paramsK);
|
|
|
|
// sample secret s
|
|
var nonce = 0;
|
|
for (var i = 0; i < paramsK; i++) {
|
|
skpv[i] = polyGetNoise(noiseSeed, nonce);
|
|
nonce = nonce + 1;
|
|
}
|
|
|
|
// sample noise e
|
|
for (var i = 0; i < paramsK; i++) {
|
|
e[i] = polyGetNoise(noiseSeed, nonce);
|
|
nonce = nonce + 1;
|
|
}
|
|
|
|
// perform number theoretic transform on secret s
|
|
var skpv = polyvecNtt(skpv, paramsK);
|
|
// barrett reduction
|
|
var skpv = polyvecReduce(skpv, paramsK);
|
|
|
|
// perform number theoretic transform on error/noise e
|
|
var e = polyvecNtt(e, paramsK);
|
|
|
|
// calculate A.s
|
|
for (var i = 0; i < paramsK; i++) {
|
|
pkpv[i] = polyToMont(polyvecPointWiseAccMontgomery(a[i], skpv, paramsK));
|
|
}
|
|
|
|
// calculate addition of e
|
|
pkpv = polyvecAdd(pkpv, e, paramsK);
|
|
|
|
// barrett reduction
|
|
pkpv = polyvecReduce(pkpv, paramsK);
|
|
|
|
var keys = new Array(2);
|
|
// encode the public key vector of polynomials from As+e into a byte array and append the public seed (already a byte array)
|
|
keys[0] = indcpaPackPublicKey(pkpv, publicSeed, paramsK); // public key
|
|
|
|
// encode secret vector of polynomials s
|
|
keys[1] = indcpaPackPrivateKey(skpv, paramsK); // secret key
|
|
|
|
return keys;
|
|
}
|
|
|
|
// indcpaGenMatrix 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).
|
|
function indcpaGenMatrix(seed, transposed, paramsK) {
|
|
var a = new Array(3);
|
|
var output = new Array(3 * 168);
|
|
const xof = new SHAKE(128);
|
|
var ctr = 0;
|
|
for (var i = 0; i < paramsK; i++) {
|
|
|
|
a[i] = polyvecNew(paramsK);
|
|
var transpose = new Array(2);
|
|
|
|
for (var j = 0; j < 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);
|
|
var buf_str = xof.digest({ buffer: Buffer.alloc(672), format: 'hex' });
|
|
// convert hex string to array
|
|
for (var n = 0; n < 672; n++) {
|
|
output[n] = hexToDec(buf_str[2 * n] + buf_str[2 * n + 1]);
|
|
}
|
|
|
|
// run rejection sampling on the output from above
|
|
var outputlen = 3 * 168; // 504
|
|
var result = new Array(2);
|
|
result = indcpaRejUniform(output.slice(0,504), outputlen, 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 < paramsN) { // if the polynomial hasnt been filled yet with mod q entries
|
|
|
|
var outputn = output.slice(504, 672); // take last 168 bytes of byte array from xof
|
|
|
|
var result1 = new Array(2);
|
|
result1 = indcpaRejUniform(outputn, 168, paramsN-ctr); // run sampling function again
|
|
var missing = result1[0]; // here is additional mod q polynomial coefficients
|
|
var 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 (var k = ctr; k < 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;
|
|
}
|
|
|
|
// indcpaRejUniform runs rejection sampling on uniform random bytes
|
|
// to generate uniform random integers modulo `Q`.
|
|
function indcpaRejUniform(buf, bufl, len) {
|
|
var r = new Array(384).fill(0);
|
|
var val0, val1; // d1, d2 in kyber documentation
|
|
var pos = 0; // i
|
|
var ctr = 0; // j
|
|
|
|
while (ctr < len && pos + 3 <= bufl) {
|
|
|
|
// compute d1 and d2
|
|
val0 = (uint16((buf[pos]) >> 0) | (uint16(buf[pos + 1]) << 8)) & 0xFFF;
|
|
val1 = (uint16((buf[pos + 1]) >> 4) | (uint16(buf[pos + 2]) << 4)) & 0xFFF;
|
|
|
|
// increment input buffer index by 3
|
|
pos = pos + 3;
|
|
|
|
// if d1 is less than 3329
|
|
if (val0 < paramsQ) {
|
|
// assign to d1
|
|
r[ctr] = val0;
|
|
// increment position of output array
|
|
ctr = ctr + 1;
|
|
}
|
|
if (ctr < len && val1 < paramsQ) {
|
|
r[ctr] = val1;
|
|
ctr = ctr + 1;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
var result = new Array(2);
|
|
result[0] = r; // returns polynomial NTT representation
|
|
result[1] = ctr; // ideally should return 256
|
|
return result;
|
|
}
|
|
|
|
// polyGetNoise samples a polynomial deterministically from a seed
|
|
// and nonce, with the output polynomial being close to a centered
|
|
// binomial distribution with parameter paramsETA = 2.
|
|
function polyGetNoise(seed, nonce) {
|
|
var l = paramsETA * paramsN / 4;
|
|
var p = indcpaPrf(l, seed, nonce);
|
|
return byteopsCbd(p);
|
|
}
|
|
|
|
// indcpaPrf provides a pseudo-random function (PRF) which returns
|
|
// a byte array of length `l`, using the provided key and nonce
|
|
// to instantiate the PRF's underlying hash function.
|
|
function indcpaPrf(l, key, nonce) {
|
|
var buf = new Array(l);
|
|
var nonce_arr = new Array(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);
|
|
var hash_str = hash.digest({ buffer: Buffer.alloc(l), format: 'hex' }); // 128 long byte array
|
|
// convert hex string to array
|
|
for (var n = 0; n < l; n++) {
|
|
buf[n] = hexToDec(hash_str[2 * n] + hash_str[2 * n + 1]);
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
// byteopsCbd computes a polynomial with coefficients distributed
|
|
// according to a centered binomial distribution with parameter paramsETA,
|
|
// given an array of uniformly random bytes.
|
|
function byteopsCbd(buf) {
|
|
var t, d;
|
|
var a, b;
|
|
var r = new Array(384).fill(0);
|
|
for (var i = 0; i < paramsN / 8; i++) {
|
|
t = (byteopsLoad32(buf.slice(4 * i, buf.length)) >>> 0);
|
|
d = ((t & 0x55555555) >>> 0);
|
|
d = (d + ((((t >> 1) >>> 0) & 0x55555555) >>> 0) >>> 0);
|
|
for (var j = 0; j < 8; j++) {
|
|
a = int16((((d >> (4 * j + 0)) >>> 0) & 0x3) >>> 0);
|
|
b = int16((((d >> (4 * j + paramsETA)) >>> 0) & 0x3) >>> 0);
|
|
r[8 * i + j] = a - b;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// byteopsLoad32 returns a 32-bit unsigned integer loaded from byte x.
|
|
function byteopsLoad32(x) {
|
|
var r;
|
|
r = uint32(x[0]);
|
|
r = (((r | (uint32(x[1]) << 8)) >>> 0) >>> 0);
|
|
r = (((r | (uint32(x[2]) << 16)) >>> 0) >>> 0);
|
|
r = (((r | (uint32(x[3]) << 24)) >>> 0) >>> 0);
|
|
return uint32(r);
|
|
}
|
|
|
|
// polyvecNtt applies forward number-theoretic transforms (NTT)
|
|
// to all elements of a vector of polynomials.
|
|
function polyvecNtt(r, paramsK) {
|
|
for (var i = 0; i < paramsK; i++) {
|
|
r[i] = polyNtt(r[i]);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// polyNtt computes a negacyclic number-theoretic transform (NTT) of
|
|
// a polynomial in-place; the input is assumed to be in normal order,
|
|
// while the output is in bit-reversed order.
|
|
function polyNtt(r) {
|
|
return ntt(r);
|
|
}
|
|
|
|
// ntt performs an inplace number-theoretic transform (NTT) in `Rq`.
|
|
// The input is in standard order, the output is in bit-reversed order.
|
|
function ntt(r) {
|
|
var j = 0;
|
|
var k = 1;
|
|
var zeta;
|
|
var t;
|
|
// 128, 64, 32, 16, 8, 4, 2
|
|
for (var l = 128; l >= 2; l >>= 1) {
|
|
// 0,
|
|
for (var start = 0; start < 256; start = j + l) {
|
|
zeta = nttZetas[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
|
|
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`.
|
|
function nttFqMul(a, b) {
|
|
return byteopsMontgomeryReduce(a * b);
|
|
}
|
|
|
|
// byteopsMontgomeryReduce computes a Montgomery reduction; given
|
|
// a 32-bit integer `a`, returns `a * R^-1 mod Q` where `R=2^16`.
|
|
function byteopsMontgomeryReduce(a) {
|
|
var u = int16(int32(a) * paramsQinv);
|
|
var t = u * paramsQ;
|
|
t = a - t;
|
|
t >>= 16;
|
|
return int16(t);
|
|
}
|
|
|
|
// polyvecReduce applies Barrett reduction to each coefficient of each element
|
|
// of a vector of polynomials.
|
|
function polyvecReduce(r, paramsK) {
|
|
for (var i = 0; i < paramsK; i++) {
|
|
r[i] = polyReduce(r[i]);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// polyReduce applies Barrett reduction to all coefficients of a polynomial.
|
|
function polyReduce(r) {
|
|
for (var i = 0; i < paramsN; i++) {
|
|
r[i] = byteopsBarrettReduce(r[i]);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// polyToMont performs the in-place conversion of all coefficients
|
|
// of a polynomial from the normal domain to the Montgomery domain.
|
|
function polyToMont(r) {
|
|
// var f = int16(((uint64(1) << 32) >>> 0) % uint64(paramsQ));
|
|
var f = 1353; // if paramsQ changes then this needs to be updated
|
|
for (var i = 0; i < paramsN; i++) {
|
|
r[i] = byteopsMontgomeryReduce(int32(r[i]) * int32(f));
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// byteopsBarrettReduce computes a Barrett reduction; given
|
|
// a 16-bit integer `a`, returns a 16-bit integer congruent to
|
|
// `a mod Q` in {0,...,Q}.
|
|
function byteopsBarrettReduce(a) {
|
|
var t;
|
|
var v = int16(((1 << 26) + paramsQ / 2) / paramsQ);
|
|
t = int16(int32(v) * int32(a) >> 26);
|
|
t = t * paramsQ;
|
|
return a - t;
|
|
}
|
|
|
|
// polyvecPointWiseAccMontgomery pointwise-multiplies elements of polynomial-vectors
|
|
// `a` and `b`, accumulates the results into `r`, and then multiplies by `2^-16`.
|
|
function polyvecPointWiseAccMontgomery(a, b, paramsK) {
|
|
var r = polyBaseMulMontgomery(a[0], b[0]);
|
|
var t;
|
|
for (var i = 1; i < paramsK; i++) {
|
|
t = polyBaseMulMontgomery(a[i], b[i]);
|
|
r = polyAdd(r, t);
|
|
}
|
|
return polyReduce(r);
|
|
}
|
|
|
|
// polyBaseMulMontgomery performs the multiplication of two polynomials
|
|
// in the number-theoretic transform (NTT) domain.
|
|
function polyBaseMulMontgomery(a, b) {
|
|
var rx, ry;
|
|
for (var i = 0; i < paramsN / 4; i++) {
|
|
rx = nttBaseMul(
|
|
a[4 * i + 0], a[4 * i + 1],
|
|
b[4 * i + 0], b[4 * i + 1],
|
|
nttZetas[64 + i]
|
|
);
|
|
ry = nttBaseMul(
|
|
a[4 * i + 2], a[4 * i + 3],
|
|
b[4 * i + 2], b[4 * i + 3],
|
|
-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;
|
|
}
|
|
|
|
// nttBaseMul performs the multiplication of polynomials
|
|
// in `Zq[X]/(X^2-zeta)`. Used for multiplication of elements
|
|
// in `Rq` in the number-theoretic transformation domain.
|
|
function nttBaseMul(a0, a1, b0, b1, zeta) {
|
|
var r = new Array(2);
|
|
r[0] = nttFqMul(a1, b1);
|
|
r[0] = nttFqMul(r[0], zeta);
|
|
r[0] = r[0] + nttFqMul(a0, b0);
|
|
r[1] = nttFqMul(a0, b1);
|
|
r[1] = r[1] + nttFqMul(a1, b0);
|
|
return r;
|
|
}
|
|
|
|
// polyAdd adds two polynomials.
|
|
function polyAdd(a, b) {
|
|
var c = new Array(384);
|
|
for (var i = 0; i < paramsN; i++) {
|
|
c[i] = a[i] + b[i];
|
|
}
|
|
return c;
|
|
}
|
|
|
|
// polyvecInvNttToMont applies the inverse number-theoretic transform (NTT)
|
|
// to all elements of a vector of polynomials and multiplies by Montgomery
|
|
// factor `2^16`.
|
|
function polyvecInvNttToMont(r, paramsK) {
|
|
for (var i = 0; i < paramsK; i++) {
|
|
r[i] = polyInvNttToMont(r[i]);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// polySub subtracts two polynomials.
|
|
function polySub(a, b) {
|
|
for (var i = 0; i < paramsN; i++) {
|
|
a[i] = a[i] - b[i];
|
|
}
|
|
return a;
|
|
}
|
|
|
|
// polyInvNttToMont computes the inverse of a negacyclic number-theoretic
|
|
// transform (NTT) of a polynomial in-place; the input is assumed to be in
|
|
// bit-reversed order, while the output is in normal order.
|
|
function polyInvNttToMont(r) {
|
|
return nttInv(r);
|
|
}
|
|
|
|
// nttInv 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.
|
|
function nttInv(r) {
|
|
var j = 0;
|
|
var k = 0;
|
|
var zeta;
|
|
var t;
|
|
for (var l = 2; l <= 128; l <<= 1) {
|
|
for (var start = 0; start < 256; start = j + l) {
|
|
zeta = nttZetasInv[k];
|
|
k = k + 1;
|
|
for (j = start; j < start + l; j++) {
|
|
t = r[j];
|
|
r[j] = byteopsBarrettReduce(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], nttZetasInv[127]);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// polyvecAdd adds two vectors of polynomials.
|
|
function polyvecAdd(a, b, paramsK) {
|
|
var c = new Array(3);
|
|
for (var i = 0; i < paramsK; i++) {
|
|
c[i] = polyAdd(a[i], b[i]);
|
|
}
|
|
return c;
|
|
}
|
|
|
|
// indcpaPackCiphertext serializes the ciphertext as a concatenation of
|
|
// the compressed and serialized vector of polynomials `b` and the
|
|
// compressed and serialized polynomial `v`.
|
|
function indcpaPackCiphertext(b, v, paramsK) {
|
|
var arr1 = polyvecCompress(b, paramsK);
|
|
var arr2 = polyCompress(v, paramsK);
|
|
return arr1.concat(arr2);
|
|
}
|
|
|
|
// indcpaUnpackCiphertext de-serializes and decompresses the ciphertext
|
|
// from a byte array, and represents the approximate inverse of
|
|
// indcpaPackCiphertext.
|
|
function indcpaUnpackCiphertext(c, paramsK) {
|
|
var b = polyvecDecompress(c.slice(0, 960), paramsK);
|
|
var v = polyDecompress(c.slice(960, 1088), paramsK);
|
|
var result = new Array(2);
|
|
result[0] = b;
|
|
result[1] = v;
|
|
return result;
|
|
}
|
|
|
|
// polyvecCompress lossily compresses and serializes a vector of polynomials.
|
|
function polyvecCompress(a, paramsK) {
|
|
|
|
a = polyvecCSubQ(a, paramsK);
|
|
|
|
var rr = 0;
|
|
|
|
var r = new Array(paramsPolyvecCompressedBytesK768);
|
|
|
|
var t = new Array(4);
|
|
for (var i = 0; i < paramsK; i++) {
|
|
for (var j = 0; j < paramsN / 4; j++) {
|
|
for (var k = 0; k < 4; k++) {
|
|
t[k] = uint16((((a[i][4 * j + k] << 10) + paramsQ / 2) / paramsQ) & 0x3ff);
|
|
}
|
|
r[rr + 0] = byte(t[0] >> 0);
|
|
r[rr + 1] = byte((t[0] >> 8) | (t[1] << 2));
|
|
r[rr + 2] = byte((t[1] >> 6) | (t[2] << 4));
|
|
r[rr + 3] = byte((t[2] >> 4) | (t[3] << 6));
|
|
r[rr + 4] = byte((t[3] >> 2));
|
|
rr = rr + 5;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// polyvecDecompress de-serializes and decompresses a vector of polynomials and
|
|
// represents the approximate inverse of polyvecCompress. Since compression is lossy,
|
|
// the results of decompression will may not match the original vector of polynomials.
|
|
function polyvecDecompress(a, paramsK) {
|
|
var r = polyvecNew(paramsK);
|
|
var aa = 0;
|
|
var t = new Array(4);
|
|
for (var i = 0; i < paramsK; i++) {
|
|
for (var j = 0; j < paramsN / 4; j++) {
|
|
t[0] = (uint16(a[aa + 0]) >> 0) | (uint16(a[aa + 1]) << 8);
|
|
t[1] = (uint16(a[aa + 1]) >> 2) | (uint16(a[aa + 2]) << 6);
|
|
t[2] = (uint16(a[aa + 2]) >> 4) | (uint16(a[aa + 3]) << 4);
|
|
t[3] = (uint16(a[aa + 3]) >> 6) | (uint16(a[aa + 4]) << 2);
|
|
aa = aa + 5;
|
|
for (var k = 0; k < 4; k++) {
|
|
r[i][4 * j + k] = int16((((uint32(t[k] & 0x3FF) >>> 0) * (uint32(paramsQ) >>> 0) >>> 0) + 512) >> 10 >>> 0);
|
|
}
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// polyvecCSubQ applies the conditional subtraction of `Q` to each coefficient
|
|
// of each element of a vector of polynomials.
|
|
function polyvecCSubQ(r, paramsK) {
|
|
for (var i = 0; i < paramsK; i++) {
|
|
r[i] = polyCSubQ(r[i]);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// polyCSubQ applies the conditional subtraction of `Q` to each coefficient
|
|
// of a polynomial.
|
|
function polyCSubQ(r) {
|
|
for (var i = 0; i < paramsN; i++) {
|
|
r[i] = byteopsCSubQ(r[i]);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// polyCompress lossily compresses and subsequently serializes a polynomial.
|
|
function polyCompress(a, paramsK) {
|
|
var t = new Array(8);
|
|
a = polyCSubQ(a);
|
|
var rr = 0;
|
|
var r = new Array(paramsPolyCompressedBytesK768);
|
|
for (var i = 0; i < paramsN / 8; i++) {
|
|
for (var j = 0; j < 8; j++) {
|
|
t[j] = byte(((a[8 * i + j] << 4) + paramsQ / 2) / paramsQ) & 15;
|
|
}
|
|
r[rr + 0] = t[0] | (t[1] << 4);
|
|
r[rr + 1] = t[2] | (t[3] << 4);
|
|
r[rr + 2] = t[4] | (t[5] << 4);
|
|
r[rr + 3] = t[6] | (t[7] << 4);
|
|
rr = rr + 4;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// polyDecompress de-serializes and subsequently decompresses a polynomial,
|
|
// representing the approximate inverse of polyCompress.
|
|
// Note that compression is lossy, and thus decompression will not match the
|
|
// original input.
|
|
function polyDecompress(a, paramsK) {
|
|
var r = new Array(384);
|
|
var aa = 0;
|
|
for (var i = 0; i < paramsN / 2; i++) {
|
|
r[2 * i + 0] = int16(((uint16(a[aa] & 15) * uint16(paramsQ)) + 8) >> 4);
|
|
r[2 * i + 1] = int16(((uint16(a[aa] >> 4) * uint16(paramsQ)) + 8) >> 4);
|
|
aa = aa + 1;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// byteopsCSubQ conditionally subtracts Q from a.
|
|
// if a is 3329 then convert to 0
|
|
// Returns: a - q if a >= q, else a
|
|
function byteopsCSubQ(a) {
|
|
a = a - paramsQ; // should result in a negative integer
|
|
// push left most signed bit to right most position
|
|
// remember javascript does bitwise operations in signed 32 bit
|
|
// add q back again if left most bit was 0 (positive number)
|
|
a = a + ((a >> 31) & paramsQ);
|
|
return a;
|
|
}
|
|
|
|
function byte(n) {
|
|
n = n % 256;
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
// commented out because not needed, just here for reference
|
|
function int8(n){
|
|
var end = -128;
|
|
var start = 127;
|
|
|
|
if( n >= end && n <= start){
|
|
return n;
|
|
}
|
|
if( n < end){
|
|
n = n+129;
|
|
n = n%256;
|
|
n = start + n;
|
|
return n;
|
|
}
|
|
if( n > start){
|
|
n = n-128;
|
|
n = n%256;
|
|
n = end + n;
|
|
return n;
|
|
}
|
|
}
|
|
|
|
function uint8(n){
|
|
n = n%256;
|
|
return n;
|
|
}
|
|
*/
|
|
|
|
function int16(n) {
|
|
var end = -32768;
|
|
var start = 32767;
|
|
|
|
if (n >= end && n <= start) {
|
|
return n;
|
|
}
|
|
if (n < end) {
|
|
n = n + 32769;
|
|
n = n % 65536;
|
|
n = start + n;
|
|
return n;
|
|
}
|
|
if (n > start) {
|
|
n = n - 32768;
|
|
n = n % 65536;
|
|
n = end + n;
|
|
return n;
|
|
}
|
|
}
|
|
|
|
function uint16(n) {
|
|
n = n % 65536;
|
|
return n;
|
|
}
|
|
|
|
|
|
function int32(n) {
|
|
var end = -2147483648;
|
|
var start = 2147483647;
|
|
|
|
if (n >= end && n <= start) {
|
|
return n;
|
|
}
|
|
if (n < end) {
|
|
n = n + 2147483649;
|
|
n = n % 4294967296;
|
|
n = start + n;
|
|
return n;
|
|
}
|
|
if (n > start) {
|
|
n = n - 2147483648;
|
|
n = n % 4294967296;
|
|
n = end + n;
|
|
return n;
|
|
}
|
|
}
|
|
|
|
// any bit operations to be done in uint32 must have >>> 0
|
|
// javascript calculates bitwise in SIGNED 32 bit so you need to convert
|
|
function uint32(n) {
|
|
n = n % 4294967296;
|
|
return n;
|
|
}
|
|
|
|
// compares two arrays and returns 1 if they are the same or 0 if not
|
|
function ArrayCompare(a, b) {
|
|
// check array lengths
|
|
if (a.length != b.length) {
|
|
return 0;
|
|
}
|
|
// check contents
|
|
for (var i = 0; i < a.length; i++) {
|
|
if (a[i] != b[i]) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
// test run function
|
|
function TestK768(){
|
|
|
|
// read values from PQCkemKAT_2400.rsp
|
|
// sk, ct, ss
|
|
|
|
var fs = require('fs');
|
|
var textByLine = fs.readFileSync('PQCkemKAT_2400.rsp').toString().split("\n");
|
|
|
|
// console.log(textByLine.length); // seems to be an array of strings (lines)
|
|
var sk100 = [];
|
|
var ct100 = [];
|
|
var ss100 = [];
|
|
var counter = 0;
|
|
while (counter < textByLine.length){
|
|
if (textByLine[counter][0] == 'c' && textByLine[counter][1] == 't'){
|
|
var tmp = [];
|
|
for (j = 0; j < 1088; j++) {
|
|
tmp[j] = hexToDec(textByLine[counter][2 * j + 5] + textByLine[counter][2 * j + 1 + 5]);
|
|
}
|
|
ct100.push(tmp);
|
|
counter = counter + 1;
|
|
continue;
|
|
}
|
|
else if(textByLine[counter][0] == 's' && textByLine[counter][1] == 's'){
|
|
var tmp = [];
|
|
for (j = 0; j < 32; j++) {
|
|
tmp[j] = hexToDec(textByLine[counter][2 * j + 5] + textByLine[counter][2 * j + 1 + 5]);
|
|
}
|
|
ss100.push(tmp);
|
|
counter = counter + 1;
|
|
continue;
|
|
}
|
|
else if(textByLine[counter][0] == 's' && textByLine[counter][1] == 'k'){
|
|
var tmp = [];
|
|
for (j = 0; j < 2400; j++) {
|
|
tmp[j] = hexToDec(textByLine[counter][2 * j + 5] + textByLine[counter][2 * j + 1 + 5]);
|
|
}
|
|
sk100.push(tmp);
|
|
counter = counter + 1;
|
|
continue;
|
|
}
|
|
else{
|
|
counter = counter + 1;
|
|
}
|
|
}
|
|
|
|
var failures = 0;
|
|
|
|
// for each case (100 total)
|
|
// test if ss equals Decrypt768(c,sk)
|
|
for (var i=0; i<100; i++){
|
|
var ss2 = Decrypt768(ct100[i],sk100[i]);
|
|
|
|
// success if both symmetric keys are the same
|
|
if (ArrayCompare(ss100[i], ss2)){
|
|
console.log("Test run [", i, "] success");
|
|
}
|
|
else{
|
|
console.log("Test run [", i, "] fail");
|
|
fail += 1;
|
|
}
|
|
}
|
|
|
|
if(failures==0){
|
|
console.log(" ");
|
|
console.log("All test runs successful.")
|
|
}
|
|
else{
|
|
console.log(" ");
|
|
console.log(failures, " test cases have failed.")
|
|
}
|
|
return
|
|
}
|
|
|
|
// test here
|
|
/*******************************************************
|
|
|
|
TestK768();
|
|
|
|
// To generate a public and private key pair (pk, sk)
|
|
var pk_sk = KeyGen768();
|
|
var pk = pk_sk[0];
|
|
var sk = pk_sk[1];
|
|
|
|
// To generate a random 256 bit symmetric key (ss) and its encapsulation (c)
|
|
var c_ss = Encrypt768(pk);
|
|
var c = c_ss[0];
|
|
var ss1 = c_ss[1];
|
|
|
|
// To decapsulate and obtain the same symmetric key
|
|
var ss2 = Decrypt768(c, sk);
|
|
|
|
console.log("ss1", ss1);
|
|
console.log("ss2",ss2);
|
|
|
|
// returns 1 if both symmetric keys are the same
|
|
console.log(ArrayCompare(ss1, ss2));
|
|
********************************************************/
|
|
|
|
|