Last active
July 14, 2017 18:56
-
-
Save bellbind/1cfcc96514099fcebd455259b7249525 to your computer and use it in GitHub Desktop.
[JavaScript] Simple SHA256 implementation with TypedArray
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
"use strict"; | |
const fs = require("fs"); | |
const Hash = require("."); | |
function main() { | |
if (process.argv[2] === "-c") { | |
const names = process.argv.slice(3); | |
check(names).then(({fails, sumerror}) => { | |
if (fails > 0) { | |
console.error(`${process.argv[1]}: WARNING: ${fails | |
} computed checksum${fails > 1 ? "s" : ""} did NOT match`); | |
} | |
process.exit(fails > 0 || sumerror > 0 ? 1 : 0); | |
}).catch(err => { | |
console.error(err); | |
process.exit(1); | |
}); | |
} else { | |
const names = process.argv.slice(2); | |
generate(names).then(_ => process.exit(0)).catch(err => { | |
console.error(err); | |
process.exit(1); | |
}); | |
} | |
} | |
main(); | |
function digestFile(name) { | |
return new Promise((resolve, reject) => { | |
const h = new Hash(), bufsize = 64 * 1024; | |
const rs = fs.createReadStream(name); | |
rs.on("readable", _ => { | |
let chunk; | |
while ((chunk = rs.read(bufsize)) !== null) { | |
h.update(chunk); | |
} | |
}); | |
rs.on("end", _ => { | |
resolve(Buffer.from(h.digest())); | |
}); | |
rs.on("error", error => { | |
reject(error); | |
}); | |
}); | |
} | |
async function generate(names) { | |
for (const name of names) { | |
const digest = await digestFile(name); | |
console.log(`${digest.toString("hex")} ${name}`); | |
} | |
} | |
async function check(names) { | |
let fails = 0, sumerror = 0; | |
for (const name of names) { | |
try { | |
const r = await checkFile(name); | |
fails += r.fails; | |
} catch (err) { | |
sumerror++; | |
console.error(`${process.argv[1]}: ${name | |
}: No such file or directory`); | |
} | |
} | |
return {fails, sumerror}; | |
} | |
async function checkFile(name) { | |
const sumReg = /^\s*([\da-fA-F]{64})[\t ][ ]?(.+)$/; | |
let fails = 0; | |
for (const sum of fs.readFileSync(name, "utf8").split(/\n/)) { | |
const match = sum.match(sumReg); | |
if (!match) continue; | |
const expected = Buffer.from(match[1], "hex"); | |
try { | |
const digest = await digestFile(match[2]); | |
if (digest.compare(expected) === 0) { | |
console.log(`${match[2]}: OK`); | |
} else { | |
fails++; | |
console.log(`${match[2]}: FAILED`); | |
} | |
} catch (err) { | |
fails++; | |
console.log(`${match[2]}: FAILED open or read`); | |
} | |
} | |
return {fails}; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{"name": "sha256", "version": "1.0.0", "main": "sha256.js", "bin": "main.js"} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
// see: https://en.wikipedia.org/wiki/SHA-2#Pseudocode | |
const K = Uint32Array.from([ | |
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 H = Uint32Array.from([ | |
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, | |
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, | |
]); | |
class sha256 { | |
constructor() { | |
this.hv = H.slice(); | |
this.total = 0; | |
this.chunk = new Uint8Array(64); | |
this.filled = 0; | |
} | |
update(buffer) { | |
let {filled} = this; | |
if (filled < 0) return this; | |
const {hv, chunk} = this; | |
const buf = new Uint8Array(buffer); | |
const bytes = buf.byteLength; | |
let offs = 0; | |
for (let next = 64 - filled; next <= bytes; next += 64) { | |
chunk.set(buf.subarray(offs, next), filled); | |
update(hv, chunk); | |
filled = 0; | |
offs = next; | |
} | |
chunk.set(buf.subarray(offs), filled); | |
this.filled = filled + bytes - offs; | |
this.total += bytes; | |
return this; | |
} | |
digest() { | |
if (this.filled >= 0) { | |
last(this); | |
this.filled = -1; | |
} | |
const digest = new DataView(new ArrayBuffer(32)); | |
this.hv.forEach((v, i) => {digest.setUint32(i * 4, v, false);}); | |
return digest.buffer; | |
} | |
} | |
function rotr(v, n) { | |
// note: `>>>` result casts unint32, e.g. -1 >>> 0 is UINT32_MAX | |
return ((v >>> n) | (v << (32 - n))) >>> 0; | |
} | |
function step(i, w, a, b, c, d, e, f, g, h) { | |
if (i === 64) return [a, b, c, d, e, f, g, h]; | |
// t1 from e, f, g, h (and K[i], w[i]) | |
const s1 = (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) >>> 0; | |
const ch = ((e & f) ^ ((~e >>> 0) & g)) >>> 0; | |
const t1 = (h + s1 + ch + K[i] + w[i]) >>> 0; | |
// t2 from a, b, c | |
const s0 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) >>> 0; | |
const maj = ((a & b) ^ (a & c) ^ (b & c)) >>> 0; | |
const t2 = (s0 + maj) >>> 0; | |
// rotating vars a,b,c,d,e,f,g,h with mixed d and h | |
return step(i + 1, w, (t1 + t2) >>> 0, a, b, c, (d + t1) >>> 0, e, f, g); | |
} | |
function update(hv, chunk) { | |
const w = new Uint32Array(64); | |
const cd = new DataView(chunk.buffer, chunk.byteOffset, chunk.byteLength); | |
for (let i = 0; i < 16; i++) { | |
w[i] = cd.getUint32(i * 4, false); | |
} | |
for (let i = 16; i < 64; i++) { | |
const w16 = w[i - 16], w15 = w[i - 15], w7 = w[i - 7], w2 = w[i - 2]; | |
const s0 = (rotr(w15, 7) ^ rotr(w15, 18) ^ (w15 >>> 3)) >>> 0; | |
const s1 = (rotr(w2, 17) ^ rotr(w2, 19) ^ (w2 >>> 10)) >>> 0; | |
w[i] = (w16 + s0 + w7 + s1) >>> 0; | |
} | |
// note: tailcall recursive function is faster than loop | |
const data = step(0, w, ...hv); | |
hv.set(hv.map((v, i) => (v + data[i]) >>> 0)); | |
} | |
function last(ctx) { | |
const {hv, total, chunk, filled} = ctx; | |
chunk[filled] = 0x80; | |
let start0 = filled + 1; | |
if (start0 + 8 > 64) { | |
chunk.fill(0, start0); | |
update(hv, chunk); | |
start0 = 0; | |
} | |
chunk.fill(0, start0, -8); | |
const dv = new DataView(chunk.buffer, chunk.byteLength - 8); | |
const b30 = 1 << 29; // note: bits is bytes * 8, lowwer use 29bit | |
const low = total % b30; | |
const high = (total - low) / b30; | |
dv.setUint32(0, high, false); | |
dv.setUint32(4, (low << 3) >>> 0, false); | |
update(hv, chunk); | |
} | |
if (typeof require === "function" && require.main === module) { | |
const fs = require("fs"); | |
const digestFile = name => new Promise((resolve, reject) => { | |
//const hash = require("crypto").createHash("sha256"); | |
const hash = new sha256(); | |
const rs = fs.createReadStream(name); | |
rs.on("readable", _ => { | |
const buf = rs.read(); | |
if (buf) hash.update(buf); | |
}); | |
rs.on("end", () => {resolve(hash.digest());}); | |
rs.on("error", error => {reject(error);}); | |
}); | |
const names = process.argv.slice(process.argv[0].includes("node") ? 2 : 1); | |
(async function () { | |
for (const name of names) { | |
const hex = Buffer.from(await digestFile(name)).toString("hex"); | |
console.log(`${hex} ${name}`); | |
} | |
})().catch(console.log); | |
} else if (typeof module !== "undefined") { | |
module.exports = sha256; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
function fractop32(v) { | |
const [b0, b1, b2, b3, b4, b5, b6, b7] = | |
new Uint8Array(Float64Array.of(v).buffer); | |
const e = (((b7 & 0x7f) << 4) | (b6 >> 4)) - 1023; | |
const upper = (((b6 & 0x0f) << 28) | (b5 << 20) | (b4 << 12) | | |
(b3 << 4) | (b2 >>> 4)) >>> 0; | |
const lower = ((b2 & 0x0f) << 16) | (b1 << 8) | b0; | |
if (e < 0) return (((1 << 31) >>> (-e - 1)) | (upper >>> -e)) >>> 0; | |
return ((upper << e) | (lower >>> (20 - e))) >>> 0; | |
} | |
function primes(len) { | |
const ps = [2]; | |
for (let n = 3; ps.length < len; n += 2) { | |
if (ps.every(p => n % p !== 0)) ps.push(n); | |
} | |
return ps.slice(0, len); | |
} | |
// constants of SHA-256 as https://en.wikipedia.org/wiki/SHA-2#Pseudocode | |
const K = Uint32Array.from(primes(64), p => fractop32(Math.cbrt(p))); | |
const H = Uint32Array.from(primes(8), p => fractop32(Math.sqrt(p))); | |
console.log("K", Array.from(K, n => `0x${n.toString(16).padStart(8, "0")}`)); | |
console.log("H", Array.from(H, n => `0x${n.toString(16).padStart(8, "0")}`)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
with npx:
$ npx https://gist.github.com/bellbind/1cfcc96514099fcebd455259b7249525 /bin/bash | tee sum 295fbc2356e8605e804f95cb6d6f992335e247dbf11767fe8781e2a7f889978a /bin/bash $ npx https://gist.github.com/bellbind/1cfcc96514099fcebd455259b7249525 -c sum /bin/bash: OK