From 29b99867c51c41913fb760c560fe18d4f0d7886c Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Wed, 12 Jul 2023 00:27:56 +0200 Subject: [PATCH] [sha256] perf update --- hash/sha256.js | 91 ++++++++++++++++++++++----------------------- hash/sha256.test.js | 21 ++++++----- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/hash/sha256.js b/hash/sha256.js index 59850c5..5984f1b 100644 --- a/hash/sha256.js +++ b/hash/sha256.js @@ -8,77 +8,80 @@ import * as binary from '../binary.js' import * as math from '../math.js' +// @todo don't init these variables globally + +/** + * See 4.2.2: Constant for sha256 & sha224 + * These words represent the first thirty-two bits of the fractional parts of + * the cube roots of the first sixty-four prime numbers. In hex, these constant words are (from left to + * right) + */ +const K = new Uint32Array([ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +]) +/** + * See 5.3.3. Initial hash value. + * + * These words were obtained by taking the first thirty-two bits of the fractional parts of the + * square roots of the first eight prime numbers. + * + * @todo shouldn't be a global variable + */ +const HINIT = new Uint32Array([ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 +]) + /** * @param {Uint8Array} data */ export const hash = data => { // Init working variables. - /** - * See 4.2.2: Constant for sha256 & sha224 - * These words represent the first thirty-two bits of the fractional parts of - * the cube roots of the first sixty-four prime numbers. In hex, these constant words are (from left to - * right) - */ - const K = new Uint32Array([ - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 - ]) - /** - * See 5.3.3. Initial hash value. - * - * These words were obtained by taking the first thirty-two bits of the fractional parts of the - * square roots of the first eight prime numbers. - * - * @todo shouldn't be a global variable - */ - const H = new Uint32Array([ - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 - ]) + const H = new Uint32Array(HINIT) // "Message schedule" - a working variable const W = new Uint32Array(64) - const M = new Uint32Array(16) // 512 bit - current "view" of the data let i = 0 let isPaddedWith1 = false for (; i + 56 <= data.length;) { // write data in big endianess let j = 0 - for (; j < M.length && i + 3 < data.length; j++) { - M[j] = data[i++] << 24 | data[i++] << 16 | data[i++] << 8 | data[i++] + for (; j < 16 && i + 3 < data.length; j++) { + W[j] = data[i++] << 24 | data[i++] << 16 | data[i++] << 8 | data[i++] } if (i % 64 !== 0) { // there is still room to write partial content and the ending bit. - M.fill(0, j) + W.fill(0, j, 16) isPaddedWith1 = true while (i < data.length) { - M[j] |= data[i] << ((3 - (i % 4)) * 8) + W[j] |= data[i] << ((3 - (i % 4)) * 8) i++ } - M[j] |= binary.BIT8 << ((3 - (i % 4)) * 8) + W[j] |= binary.BIT8 << ((3 - (i % 4)) * 8) } - updateHash(H, W, K, M) + updateHash(H, W, K) } // write rest of the data, including the padding (using msb endiannes) let j = 0 - M.fill(0) + W.fill(0, 0, 16) for (; i < data.length; j++) { for (let ci = 3; ci >= 0 && i < data.length; ci--) { - M[j] |= data[i++] << (ci * 8) + W[j] |= data[i++] << (ci * 8) } } // Write padding of the message. See 5.1.2. if (!isPaddedWith1) { - M[j - (i % 4 === 0 ? 0 : 1)] |= binary.BIT8 << ((3 - (i % 4)) * 8) + W[j - (i % 4 === 0 ? 0 : 1)] |= binary.BIT8 << ((3 - (i % 4)) * 8) } // write length of message (size in bits) as 64 bit uint // @todo test that this works correctly - M[14] = math.round(data.byteLength / binary.BIT29) - M[15] = data.byteLength * 8 - updateHash(H, W, K, M) + W[14] = math.round(data.byteLength / binary.BIT29) + W[15] = data.byteLength * 8 + updateHash(H, W, K) // correct H endianness and return a Uint8Array view const dv = new DataView(H.buffer) for (let i = 0; i < H.length; i++) { @@ -92,14 +95,8 @@ export const hash = data => { * @param {Uint32Array} H - @todo since this is manipulated, it should be lower case * @param {Uint32Array} W * @param {Uint32Array} K - * @param {Uint32Array} M */ -const updateHash = (H, W, K, M) => { - // Step 1. Prepare message schedule - for (let t = 0; t < 16; t++) { - // @todo omit M, write to W directly in above function - W[t] = M[t] - } +const updateHash = (H, W, K) => { for (let t = 16; t < 64; t++) { W[t] = sigma1to256(W[t - 2]) + W[t - 7] + sigma0to256(W[t - 15]) + W[t - 16] } diff --git a/hash/sha256.test.js b/hash/sha256.test.js index 5febe89..056446e 100644 --- a/hash/sha256.test.js +++ b/hash/sha256.test.js @@ -45,8 +45,8 @@ export const testRepeatSha256Hashing = async tc => { * @param {t.TestCase} _tc */ export const testBenchmarkSha256 = async _tc => { - const N = 10000 // 100k - const BS = 530 + const N = 100 * 1000 + const BS = 500 /** * @type {Array} */ @@ -56,9 +56,16 @@ export const testBenchmarkSha256 = async _tc => { webcrypto.getRandomValues(data) datas.push(data) } + t.measureTime(`[lib0] Time to hash ${N} random values of size ${BS}`, () => { + for (let i = 0; i < N; i++) { + const x = sha256.hash(datas[i]) + if (x === null) throw new Error() + } + }) t.measureTime(`[webcrypto sequentially] Time to hash ${N} random values of size ${BS}`, async () => { for (let i = 0; i < N; i++) { - await webcrypto.subtle.digest('SHA-256', datas[i]) + const x = await webcrypto.subtle.digest('SHA-256', datas[i]) + if (x === null) throw new Error() } }) t.measureTime(`[webcrypto concurrent] Time to hash ${N} random values of size ${BS}`, async () => { @@ -69,11 +76,7 @@ export const testBenchmarkSha256 = async _tc => { for (let i = 0; i < N; i++) { ps.push(webcrypto.subtle.digest('SHA-256', datas[i])) } - await promise.all(ps) - }) - t.measureTime(`[lib0] Time to hash ${N} random values of size ${BS}`, () => { - for (let i = 0; i < N; i++) { - sha256.hash(datas[i]) - } + const x = await promise.all(ps) + if (x === null) throw new Error() }) }