keccak JS implementation

This commit is contained in:
Sam Hadow 2025-02-17 22:22:57 +01:00
parent 1816cd3cf1
commit 035aaaaca6
8 changed files with 188 additions and 0 deletions

View File

@ -0,0 +1 @@

View File

@ -16,6 +16,9 @@ const mainController = {
ecc: (req, res) => {
res.sendFile(path.resolve(__dirname + '/../public/ecc.js'));
},
ecdh: (req, res) => {
res.sendFile(path.resolve(__dirname + '/../public/ecdh.js'));
},
popups: (req, res) => {
res.sendFile(path.resolve(__dirname + '/../public/popups.js'));
},

172
src/keccak.js Normal file
View File

@ -0,0 +1,172 @@
// referencse:
// https://github.com/XKCP/XKCP/blob/master/Standalone/CompactFIPS202/Python/CompactFIPS202.py (official python implementation)
// https://keccak.team/keccak_specs_summary.html (pseudo code and description)
function ROL64(a, n) {
const shift = BigInt(n) % 64n;
if (shift === 0n) return a;
const part1 = (a >> (64n - shift)) & ((1n << shift) - 1n);
const part2 = (a << shift) & 0xffffffffffffffffn;
return (part1 + part2) & 0xffffffffffffffffn;
}
function KeccakF1600onLanes(lanes) {
let R = 1n;
for (let round = 0; round < 24; round++) {
// θ step
const C = new Array(5);
for (let x = 0; x < 5; x++) {
C[x] = lanes[x][0] ^ lanes[x][1] ^ lanes[x][2] ^ lanes[x][3] ^ lanes[x][4];
}
const D = new Array(5);
for (let x = 0; x < 5; x++) {
D[x] = C[(x + 4) % 5] ^ ROL64(C[(x + 1) % 5], 1);
}
for (let x = 0; x < 5; x++) {
for (let y = 0; y < 5; y++) {
lanes[x][y] ^= D[x];
}
}
// ρ and π steps
let x = 1, y = 0;
let current = lanes[x][y];
for (let t = 0; t < 24; t++) {
const nextY = (2 * x + 3 * y) % 5;
[x, y] = [y, nextY];
const temp = lanes[x][y];
const rotate = (t + 1) * (t + 2) / 2;
lanes[x][y] = ROL64(current, rotate);
current = temp;
}
// χ step
for (let y = 0; y < 5; y++) {
const T = new Array(5);
for (let x = 0; x < 5; x++) {
T[x] = lanes[x][y];
}
for (let x = 0; x < 5; x++) {
lanes[x][y] = T[x] ^ ((~T[(x + 1) % 5]) & T[(x + 2) % 5]);
}
}
// ι step
for (let j = 0; j < 7; j++) {
const R_top = R >> 7n;
R = ((R << 1n) ^ (R_top * 0x71n)) & 0xffn;
if ((R & 2n) !== 0n) {
const shift = (1n << BigInt(j)) - 1n;
lanes[0][0] ^= 1n << shift;
}
}
}
return lanes;
}
function load64(state, offset) {
let result = 0n;
for (let i = 0; i < 8; i++) {
result |= BigInt(state[offset + i]) << (8n * BigInt(i));
}
return result;
}
function store64(a, state, offset) {
a = BigInt(a);
for (let i = 0; i < 8; i++) {
state[offset + i] = Number((a >> (8n * BigInt(i))) & 0xffn);
}
}
function KeccakF1600(state) {
const lanes = Array.from({ length: 5 }, () => new Array(5).fill(0n));
for (let x = 0; x < 5; x++) {
for (let y = 0; y < 5; y++) {
const offset = 8 * (x + 5 * y);
lanes[x][y] = load64(state, offset);
}
}
const processedLanes = KeccakF1600onLanes(lanes);
const newState = new Uint8Array(200);
for (let x = 0; x < 5; x++) {
for (let y = 0; y < 5; y++) {
const offset = 8 * (x + 5 * y);
store64(processedLanes[x][y], newState, offset);
}
}
return newState;
}
function Keccak(rate, capacity, inputBytes, delimitedSuffix, outputByteLen) {
if (rate + capacity !== 1600 || rate % 8 !== 0) {
throw new Error("Invalid parameters");
}
let state = new Uint8Array(200);
const rateInBytes = rate / 8;
let inputOffset = 0;
let blockSize = 0;
// Absorb phase
while (inputOffset < inputBytes.length) {
blockSize = Math.min(inputBytes.length - inputOffset, rateInBytes);
for (let i = 0; i < blockSize; i++) {
state[i] ^= inputBytes[inputOffset + i];
}
inputOffset += blockSize;
if (blockSize === rateInBytes) {
state = KeccakF1600(state);
blockSize = 0;
}
}
// Padding
state[blockSize] ^= delimitedSuffix;
if ((delimitedSuffix & 0x80) !== 0 && blockSize === rateInBytes - 1) {
state = KeccakF1600(state);
}
state[rateInBytes - 1] ^= 0x80;
state = KeccakF1600(state);
// Squeeze phase
const output = [];
let remaining = outputByteLen;
while (remaining > 0) {
const blockSize = Math.min(remaining, rateInBytes);
for (let i = 0; i < blockSize; i++) {
output.push(state[i]);
}
remaining -= blockSize;
if (remaining > 0) {
state = KeccakF1600(state);
}
}
return new Uint8Array(output);
}
// SHAKE and SHA3 functions
function SHAKE128(inputBytes, outputByteLen) {
return Keccak(1344, 256, inputBytes, 0x1f, outputByteLen);
}
function SHAKE256(inputBytes, outputByteLen) {
return Keccak(1088, 512, inputBytes, 0x1f, outputByteLen);
}
function SHA3_224(inputBytes) {
return Keccak(1152, 448, inputBytes, 0x06, 224 / 8);
}
function SHA3_256(inputBytes) {
return Keccak(1088, 512, inputBytes, 0x06, 256 / 8);
}
function SHA3_384(inputBytes) {
return Keccak(832, 768, inputBytes, 0x06, 384 / 8);
}
function SHA3_512(inputBytes) {
return Keccak(576, 1024, inputBytes, 0x06, 512 / 8);
}

0
src/public/ecdh.js Normal file
View File

View File

@ -0,0 +1,5 @@
// const express = require("express");
// const chatController = require("../controllers/chat");
// const router = express.Router();
//
// module.exports = router;

View File

@ -1,9 +1,11 @@
const express = require("express");
const rootRoutes = require('./root');
const accountRoutes = require('./account.js');
// const chatRoutes = require('./chat.js');
const router = express.Router();
router.use("/", rootRoutes);
router.use("/account", accountRoutes);
// router.use("/chat", chatRoutes);
module.exports = router;

View File

@ -18,6 +18,10 @@ router
.route("/ecc.js")
.get(mainController.ecc);
router
.route("/ecdh.js")
.get(mainController.ecdh);
router
.route("/popups.js")
.get(mainController.popups);

View File

@ -10,6 +10,7 @@ html(lang="en-US")
script(type="module", src="/ecc.js", defer)
if isLoggedIn
script(src="/chat.js", defer)
script(src="/ecdh.js", defer)
else
script(type="module", src="/popups.js", defer)
script(type="module", src="/register.js", defer)