From 096d3487910bf22cb548915e91ee6d13c7ea115b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 1 Feb 2017 16:59:40 -0800 Subject: [PATCH] crypto: optimized sha256 for browser. --- lib/crypto/backend-browser.js | 28 +-- lib/crypto/sha256.js | 377 ++++++++++++++++++++++++++++++++++ 2 files changed, 392 insertions(+), 13 deletions(-) create mode 100644 lib/crypto/sha256.js diff --git a/lib/crypto/backend-browser.js b/lib/crypto/backend-browser.js index f4f69212..9252a288 100644 --- a/lib/crypto/backend-browser.js +++ b/lib/crypto/backend-browser.js @@ -10,6 +10,7 @@ var assert = require('assert'); var hashjs = require('hash.js'); var util = require('../utils/util'); var aes = require('./aes'); +var sha256 = require('./sha256'); var global = util.global; var crypto = global.crypto || global.msCrypto || {}; var subtle = crypto.subtle && crypto.subtle.importKey ? crypto.subtle : {}; @@ -19,14 +20,17 @@ var backend = exports; * Hashing */ -backend._hash = function _hash(alg, data) { - var hash = hashjs[alg]; - assert(hash != null, 'Unknown algorithm.'); - return hash().update(data).digest(); -}; +backend.hash = function hash(alg, data) { + var hash; -backend.hash = function _hash(alg, data) { - return new Buffer(backend._hash(alg, data)); + if (alg === 'sha256') + return sha256.digest(data); + + hash = hashjs[alg]; + + assert(hash != null, 'Unknown algorithm.'); + + return new Buffer(hash().update(data).digest()); }; backend.ripemd160 = function ripemd160(data) { @@ -37,18 +41,16 @@ backend.sha1 = function sha1(data) { return backend.hash('sha1', data); }; -backend.sha256 = function sha256(data) { - return backend.hash('sha256', data); +backend.sha256 = function _sha256(data) { + return sha256.digest(data); }; backend.hash160 = function hash160(data) { - var hash = backend._hash('sha256', data); - return backend.hash('ripemd160', hash); + return backend.hash('ripemd160', sha256.digest(hash)); }; backend.hash256 = function hash256(data) { - var hash = backend._hash('sha256', data); - return backend.hash('sha256', hash); + return sha256.hash256(data); }; backend.hmac = function _hmac(alg, data, key) { diff --git a/lib/crypto/sha256.js b/lib/crypto/sha256.js new file mode 100644 index 00000000..91e85c3d --- /dev/null +++ b/lib/crypto/sha256.js @@ -0,0 +1,377 @@ +/*! + * sha256.js - SHA256 implementation for bcoin + * Copyright (c) 2016, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + * Parts of this software based on hash.js. + */ + +'use strict'; + +/* + * Constants + */ + +var DESC = new Buffer(8); +var BUFFER64 = new Buffer(64); +var K, PADDING; +var ctx, mctx; + +PADDING = new Buffer(64); +PADDING.fill(0); +PADDING[0] = 0x80; + +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 +]; + +/** + * SHA256 + * @constructor + * @property {Number[]} s + * @property {Number[]} w + * @property {Buffer} block + * @property {Number} bytes + */ + +function SHA256() { + if (!(this instanceof SHA256)) + return new SHA256(); + + this.s = new Array(8); + this.w = new Array(64); + this.block = new Buffer(64); + this.bytes = 0; +} + +/** + * Initialize SHA256 context. + */ + +SHA256.prototype.init = function init() { + this.s[0] = 0x6a09e667; + this.s[1] = 0xbb67ae85; + this.s[2] = 0x3c6ef372; + this.s[3] = 0xa54ff53a; + this.s[4] = 0x510e527f; + this.s[5] = 0x9b05688c; + this.s[6] = 0x1f83d9ab; + this.s[7] = 0x5be0cd19; + this.bytes = 0; +}; + +/** + * Update SHA256 context. + * @param {Buffer} data + */ + +SHA256.prototype.update = function update(data) { + return this._update(data, data.length); +}; + +/** + * Finalize SHA256 context. + * @returns {Buffer} + */ + +SHA256.prototype.finish = function finish() { + return this._finish(new Buffer(32)); +}; + +/** + * Update SHA256 context. + * @private + * @param {Buffer} data + * @param {Number} len + */ + +SHA256.prototype._update = function update(data, len) { + var size = this.bytes & 0x3f; + var pos = 0; + var i, want; + + this.bytes += len; + + if (size > 0) { + want = 64 - size; + + if (want > len) + want = len; + + for (i = 0; i < want; i++) + this.block[size + i] = data[i]; + + size += want; + len -= want; + pos += want; + + if (size < 64) + return; + + this.transform(this.block, 0); + } + + while (len >= 64) { + this.transform(data, pos); + pos += 64; + len -= 64; + } + + for (i = 0; i < len; i++) + this.block[i] = data[pos + i]; +}; + +/** + * Finalize SHA256 context. + * @private + * @param {Buffer} out + * @returns {Buffer} + */ + +SHA256.prototype._finish = function _finish(out) { + var i; + + writeU32(DESC, this.bytes >>> 29, 0); + writeU32(DESC, this.bytes << 3, 4); + + this._update(PADDING, 1 + ((119 - (this.bytes % 64)) % 64)); + this._update(DESC, 8); + + for (i = 0; i < 8; i++) { + writeU32(out, this.s[i], i * 4); + this.s[i] = 0; + } + + return out; +}; + +/** + * Transform SHA256 block. + * @param {Buffer} chunk + * @param {Number} pos + */ + +SHA256.prototype.transform = function transform(chunk, pos) { + var a = this.s[0]; + var b = this.s[1]; + var c = this.s[2]; + var d = this.s[3]; + var e = this.s[4]; + var f = this.s[5]; + var g = this.s[6]; + var h = this.s[7]; + var w = this.w; + var i, t1, t2; + + for (i = 0; i < 16; i++) + w[i] = readU32(chunk, pos + i * 4); + + for (; i < 64; i++) + w[i] = sigma1(w[i - 2]) + w[i - 7] + sigma0(w[i - 15]) + w[i - 16]; + + for (i = 0; i < 64; i++) { + t1 = h + Sigma1(e); + t1 += Ch(e, f, g); + t1 += K[i] + w[i]; + + t2 = Sigma0(a); + t2 += Maj(a, b, c); + + h = g; + g = f; + f = e; + + e = d + t1; + + d = c; + c = b; + b = a; + + a = t1 + t2; + } + + this.s[0] += a; + this.s[1] += b; + this.s[2] += c; + this.s[3] += d; + this.s[4] += e; + this.s[5] += f; + this.s[6] += g; + this.s[7] += h; + + this.s[0] >>>= 0; + this.s[1] >>>= 0; + this.s[2] >>>= 0; + this.s[3] >>>= 0; + this.s[4] >>>= 0; + this.s[5] >>>= 0; + this.s[6] >>>= 0; + this.s[7] >>>= 0; +}; + +/** + * SHA256Hmac + * @constructor + * @property {SHA256} inner + * @property {SHA256} outer + */ + +function SHA256Hmac() { + if (!(this instanceof SHA256Hmac)) + return new SHA256Hmac(); + + this.inner = new SHA256(); + this.outer = new SHA256(); +} + +/** + * Initialize HMAC context. + * @param {Buffer} data + */ + +SHA256Hmac.prototype.init = function init(data) { + var key = BUFFER64; + var i; + + if (data.length > 64) { + this.inner.init(); + this.inner.update(data); + this.inner._finish(key); + key.fill(0, 32); + } else { + data.copy(key, 0); + key.fill(0, data.length, 64); + } + + this.inner.init(); + this.outer.init(); + + for (i = 0; i < key.length; i++) + key[i] ^= 0x36; + + this.inner.update(key); + + for (i = 0; i < key.length; i++) + key[i] ^= 0x6a; + + this.outer.update(key); +}; + +/** + * Update HMAC context. + * @param {Buffer} data + */ + +SHA256Hmac.prototype.update = function update(data) { + this.inner.update(data); +}; + +/** + * Finalize HMAC context. + * @returns {Buffer} + */ + +SHA256Hmac.prototype.finish = function finish() { + this.outer.update(this.inner.finish()); + return this.outer.finish(); +}; + +/* + * Helpers + * @see https://github.com/bitcoin-core/secp256k1/blob/master/src/hash_impl.h + */ + +function Sigma0(x) { + return (x >>> 2 | x << 30) ^ (x >>> 13 | x << 19) ^ (x >>> 22 | x << 10); +} + +function Sigma1(x) { + return (x >>> 6 | x << 26) ^ (x >>> 11 | x << 21) ^ (x >>> 25 | x << 7); +} + +function sigma0(x) { + return (x >>> 7 | x << 25) ^ (x >>> 18 | x << 14) ^ (x >>> 3); +} + +function sigma1(x) { + return (x >>> 17 | x << 15) ^ (x >>> 19 | x << 13) ^ (x >>> 10); +} + +function Ch(x, y, z) { + return z ^ (x & (y ^ z)); +} + +function Maj(x, y, z) { + return (x & y) | (z & (x | y)); +} + +function writeU32(buf, value, offset) { + buf[offset] = value >>> 24; + buf[offset + 1] = (value >> 16) & 0xff; + buf[offset + 2] = (value >> 8) & 0xff; + buf[offset + 3] = value & 0xff; +} + +function readU32(buf, offset) { + return ((buf[offset] & 0xff) * 0x1000000) + + ((buf[offset + 1] & 0xff) << 16) + | ((buf[offset + 2] & 0xff) << 8) + | (buf[offset + 3] & 0xff); +} + +/* + * Context Helpers + */ + +ctx = new SHA256(); +mctx = new SHA256Hmac(); + +function sha256(data) { + ctx.init(); + ctx.update(data); + return ctx.finish(); +} + +function hash256(data) { + var out = new Buffer(32); + ctx.init(); + ctx.update(data); + ctx._finish(out); + ctx.init(); + ctx.update(out); + ctx._finish(out); + return out; +} + +function hmac(data, key) { + mctx.init(key); + mctx.update(data); + return mctx.finish(); +} + +/* + * Expose + */ + +exports = SHA256; +exports.SHA256 = SHA256; +exports.SHA256Hmac = SHA256Hmac; +exports.digest = sha256; +exports.hmac = hmac; +exports.hash256 = hash256; + +module.exports = exports;