From 7f31a41e8407eab0c7f9c1bf3e36287d1357b8bb Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 12 Sep 2016 13:33:00 -0700 Subject: [PATCH] perf: start using bcoin-native. --- lib/crypto/chachapoly.js | 9 ++- lib/crypto/crypto.js | 33 ++++++++--- lib/crypto/scrypt-async.js | 9 ++- lib/crypto/scrypt.js | 9 ++- lib/crypto/siphash.js | 53 ++++++++++++++++-- lib/primitives/bloom.js | 91 ++---------------------------- lib/utils/murmur3.js | 112 +++++++++++++++++++++++++++++++++++++ lib/utils/native.js | 21 +++++++ lib/utils/utils.js | 7 +++ package.json | 6 +- test/chachapoly-test.js | 4 +- 11 files changed, 244 insertions(+), 110 deletions(-) create mode 100644 lib/utils/murmur3.js create mode 100644 lib/utils/native.js diff --git a/lib/crypto/chachapoly.js b/lib/crypto/chachapoly.js index 342fa8e1..e3b2d57d 100644 --- a/lib/crypto/chachapoly.js +++ b/lib/crypto/chachapoly.js @@ -7,6 +7,7 @@ 'use strict'; var assert = require('assert'); +var native = require('../utils/native'); var BIG_ENDIAN = new Int8Array(Int16Array.of(1).buffer)[0] === 0; @@ -176,6 +177,9 @@ ChaCha20.prototype.getCounter = function getCounter() { return lo; }; +if (native) + ChaCha20 = native.ChaCha20; + /* * Helpers */ @@ -489,6 +493,9 @@ Poly1305.verify = function verify(mac1, mac2) { return (dif & 1) !== 0; }; +if (native) + Poly1305 = native.Poly1305; + /** * AEAD (used for bip151) * @exports AEAD @@ -527,7 +534,7 @@ AEAD.prototype.init = function init(key, iv) { this.chacha20.encrypt(new Buffer(32)); // Counter should be one. - assert(this.chacha20.state[12] === 1); + assert(this.chacha20.getCounter() === 1); // Expose for debugging. this.polyKey = polyKey; diff --git a/lib/crypto/crypto.js b/lib/crypto/crypto.js index 73135770..db07d874 100644 --- a/lib/crypto/crypto.js +++ b/lib/crypto/crypto.js @@ -9,6 +9,7 @@ var assert = require('assert'); var random = require('./random'); +var native = require('../utils/native'); var nativeCrypto, supersha, hash, aes; var isBrowser = @@ -17,11 +18,6 @@ var isBrowser = if (!isBrowser) { nativeCrypto = require('crypto'); - try { - supersha = require('supersha'); - } catch (e) { - ; - } } else { hash = require('hash.js'); aes = require('./aes'); @@ -47,6 +43,9 @@ crypto.hash = function _hash(alg, data) { return nativeCrypto.createHash(alg).update(data).digest(); }; +if (native) + crypto.hash = native.hash; + /** * Hash with ripemd160. * @param {Buffer} data @@ -74,11 +73,12 @@ crypto.sha1 = function sha1(data) { */ crypto.sha256 = function sha256(data) { - if (supersha) - return supersha.sha256(data); return crypto.hash('sha256', data); }; +if (native) + crypto.sha256 = native.sha256; + /** * Hash with sha256 and ripemd160 (OP_HASH160). * @param {Buffer} data @@ -89,6 +89,9 @@ crypto.hash160 = function hash160(data) { return crypto.ripemd160(crypto.sha256(data)); }; +if (native) + crypto.hash160 = native.hash160; + /** * Hash with sha256 twice (OP_HASH256). * @param {Buffer} data @@ -96,11 +99,12 @@ crypto.hash160 = function hash160(data) { */ crypto.hash256 = function hash256(data) { - if (supersha) - return supersha.hash256(data); return crypto.sha256(crypto.sha256(data)); }; +if (native) + crypto.hash256 = native.hash256; + /** * Create a sha256 checksum (common in bitcoin). * @param {Buffer} data @@ -122,6 +126,14 @@ crypto.checksum = function checksum(data) { crypto.hmac = function hmac(alg, data, salt) { var hmac; + if (native) { + if (typeof data === 'string') + data = new Buffer(data, 'utf8'); + if (typeof salt === 'string') + salt = new Buffer(salt, 'utf8'); + return native.hmac(alg, data, salt); + } + if (!nativeCrypto) { hmac = hash.hmac(hash[alg], salt); return new Buffer(hmac.update(data).digest()); @@ -472,6 +484,9 @@ crypto.buildMerkleTree = function buildMerkleTree(leaves) { return tree; }; +if (native) + crypto.buildMerkleTree = native.buildMerkleTree; + /** * Calculate merkle root from leaves. * @param {Buffer[]} leaves diff --git a/lib/crypto/scrypt-async.js b/lib/crypto/scrypt-async.js index 73f6bef9..2eb14fd9 100644 --- a/lib/crypto/scrypt-async.js +++ b/lib/crypto/scrypt-async.js @@ -35,6 +35,8 @@ var utils = require('../utils/utils'); var crypto = require('./crypto'); +var native = require('../utils/native'); +var U32Array = typeof Uint32Array === 'function' ? Uint32Array : Array; /** * Javascript scrypt implementation. Scrypt is @@ -58,6 +60,9 @@ function scrypt(passwd, salt, N, r, p, len, callback) { if (typeof salt === 'string') salt = new Buffer(salt, 'utf8'); + if (native) + return native.scryptAsync(passwd, salt, N, r, p, len, callback); + if (r * p >= (1 << 30)) return callback(new Error('EFBIG')); @@ -86,8 +91,8 @@ function scrypt(passwd, salt, N, r, p, len, callback) { } function salsa20_8(B) { - var B32 = new Array(16); - var x = new Array(16); + var B32 = new U32Array(16); + var x = new U32Array(16); var i; for (i = 0; i < 16; i++) diff --git a/lib/crypto/scrypt.js b/lib/crypto/scrypt.js index 8bacbb85..4a8294f5 100644 --- a/lib/crypto/scrypt.js +++ b/lib/crypto/scrypt.js @@ -34,6 +34,8 @@ 'use strict'; var crypto = require('./crypto'); +var native = require('../utils/native'); +var U32Array = typeof Uint32Array === 'function' ? Uint32Array : Array; /** * Javascript scrypt implementation. Scrypt is @@ -57,6 +59,9 @@ function scrypt(passwd, salt, N, r, p, len) { if (typeof salt === 'string') salt = new Buffer(salt, 'utf8'); + if (native) + return native.scrypt(passwd, salt, N, r, p, len); + if (r * p >= (1 << 30)) throw new Error('EFBIG'); @@ -78,8 +83,8 @@ function scrypt(passwd, salt, N, r, p, len) { } function salsa20_8(B) { - var B32 = new Array(16); - var x = new Array(16); + var B32 = new U32Array(16); + var x = new U32Array(16); var i; for (i = 0; i < 16; i++) diff --git a/lib/crypto/siphash.js b/lib/crypto/siphash.js index 8ea8a592..0d06b2c8 100644 --- a/lib/crypto/siphash.js +++ b/lib/crypto/siphash.js @@ -9,6 +9,8 @@ 'use strict'; +var native = require('../utils/native'); + /** * Javascript siphash implementation. Used for compact block relay. * @param {Buffer} data @@ -16,16 +18,17 @@ * @returns {Buffer} uint64le */ -function siphash(data, key) { - var blocks = Math.ceil(data.length / 8); +function siphash24(data, key, shift) { + var blocks = Math.floor(data.length / 8); var c0 = U64(0x736f6d65, 0x70736575); var c1 = U64(0x646f7261, 0x6e646f6d); var c2 = U64(0x6c796765, 0x6e657261); var c3 = U64(0x74656462, 0x79746573); - var f0 = U64(blocks << 27, 0); + var f0 = U64(blocks << (shift - 32), 0); var f1 = U64(0, 0xff); var k0 = U64.fromRaw(key, 0); var k1 = U64.fromRaw(key, 8); + var p = 0; var i, d, v0, v1, v2, v3; // Init @@ -36,13 +39,38 @@ function siphash(data, key) { // Blocks for (i = 0; i < blocks; i++) { - d = U64.fromRaw(data, i * 8); + d = U64.fromRaw(data, p); + p += 8; v3.xor(d); sipround(v0, v1, v2, v3); sipround(v0, v1, v2, v3); v0.xor(d); } + switch (data.length & 7) { + case 7: + f0.hi |= data[p + 6] << 16; + case 6: + f0.hi |= data[p + 5] << 8; + case 5: + f0.hi |= data[p + 4] << 0; + case 4: + f0.lo |= data[p + 3] << 24; + case 3: + f0.lo |= data[p + 2] << 16; + case 2: + f0.lo |= data[p + 1] << 8; + case 1: + f0.lo |= data[p + 0] << 0; + if (f0.lo < 0) + f0.lo += 0x100000000; + if (f0.hi < 0) + f0.hi += 0x100000000; + break; + case 0: + break; + } + // Finalization v3.xor(f0); sipround(v0, v1, v2, v3); @@ -82,6 +110,19 @@ function sipround(v0, v1, v2, v3) { v2.rotl(32); } +function siphash(data, key) { + return siphash24(data, key, 56); +} + +function siphash256(data, key) { + return siphash24(data, key, 59); +} + +if (native) { + siphash = native.siphash; + siphash256 = native.siphash256; +} + /* * Helpers */ @@ -186,7 +227,9 @@ U64.fromRaw = function fromRaw(data, off) { * Expose */ -exports = siphash; +exports = siphash256; +exports.siphash = siphash; +exports.siphash256 = siphash256; exports.U64 = U64; module.exports = exports; diff --git a/lib/primitives/bloom.js b/lib/primitives/bloom.js index 9670540d..4f58a5eb 100644 --- a/lib/primitives/bloom.js +++ b/lib/primitives/bloom.js @@ -11,6 +11,9 @@ var constants = require('../protocol/constants'); var assert = require('assert'); var BufferReader = require('../utils/reader'); var BufferWriter = require('../utils/writer'); +var murmur3 = require('../utils/murmur3'); +var sum32 = murmur3.sum32; +var mul32 = murmur3.mul32; /* * Constants @@ -408,94 +411,10 @@ RollingFilter.prototype.added = function added(val, enc) { return false; }; -/** - * Murmur3 hash. - * @memberof Bloom - * @param {Buffer} data - * @param {Number} seed - * @returns {Number} +/* + * Helpers */ -function murmur3(data, seed) { - var tail = data.length - (data.length % 4); - var c1 = 0xcc9e2d51; - var c2 = 0x1b873593; - var h1 = seed; - var i, k1; - - for (i = 0; i < tail; i += 4) { - k1 = (data[i + 3] << 24) - | (data[i + 2] << 16) - | (data[i + 1] << 8) - | data[i]; - k1 = mul32(k1, c1); - k1 = rotl32(k1, 15); - k1 = mul32(k1, c2); - h1 ^= k1; - h1 = rotl32(h1, 13); - h1 = sum32(mul32(h1, 5), 0xe6546b64); - } - - k1 = 0; - switch (data.length & 3) { - case 3: - k1 ^= data[tail + 2] << 16; - case 2: - k1 ^= data[tail + 1] << 8; - case 1: - k1 ^= data[tail + 0]; - k1 = mul32(k1, c1); - k1 = rotl32(k1, 15); - k1 = mul32(k1, c2); - h1 ^= k1; - } - - h1 ^= data.length; - h1 ^= h1 >>> 16; - h1 = mul32(h1, 0x85ebca6b); - h1 ^= h1 >>> 13; - h1 = mul32(h1, 0xc2b2ae35); - h1 ^= h1 >>> 16; - - if (h1 < 0) - h1 += 0x100000000; - - return h1; -} - -function mul32(a, b) { - var alo = a & 0xffff; - var blo = b & 0xffff; - var ahi = a >>> 16; - var bhi = b >>> 16; - var r, lo, hi; - - lo = alo * blo; - hi = (ahi * blo + bhi * alo) & 0xffff; - - hi += lo >>> 16; - lo &= 0xffff; - r = (hi << 16) | lo; - - if (r < 0) - r += 0x100000000; - - return r; -} - -function sum32(a, b) { - var r = (a + b) & 0xffffffff; - - if (r < 0) - r += 0x100000000; - - return r; -} - -function rotl32(w, b) { - return (w << b) | (w >>> (32 - b)); -} - function read(data, off) { return { hi: data.readUInt32LE(off + 4, true), diff --git a/lib/utils/murmur3.js b/lib/utils/murmur3.js new file mode 100644 index 00000000..b760c7e1 --- /dev/null +++ b/lib/utils/murmur3.js @@ -0,0 +1,112 @@ +/*! + * murmur3.js - murmur3 hash for bcoin + * Copyright (c) 2014-2015, Fedor Indutny (MIT License) + * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +var native = require('./native'); + +/** + * Murmur3 hash. + * @memberof Bloom + * @param {Buffer} data + * @param {Number} seed + * @returns {Number} + */ + +function murmur3(data, seed) { + var tail = data.length - (data.length % 4); + var c1 = 0xcc9e2d51; + var c2 = 0x1b873593; + var h1 = seed; + var i, k1; + + for (i = 0; i < tail; i += 4) { + k1 = (data[i + 3] << 24) + | (data[i + 2] << 16) + | (data[i + 1] << 8) + | data[i]; + k1 = mul32(k1, c1); + k1 = rotl32(k1, 15); + k1 = mul32(k1, c2); + h1 ^= k1; + h1 = rotl32(h1, 13); + h1 = sum32(mul32(h1, 5), 0xe6546b64); + } + + k1 = 0; + switch (data.length & 3) { + case 3: + k1 ^= data[tail + 2] << 16; + case 2: + k1 ^= data[tail + 1] << 8; + case 1: + k1 ^= data[tail + 0]; + k1 = mul32(k1, c1); + k1 = rotl32(k1, 15); + k1 = mul32(k1, c2); + h1 ^= k1; + } + + h1 ^= data.length; + h1 ^= h1 >>> 16; + h1 = mul32(h1, 0x85ebca6b); + h1 ^= h1 >>> 13; + h1 = mul32(h1, 0xc2b2ae35); + h1 ^= h1 >>> 16; + + if (h1 < 0) + h1 += 0x100000000; + + return h1; +} + +if (native) + murmur3 = native.murmur3; + +function mul32(a, b) { + var alo = a & 0xffff; + var blo = b & 0xffff; + var ahi = a >>> 16; + var bhi = b >>> 16; + var r, lo, hi; + + lo = alo * blo; + hi = (ahi * blo + bhi * alo) & 0xffff; + + hi += lo >>> 16; + lo &= 0xffff; + r = (hi << 16) | lo; + + if (r < 0) + r += 0x100000000; + + return r; +} + +function sum32(a, b) { + var r = (a + b) & 0xffffffff; + + if (r < 0) + r += 0x100000000; + + return r; +} + +function rotl32(w, b) { + return (w << b) | (w >>> (32 - b)); +} + +/** + * Expose + */ + +exports = murmur3; +exports.murmur3 = murmur3; +exports.mul32 = mul32; +exports.sum32 = sum32; +exports.rotl32 = rotl32; +module.exports = exports; diff --git a/lib/utils/native.js b/lib/utils/native.js new file mode 100644 index 00000000..22e5f5fc --- /dev/null +++ b/lib/utils/native.js @@ -0,0 +1,21 @@ +/*! + * native.js - native bindings for bcoin + * Copyright (c) 2016, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +var isBrowser = + (typeof process !== 'undefined' && process.browser) + || typeof window !== 'undefined'; + +module.exports = null; + +if (!isBrowser && +process.env.BCOIN_NO_NATIVE !== 1) { + try { + module.exports = require('bcoin-native'); + } catch (e) { + ; + } +} diff --git a/lib/utils/utils.js b/lib/utils/utils.js index 2d3a1b1c..f883e00e 100644 --- a/lib/utils/utils.js +++ b/lib/utils/utils.js @@ -16,6 +16,7 @@ var utils = exports; var assert = require('assert'); +var native = require('./native'); var bn = require('bn.js'); var util = require('util'); var Number, Math, Date; @@ -165,6 +166,9 @@ utils.toBase58 = function toBase58(data) { return str; }; +if (native) + utils.toBase58 = native.toBase58; + /** * Decode a base58 string. * @see https://github.com/bitcoin/bitcoin/blob/master/src/base58.cpp @@ -217,6 +221,9 @@ utils.fromBase58 = function fromBase58(str) { return out; }; +if (native) + utils.fromBase58 = native.fromBase58; + /** * Test whether a string is base58 (note that you * may get a false positive on a hex string). diff --git a/package.json b/package.json index 34229227..af93e993 100644 --- a/package.json +++ b/package.json @@ -40,11 +40,11 @@ "elliptic": "6.3.1" }, "optionalDependencies": { + "bcoin-native": "0.0.2", "leveldown": "git://github.com/Level/leveldown.git#leveldb-1.19", "secp256k1": "3.2.0", "socket.io": "1.4.8", - "socket.io-client": "1.4.8", - "supersha": "0.0.1" + "socket.io-client": "1.4.8" }, "devDependencies": { "browserify": "13.1.0", @@ -67,7 +67,7 @@ "child_process": "./browser/empty.js", "os": "./browser/empty.js", "net": "./browser/empty.js", - "supersha": "./browser/empty.js", + "bcoin-native": "./browser/empty.js", "secp256k1": "./browser/empty.js" } } diff --git a/test/chachapoly-test.js b/test/chachapoly-test.js index e4f7b934..e2d237d5 100644 --- a/test/chachapoly-test.js +++ b/test/chachapoly-test.js @@ -50,7 +50,7 @@ describe('ChaCha20 / Poly1305 / AEAD', function() { var aead = new AEAD(); aead.init(key, nonce); - assert.equal(aead.chacha20.state[12], 1); + assert.equal(aead.chacha20.getCounter(), 1); assert.deepEqual(aead.polyKey, pk); aead.aad(aad); var plainenc = new Buffer(plain); @@ -60,7 +60,7 @@ describe('ChaCha20 / Poly1305 / AEAD', function() { var aead = new AEAD(); aead.init(key, nonce); - assert.equal(aead.chacha20.state[12], 1); + assert.equal(aead.chacha20.getCounter(), 1); assert.deepEqual(aead.polyKey, pk); aead.aad(aad); aead.decrypt(ciphertext);