From 035aaaaca6f692c27d851a50f504e7215c0e291e Mon Sep 17 00:00:00 2001 From: Sam Hadow Date: Mon, 17 Feb 2025 22:22:57 +0100 Subject: [PATCH] keccak JS implementation --- src/controllers/chat.js | 1 + src/controllers/main.js | 3 + src/keccak.js | 172 ++++++++++++++++++++++++++++++++++++++++ src/public/ecdh.js | 0 src/routes/chat.js | 5 ++ src/routes/index.js | 2 + src/routes/root.js | 4 + src/views/index.pug | 1 + 8 files changed, 188 insertions(+) create mode 100644 src/keccak.js create mode 100644 src/public/ecdh.js diff --git a/src/controllers/chat.js b/src/controllers/chat.js index e69de29..8b13789 100644 --- a/src/controllers/chat.js +++ b/src/controllers/chat.js @@ -0,0 +1 @@ + diff --git a/src/controllers/main.js b/src/controllers/main.js index 2137e33..c9e440a 100644 --- a/src/controllers/main.js +++ b/src/controllers/main.js @@ -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')); }, diff --git a/src/keccak.js b/src/keccak.js new file mode 100644 index 0000000..2e38bbb --- /dev/null +++ b/src/keccak.js @@ -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); +} diff --git a/src/public/ecdh.js b/src/public/ecdh.js new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/chat.js b/src/routes/chat.js index e69de29..2b7a041 100644 --- a/src/routes/chat.js +++ b/src/routes/chat.js @@ -0,0 +1,5 @@ +// const express = require("express"); +// const chatController = require("../controllers/chat"); +// const router = express.Router(); +// +// module.exports = router; diff --git a/src/routes/index.js b/src/routes/index.js index 568650f..90ec39d 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -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; diff --git a/src/routes/root.js b/src/routes/root.js index 84fb1fe..08a118e 100644 --- a/src/routes/root.js +++ b/src/routes/root.js @@ -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); diff --git a/src/views/index.pug b/src/views/index.pug index 8d4fe14..b75b44f 100644 --- a/src/views/index.pug +++ b/src/views/index.pug @@ -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)