Skip to main content
Code Review

Return to Answer

Commonmark migration
Source Link

##Bug

Bug

##Typed arrays and DataView

Typed arrays and DataView

##Minor points

Minor points

##Rewrite

Rewrite

##Bug

##Typed arrays and DataView

##Minor points

##Rewrite

Bug

Typed arrays and DataView

Minor points

Rewrite

Source Link
Blindman67
  • 22.8k
  • 2
  • 16
  • 40

##Bug

JavaScript strings are Unicode. Your code assumes that the characters in the string are less than 256. If a character is over 256 the resulting binary encoded string will be the wrong length and the hash will fail.

##Typed arrays and DataView

There is no need to convert the numbers to a string of zeros and ones. The conversion is a massive CPU overhead, chews up RAM (16bytes for every character) and there is a lot of additional code converting to and from that string.

Use typed arrays and DataView to pack the string into 32Bit integers. The arrays, apart from Uint8ClampedArray will automatically mask the number as they are written so you do not need to do all the % (2 ** 32) operations.

The max string length in JS is \2ドル^{53}\$ the check you do is a little overkill as just to build a string of size \2ドル^{64}\$ would take many 100s of years on a top end machine.

##Minor points

  • You can pad a string with zeros using String.padStart. eg binStr = number.toString(2).padStart(32, "0");
  • Use the power operator ** rather than Math.pow. eg Math.pow(2, 32) === 2 ** 32
  • Avoid repeating the same calculation. eg Create a constant const int32Mod = 2 ** 32 to hold the modulo rather than calculate it every time

##Rewrite

I started from scratch and used typed arrays and a few other methods to speed things up. The rewrite will create a hash in about 1/15th the time and uses a lot less memory.

Rather than use the class syntax to create the tools (which is not secure) I have encapsulated the tools and hash function via closure to prevent interception of any data to be hashed.

The function using the Uint8Array will mask out the top 8 bits of Javascripts string characters. It would be simple to modify to allow for the hash to work on the full 16bits per character using Uint16Array

const sha256 = (() => {
 const stringFillArray = (str, arr, i = 0) => { while(i < str.length) { arr[i] = str.charCodeAt(i++) } }
 const H = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
 const K = [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];
 const ch = (x, y, z) => (x & y) ^ (~x & z);
 const maj = (x, y, z) => (x & y) ^ (x & z) ^ (y & z);
 const sigma0 = x => (((x >>> 2) | (x << 30)) ^ ((x >>> 13) | (x << 19)) ^ ((x >>> 22) | (x << 10)));
 const sigma1 = x => (((x >>> 6) | (x << 26)) ^ ((x >>> 11) | (x << 21)) ^ ((x >>> 25) | (x << 7)));
 const omega0 = x => (((x >>> 7) | (x << 25)) ^ ((x >>> 18) | (x << 14)) ^ (x >>> 3));
 const omega1 = x => (((x >>> 17) | (x << 15)) ^ ((x >>> 19) | (x << 13)) ^ (x >>> 10));
 const buf32 = new Array(64);
 const hTemp = new Int32Array(8);
 const totals = new Int32Array(2);
 const o1 = omega0, o2 = omega1, s1 = sigma0, s2 = sigma1, t = hTemp, b = buf32; // Aliases
 const hashIt = string => {
 var i = 0, j, result = [];
 const hashed = new Uint32Array(H);
 const packChunk = i => o2(b[i - 2]) + b[i - 7] + o1(b[i - 15]) + b[i - 16];
 const hashVals = (i = 0) => {
 while (i < 64) {
 totals[0] = t[7] + s2(t[4]) + ch(t[4], t[5], t[6]) + K[i] + b[i++];
 totals[1] = s1(t[0]) + maj(t[0], t[1], t[2]);
 t[7] = t[6];
 t[6] = t[5];
 t[5] = t[4];
 t[4] = t[3] + totals[0];
 t[3] = t[2];
 t[2] = t[1];
 t[1] = t[0];
 t[0] = totals[0] + totals[1];
 }
 };
 const sumVals = (i = 0) => { while (i < 8) { hashed[i] = t[i] + hashed[i++] } };
 const stringBuf = new ArrayBuffer(((string.length / 64 | 0) + 1) * 64);
 const stringView = new DataView(stringBuf);
 const bytes = new Uint8Array(stringBuf);
 const words = new Int32Array(stringBuf);
 stringFillArray(string, bytes);
 bytes[string.length] = 0x80;
 stringView.setUint32(bytes.length - 4, string.length * 8);
 while (i < words.length) {
 j = 0;
 while (j < 16) { buf32[j] = stringView.getInt32((i + (j++)) * 4) }
 while (j < 64) { buf32[j] = packChunk(j++) }
 hTemp.set(hashed);
 hashVals();
 sumVals();
 i += 16;
 }
 i = 0;
 while (i < 8) { result[i] = hashed[i++].toString(16).padStart(8, "0") }
 return result.join("");
 };
 return str => hashIt(str);
})();
default

AltStyle によって変換されたページ (->オリジナル) /