Skip to content

Instantly share code, notes, and snippets.

@bellbind
Last active November 2, 2017 02:58
Show Gist options
  • Save bellbind/de02a751e52ab3a3d35e843221d3d37d to your computer and use it in GitHub Desktop.
Save bellbind/de02a751e52ab3a3d35e843221d3d37d to your computer and use it in GitHub Desktop.
[emscripten][WebAssembly]SHA256 implementation
"use strict";
const fs = require("fs");
const names = process.argv.slice(2);
names.forEach(name => {
const hash = require("crypto").createHash("sha256");
const rs = fs.createReadStream(name);
rs.on("readable", _ => {
const buf = rs.read();
if (buf) hash.update(buf);
});
rs.on("end", _ => {
console.log(
`${Buffer.from(hash.digest()).toString("hex")} ${name}`);
});
});
{
"name": "sha256em",
"version": "1.0.0",
"main": "sha256mod.js",
"bin": "sha256main.js",
"scripts": {
"preinstall": "echo 'NOTICE: this module required emscripten and binaryen'",
"install": "emcc -std=c11 -pedantic -O3 -funroll-loops -Wall -Wextra sha256extracted.c -o sha256.js -s WASM=1 -s EXPORTED_FUNCTIONS='[\"_sha256_init\",\"_sha256_update\",\"_sha256_digest\"]'"
}
}
#ifndef __SHA256_H__
#define __SHA256_H__
#include <stdint.h>
#define SHA256_HASH_BYTES 32
struct sha256 {
uint32_t h[8];
uint64_t bytes;
unsigned char chunk[64];
long chunk_size;
};
extern void sha256_init(struct sha256* self);
extern void sha256_update(struct sha256* self, const void* buf, size_t bytes);
extern void sha256_digest(struct sha256* self, void* digest);
#endif
#include <stdlib.h>
#include <string.h>
#include "sha256.h"
// see: https://en.wikipedia.org/wiki/SHA-2#Pseudocode
static const uint32_t 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
};
static const uint32_t H[] = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
};
static inline uint32_t big32(uint32_t v) {
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
return v;
#else
union {
struct {uint8_t b0, b1, b2, b3;};
uint32_t v;
} le = {.v = v};
union {
struct {uint8_t b3, b2, b1, b0;};
uint32_t v;
} be = {.b0 = le.b0, .b1 = le.b1, .b2 = le.b2, .b3 = le.b3};
return be.v;
#endif
}
static inline uint64_t big64(uint64_t v) {
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
return v;
#else
union {
struct {uint8_t b0, b1, b2, b3, b4, b5, b6, b7;};
uint64_t v;
} le = {.v = v};
union {
struct {uint8_t b7, b6, b5, b4, b3, b2, b1, b0;};
uint64_t v;
} be = {
.b0 = le.b0, .b1 = le.b1, .b2 = le.b2, .b3 = le.b3,
.b4 = le.b4, .b5 = le.b5, .b6 = le.b6, .b7 = le.b7,
};
return be.v;
#endif
}
static inline uint32_t rotr(uint32_t v, size_t n) {
return (v >> n) | (v << (32 - n));
}
static void update(struct sha256* self);
static void tail(struct sha256* self);
extern void sha256_init(struct sha256* self) {
memcpy(self->h, H, sizeof H);
self->bytes = 0;
self->chunk_size = 0;
}
extern void sha256_update(struct sha256* self, const void* buf, size_t bytes) {
if (self->chunk_size < 0) return;
const char* cur = buf;
unsigned char* chunk = self->chunk;
size_t chunk_size = self->chunk_size;
self->bytes += bytes;
const char* end = &cur[bytes];
for (const char* next = &cur[64 - chunk_size]; next <= end; next += 64) {
memcpy(&chunk[chunk_size], cur, next - cur);
update(self);
cur = next;
chunk_size = 0;
}
size_t rest = end - cur;
memcpy(&chunk[chunk_size], cur, rest);
self->chunk_size = chunk_size + rest;
}
extern void sha256_digest(struct sha256* self, void* digest) {
if (self->chunk_size >= 0) {
tail(self);
self->chunk_size = -1;
}
uint32_t* buf = (uint32_t*) digest;
for (int i = 0; i < 8; i++) {
buf[i] = big32(self->h[i]);
}
}
static inline uint32_t s0(uint32_t a) {
return rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
}
static inline uint32_t s1(uint32_t e) {
return rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
}
static inline uint32_t ch(uint32_t e, uint32_t f, uint32_t g) {
return (e & f) ^ (~e & g);
}
static inline uint32_t maj(uint32_t a, uint32_t b, uint32_t c) {
return (a & b) ^ (a & c) ^ (b & c);
}
static inline uint32_t
t1(uint32_t e, uint32_t f, uint32_t g, uint32_t w, uint32_t k) {
return s1(e) + ch(e, f, g) + k + w;
}
static inline uint32_t t2(uint32_t a, uint32_t b, uint32_t c) {
return s0(a) + maj(a, b, c);
}
static inline void
step(uint32_t a, uint32_t b, uint32_t c, uint32_t* d,
uint32_t e, uint32_t f, uint32_t g, uint32_t* h, uint32_t w, uint32_t k) {
*h += t1(e, f, g, w, k);
*d += *h;
*h += t2(a, b, c);
}
static void update(struct sha256* self) {
uint32_t* sh = self->h;
uint32_t* cd = (uint32_t*) self->chunk;
uint32_t w[64];
#pragma unroll
for (size_t i = 0; i < 16; i++) {
w[i] = big32(cd[i]);
}
#pragma unroll
for (size_t i = 16; i < 64; i++) {
uint32_t w16 = w[i - 16], w15 = w[i - 15], w7 = w[i - 7], w2 = w[i - 2];
uint32_t s0 = rotr(w15, 7) ^ rotr(w15, 18) ^ (w15 >> 3);
uint32_t s1 = rotr(w2, 17) ^ rotr(w2, 19) ^ (w2 >> 10);
w[i] = w16 + s0 + w7 + s1;
}
uint32_t a = sh[0], b = sh[1], c = sh[2], d = sh[3],
e = sh[4], f = sh[5], g = sh[6], h = sh[7];
/*
for (int i = 0; i < 64; i++) {
uint32_t s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
uint32_t s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
uint32_t ch = (e & f) ^ (~e & g);
uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
uint32_t t1 = h + s1 + ch + K[i] + w[i];
uint32_t t2 = s0 + maj;
h = g; g = f; f = e; e = d + t1;
d = c; c = b; b = a; a = t1 + t2;
}
*/
// extract whole loop for speed: Instead of variable rotation,
// Rotating parameters for avoiding assignment,
// In each step, updated only `d` and `h`
step(a, b, c, &d, e, f, g, &h, w[0], K[0]);
step(h, a, b, &c, d, e, f, &g, w[1], K[1]);
step(g, h, a, &b, c, d, e, &f, w[2], K[2]);
step(f, g, h, &a, b, c, d, &e, w[3], K[3]);
step(e, f, g, &h, a, b, c, &d, w[4], K[4]);
step(d, e, f, &g, h, a, b, &c, w[5], K[5]);
step(c, d, e, &f, g, h, a, &b, w[6], K[6]);
step(b, c, d, &e, f, g, h, &a, w[7], K[7]);
step(a, b, c, &d, e, f, g, &h, w[8], K[8]);
step(h, a, b, &c, d, e, f, &g, w[9], K[9]);
step(g, h, a, &b, c, d, e, &f, w[10], K[10]);
step(f, g, h, &a, b, c, d, &e, w[11], K[11]);
step(e, f, g, &h, a, b, c, &d, w[12], K[12]);
step(d, e, f, &g, h, a, b, &c, w[13], K[13]);
step(c, d, e, &f, g, h, a, &b, w[14], K[14]);
step(b, c, d, &e, f, g, h, &a, w[15], K[15]);
step(a, b, c, &d, e, f, g, &h, w[16], K[16]);
step(h, a, b, &c, d, e, f, &g, w[17], K[17]);
step(g, h, a, &b, c, d, e, &f, w[18], K[18]);
step(f, g, h, &a, b, c, d, &e, w[19], K[19]);
step(e, f, g, &h, a, b, c, &d, w[20], K[20]);
step(d, e, f, &g, h, a, b, &c, w[21], K[21]);
step(c, d, e, &f, g, h, a, &b, w[22], K[22]);
step(b, c, d, &e, f, g, h, &a, w[23], K[23]);
step(a, b, c, &d, e, f, g, &h, w[24], K[24]);
step(h, a, b, &c, d, e, f, &g, w[25], K[25]);
step(g, h, a, &b, c, d, e, &f, w[26], K[26]);
step(f, g, h, &a, b, c, d, &e, w[27], K[27]);
step(e, f, g, &h, a, b, c, &d, w[28], K[28]);
step(d, e, f, &g, h, a, b, &c, w[29], K[29]);
step(c, d, e, &f, g, h, a, &b, w[30], K[30]);
step(b, c, d, &e, f, g, h, &a, w[31], K[31]);
step(a, b, c, &d, e, f, g, &h, w[32], K[32]);
step(h, a, b, &c, d, e, f, &g, w[33], K[33]);
step(g, h, a, &b, c, d, e, &f, w[34], K[34]);
step(f, g, h, &a, b, c, d, &e, w[35], K[35]);
step(e, f, g, &h, a, b, c, &d, w[36], K[36]);
step(d, e, f, &g, h, a, b, &c, w[37], K[37]);
step(c, d, e, &f, g, h, a, &b, w[38], K[38]);
step(b, c, d, &e, f, g, h, &a, w[39], K[39]);
step(a, b, c, &d, e, f, g, &h, w[40], K[40]);
step(h, a, b, &c, d, e, f, &g, w[41], K[41]);
step(g, h, a, &b, c, d, e, &f, w[42], K[42]);
step(f, g, h, &a, b, c, d, &e, w[43], K[43]);
step(e, f, g, &h, a, b, c, &d, w[44], K[44]);
step(d, e, f, &g, h, a, b, &c, w[45], K[45]);
step(c, d, e, &f, g, h, a, &b, w[46], K[46]);
step(b, c, d, &e, f, g, h, &a, w[47], K[47]);
step(a, b, c, &d, e, f, g, &h, w[48], K[48]);
step(h, a, b, &c, d, e, f, &g, w[49], K[49]);
step(g, h, a, &b, c, d, e, &f, w[50], K[50]);
step(f, g, h, &a, b, c, d, &e, w[51], K[51]);
step(e, f, g, &h, a, b, c, &d, w[52], K[52]);
step(d, e, f, &g, h, a, b, &c, w[53], K[53]);
step(c, d, e, &f, g, h, a, &b, w[54], K[54]);
step(b, c, d, &e, f, g, h, &a, w[55], K[55]);
step(a, b, c, &d, e, f, g, &h, w[56], K[56]);
step(h, a, b, &c, d, e, f, &g, w[57], K[57]);
step(g, h, a, &b, c, d, e, &f, w[58], K[58]);
step(f, g, h, &a, b, c, d, &e, w[59], K[59]);
step(e, f, g, &h, a, b, c, &d, w[60], K[60]);
step(d, e, f, &g, h, a, b, &c, w[61], K[61]);
step(c, d, e, &f, g, h, a, &b, w[62], K[62]);
step(b, c, d, &e, f, g, h, &a, w[63], K[63]);
sh[0] += a; sh[1] += b; sh[2] += c; sh[3] += d;
sh[4] += e; sh[5] += f; sh[6] += g; sh[7] += h;
}
static void tail(struct sha256* self) {
uint64_t bits = big64(self->bytes * 8);
self->chunk[self->chunk_size] = 0x80;
memset(&self->chunk[self->chunk_size + 1], 0, 63 - self->chunk_size);
if (self->chunk_size + 1 + sizeof bits > 64) {
update(self);
memset(self->chunk, 0, 64 - sizeof bits);
}
memcpy(&self->chunk[64 - sizeof bits], &bits, sizeof bits);
update(self);
}
#!/usr/bin/env node
"use strict";
const fs = require("fs");
// $ emcc -std=c11 -pedantic -O3 -Wall -Wextra sha256.c -o sha256.js -s WASM=1
// -s EXPORTED_FUNCTIONS='["_sha256_init","_sha256_update","_sha256_digest"]'
const Hash = require("./sha256mod");
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);
});
}
}
Hash.boot(main);
function digestFile(name) {
return new Promise((resolve, reject) => {
const h = new Hash();
const 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(h.digest());
});
rs.on("error", error => {
reject(error);
h.close();
});
});
}
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};
}
"use strict";
const fs = require("fs");
// $ emcc -std=c11 -pedantic -O3 -Wall -Wextra sha256.c -o sha256.js -s WASM=1
// -s EXPORTED_FUNCTIONS='["_sha256_init","_sha256_update","_sha256_digest"]'
const cwd = process.cwd();
process.chdir(__dirname);
const sha256 = require("./sha256");
let initfuncs = [];
if (sha256.calledRun) process.chdir(cwd);
else sha256.onRuntimeInitialized = function () {
process.chdir(cwd);
initfuncs.forEach(initfunc => {
try {
initfunc();
} catch (err) {
console.error(err);
}
});
initfuncs = [];
};
class Hash {
constructor() {
// sizeof (struct sha256) = 112 (align 8)
const pctx = sha256._malloc(112);
const hashsize = 32;
const phash = sha256._malloc(hashsize);
const hash = new Uint8Array(sha256.buffer, phash, hashsize);
const bufsiz = 64 * 1024;
const pbuffer = sha256._malloc(bufsiz);
const buffer = new Uint8Array(sha256.buffer, pbuffer, bufsiz);
this.pctx = pctx;
this.buffer = buffer;
this.hash = hash;
this.closed = false;
sha256._sha256_init(pctx);
}
update(chunk) {
if (this.closed) throw Error("already closed");
if (this.buffer.byteLength < chunk.length) {
let bufsiz = this.buffer.byteLength * 2;
while (bufsiz < chunk.length) bufsiz *= 2;
sha256._free(this.buffer.byteOffset);
const pbuffer = sha256._malloc(bufsiz);
this.buffer = new Uint8Array(sha256.buffer, pbuffer, bufsiz);
}
this.buffer.set(chunk);
sha256._sha256_update(this.pctx, this.buffer.byteOffset, chunk.length);
return this;
}
digest() {
if (this.closed) throw Error("already closed");
sha256._sha256_digest(this.pctx, this.hash.byteOffset);
const r = Buffer.from(this.hash);
this.close();
return r;
}
close() {
if (this.closed) return;
sha256._free(this.pctx);
sha256._free(this.buffer.byteOffset);
sha256._free(this.hash.byteOffset);
this.closed = true;
}
static boot(initfunc) {
if (sha256.calledRun) initfunc();
else initfuncs.push(initfunc);
}
}
module.exports = Hash;
#include <stdlib.h>
#include <stdio.h>
#include "sha256.h"
// [sizeof value checker]
// $ emcc -O3 -std=c11 -pedantic -Wall -Wextra sizeof.c -o sizeof.js -s WASM=1
// $ node sizeof.js
#define show_size(type) printf("sizeof (" #type ") = %zu\n", sizeof (type))
#define show_offset(type, member) {\
type *d = NULL;\
printf("offset " #type "." #member " = %zu\n", \
(void*) &d->member - (void*) d);\
}
int main(int argc, char** argv) {
(void) argc, (void) argv;
show_size(size_t);
show_size(struct sha256);
show_offset(struct sha256, h);
show_offset(struct sha256, bytes);
show_offset(struct sha256, chunk);
show_offset(struct sha256, chunk_size);
return 0;
}
@bellbind
Copy link
Author

bellbind commented Jul 8, 2017

Execution times

$ dd if=/dev/zero bs=1m count=512 of=512M.data
512+0 records in
512+0 records out
536870912 bytes transferred in 0.322020 secs (1667197137 bytes/sec)
$ emcc -std=c11 -pedantic -O3 -Wall -Wextra sha256.c -o sha256.js -s WASM=1 
$ time node sha256main.js 512M.data 
9acca8e8c22201155389f65abbf6bc9723edc7384ead80503839f49dcc56d767 512M.data

real	0m4.854s
user	0m4.749s
sys	0m0.405s
$ time node crypto-sha256.js 512M.data 
9acca8e8c22201155389f65abbf6bc9723edc7384ead80503839f49dcc56d767 512M.data

real	0m1.777s
user	0m1.677s
sys	0m0.361s
$ time shasum -a 256 512M.data 
9acca8e8c22201155389f65abbf6bc9723edc7384ead80503839f49dcc56d767  512M.data

real	0m3.477s
user	0m3.271s
sys	0m0.165s
$ time node ../../es6/sha256/sha256.js 512M.data 
9acca8e8c22201155389f65abbf6bc9723edc7384ead80503839f49dcc56d767 512M.data

real	1m10.004s
user	1m9.583s
sys	0m0.661s
$
program time note
sha256main.js 4.854s emscripten(WASM)
crypto-sha256.js 1.777s nodejs crypto
shasum -a 256 3.477s perl script
es6/sha256.js 1m10.004s pure es6: https://gist.github.com/bellbind/1cfcc96514099fcebd455259b7249525

@bellbind
Copy link
Author

When emscripten and binaryen well set up, with npx:

$ npx https://gist.github.com/bellbind/de02a751e52ab3a3d35e843221d3d37d /bin/bash |tee sum
295fbc2356e8605e804f95cb6d6f992335e247dbf11767fe8781e2a7f889978a /bin/bash
$ npx https://gist.github.com/bellbind/de02a751e52ab3a3d35e843221d3d37d -c sum
/bin/bash: OK

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment