Skip to content

Commit

Permalink
[sha256] perf update
Browse files Browse the repository at this point in the history
  • Loading branch information
dmonad committed Jul 11, 2023
1 parent bdb5177 commit 29b9986
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 56 deletions.
91 changes: 44 additions & 47 deletions hash/sha256.js
Expand Up @@ -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++) {
Expand All @@ -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]
}
Expand Down
21 changes: 12 additions & 9 deletions hash/sha256.test.js
Expand Up @@ -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<Uint8Array>}
*/
Expand All @@ -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 () => {
Expand All @@ -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()
})
}

0 comments on commit 29b9986

Please sign in to comment.