diff --git a/.travis.yml b/.travis.yml index 872572b..9d0760d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ notifications: language: node_js node_js: - node -- 7 +- 8 - 6 - 4 - 0.12 diff --git a/README.md b/README.md index fae416c..5a44f80 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ This module is based on @dcposch * This module requires you to pass in a `out` buffer, saving an allocation * This module allows you to set the `salt` and `personal` parameters * This module exports constants for the parameters in libsodium style +* Uses a WASM version (where it is supported) for massive performance boosts All credit goes to @dcposch for doing the hard work of porting the implementation from C to Javascript. @@ -27,17 +28,16 @@ blake2b(output, input) ## API -### `var out = blake2b(out, input, [key], [salt], [personal], [noAssert = false])` +### `var hash = blake2b(outLength, [key], [salt], [personal], [noAssert = false])` -Hash `input` and write result to `out`, optionally with `key`, `salt` and +Create a new hash instance, optionally with `key`, `salt` and `personal`. Bypass input assertions by setting `noAssert` to `true`. All parameters must be `Uint8Array`, `Buffer` or another object with a compatible API. All parameters must also fulfill the following constraints, or an `AssertionError` will be thrown (unless `noAssert = true`): -* `out` must within the byte ranges defined by the constants below. -* `input` can be any length, including `0` +* `outLength` must within the byte ranges defined by the constants below. * `key` is optional, but must within the byte ranges defined by the constants below, if given. This value must be kept secret, and can be used to create prefix-MACs. @@ -48,22 +48,18 @@ API. All parameters must also fulfill the following constraints, or an use this parameter as a kind of app id, or global versioning scheme. This value is not required to be secret. -### `var instance = blake2b.instance(outlen, [key], [salt], [personal], [noAssert = false])` +### `var hash = hash.update(input)` -Like the above method, but allows your to update the hash as you can access more -data. `noAssert` will also disable asserts in `.update` and `.final` methods. -Note that `outlen` should be a number, and that you pass the `Buffer` in the -`.final` method - -### `var instance = instance.update(input)` - -Update the hash with new `input`. Calling this method after `.final` will throw +Update the hash with new `input`. Calling this method after `.digest` will throw an error. -### `var out = instance.final(out)` +### `var out = hash.digest(out)` Finalise the the hash and write the digest to `out`. `out` must be exactly equal -to `outlen` given in the `.instance` method. +to `outLength` given in the `blake2b` method. + +Optionally you can pass `hex` to get the hash as a hex string or no arguments +to have the hash return a new Uint8Array with the hash. ### Constants diff --git a/example.js b/example.js index ebb580d..1b769ea 100644 --- a/example.js +++ b/example.js @@ -1,8 +1,11 @@ var blake2b = require('./index.js') -var output = new Uint8Array(64) -var input = Buffer.allocUnsafe(2048) +var output = new Uint8Array(32) +var input = Buffer.alloc(2048) -blake2b(output, input) +console.log('hash:', blake2b(output.length).update(input).digest('hex')) -console.log(output) +blake2b.ready(function () { + console.log('has wasm?', blake2b.WASM_LOADED) + console.log('hash again:', blake2b(output.length).update(input).digest('hex')) +}) diff --git a/index.js b/index.js index 40471a0..32226b3 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ var assert = require('assert') +var b2wasm = require('blake2b-wasm') // 64-bit unsigned addition // Sets v[a,a+1] += v[b,b+1] @@ -177,17 +178,16 @@ var parameter_block = new Uint8Array([ // Creates a BLAKE2b hashing context // Requires an output length between 1 and 64 bytes // Takes an optional Uint8Array key -function blake2bInit (outlen, key, salt, personal) { +function Blake2b (outlen, key, salt, personal) { // zero out parameter_block before usage parameter_block.fill(0) // state, 'param block' - var ctx = { - b: new Uint8Array(128), - h: new Uint32Array(16), - t: 0, // input count - c: 0, // pointer within buffer - outlen: outlen // output length in bytes - } + + this.b = new Uint8Array(128) + this.h = new Uint32Array(16) + this.t = 0 // input count + this.c = 0 // pointer within buffer + this.outlen = outlen // output length in bytes parameter_block[0] = outlen if (key) parameter_block[1] = key.length @@ -199,17 +199,33 @@ function blake2bInit (outlen, key, salt, personal) { // initialize hash state for (var i = 0; i < 16; i++) { - ctx.h[i] = BLAKE2B_IV32[i] ^ B2B_GET32(parameter_block, i * 4) + this.h[i] = BLAKE2B_IV32[i] ^ B2B_GET32(parameter_block, i * 4) } // key the hash, if applicable if (key) { - blake2bUpdate(ctx, key) + blake2bUpdate(this, key) // at the end - ctx.c = 128 + this.c = 128 } +} - return ctx +Blake2b.prototype.update = function (input) { + blake2bUpdate(this, input) + return this +} + +Blake2b.prototype.digest = function (out) { + var buf = (!out || out === 'binary' || out === 'hex') ? new Uint8Array(this.outlen) : out + blake2bFinal(this, buf) + if (out === 'hex') return hexSlice(buf) + return buf +} + +Blake2b.ready = function (cb) { + b2wasm.ready(function () { + cb() // ignore the error + }) } // Updates a BLAKE2b streaming hash @@ -241,25 +257,20 @@ function blake2bFinal (ctx, out) { return out } -module.exports = function blake2b (out, input, key, salt, personal, noAssert) { - if (noAssert !== true) { - assert(out != null) - assert(out.length >= BYTES_MIN) - assert(out.length <= BYTES_MAX) - assert(input != null) - assert(key == null ? true : key.length >= KEYBYTES_MIN) - assert(key == null ? true : key.length <= KEYBYTES_MAX) - assert(salt == null ? true : salt.length === SALTBYTES) - assert(personal == null ? true : personal.length === PERSONALBYTES) - } +function hexSlice (buf) { + var str = '' + for (var i = 0; i < buf.length; i++) str += toHex(buf[i]) + return str +} - // do the math - var ctx = blake2bInit(out.length, key, salt, personal) - blake2bUpdate(ctx, input) - return blake2bFinal(ctx, out) +function toHex (n) { + if (n < 16) return '0' + n.toString(16) + return n.toString(16) } -module.exports.instance = function instance (outlen, key, salt, personal, noAssert) { +var Proto = Blake2b + +module.exports = function createHash (outlen, key, salt, personal, noAssert) { if (noAssert !== true) { assert(outlen >= BYTES_MIN) assert(outlen <= BYTES_MAX) @@ -269,31 +280,18 @@ module.exports.instance = function instance (outlen, key, salt, personal, noAsse assert(personal == null ? true : personal.length === PERSONALBYTES) } - var ctx = blake2bInit(outlen, key, salt, personal) - var finalised = false - - return { - update: function (input) { - if (finalised === true) throw new Error('hash has been finalised') - if (noAssert !== true) { - assert(input != null) - } - - blake2bUpdate(ctx, input) - return this - }, - final: function (out) { - if (noAssert !== true) { - assert(out != null) - assert(out.length === outlen) - } - - finalised = true - return blake2bFinal(ctx, out) - } - } + return new Proto(outlen, key, salt, personal) } +module.exports.ready = function (cb) { + b2wasm.ready(function () { // ignore errors + cb() + }) +} + +module.exports.WASM_SUPPORTED = b2wasm.SUPPORTED +module.exports.WASM_LOADED = false + var BYTES_MIN = module.exports.BYTES_MIN = 16 var BYTES_MAX = module.exports.BYTES_MAX = 64 var BYTES = module.exports.BYTES = 32 @@ -302,3 +300,10 @@ var KEYBYTES_MAX = module.exports.KEYBYTES_MAX = 64 var KEYBYTES = module.exports.KEYBYTES = 32 var SALTBYTES = module.exports.SALTBYTES = 16 var PERSONALBYTES = module.exports.PERSONALBYTES = 16 + +b2wasm.ready(function (err) { + if (!err) { + module.exports.WASM_LOADED = true + Proto = b2wasm + } +}) diff --git a/package.json b/package.json index 13db11a..3ab82bd 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,9 @@ "version": "1.2.0", "description": "Blake2b (64-bit version) in pure Javascript", "main": "index.js", - "dependencies": {}, + "dependencies": { + "blake2b-wasm": "^1.0.0" + }, "devDependencies": { "tape": "^4.6.3" }, diff --git a/test.js b/test.js index fcbcb08..85e1185 100644 --- a/test.js +++ b/test.js @@ -2,95 +2,105 @@ var test = require('tape') var blake2b = require('.') var vectors = require('./test-vectors.json') -test('vectors', function (assert) { - vectors.forEach(function (v) { - var out = new Uint8Array(v.outlen) - var input = hexWrite(new Uint8Array(v.input.length / 2), v.input) - var key = v.key.length === 0 ? null : hexWrite(new Uint8Array(v.key.length / 2), v.key) - var salt = v.salt.length === 0 ? null : hexWrite(new Uint8Array(v.salt.length / 2), v.salt) - var personal = v.personal.length === 0 ? null : hexWrite(new Uint8Array(v.personal.length / 2), v.personal) - - var expected = Buffer.from(hexWrite(new Uint8Array(v.out.length / 2), v.out)) - var actual = Buffer.from(blake2b(out, input, key, salt, personal, true)) - - assert.deepEquals(actual, expected) +setup() +test('wait for ready', function (assert) { + blake2b.ready(function () { + assert.end() }) - assert.end() }) +setup() + +function setup () { + test('vectors', function (assert) { + vectors.forEach(function (v) { + var out = new Uint8Array(v.outlen) + var input = hexWrite(new Uint8Array(v.input.length / 2), v.input) + var key = v.key.length === 0 ? null : hexWrite(new Uint8Array(v.key.length / 2), v.key) + var salt = v.salt.length === 0 ? null : hexWrite(new Uint8Array(v.salt.length / 2), v.salt) + var personal = v.personal.length === 0 ? null : hexWrite(new Uint8Array(v.personal.length / 2), v.personal) + + var expected = Buffer.from(hexWrite(new Uint8Array(v.out.length / 2), v.out)) + var actual = Buffer.from(blake2b(out.length, key, salt, personal, true).update(input).digest(out)) + + assert.deepEquals(actual, expected) + }) + assert.end() + }) -test('works with buffers', function (assert) { - var vector = vectors.slice(-1)[0] + test('works with buffers', function (assert) { + var vector = vectors.slice(-1)[0] - var out = Buffer.allocUnsafe(vector.outlen) - var input = Buffer.from(vector.input, 'hex') - var key = Buffer.from(vector.key, 'hex') - var salt = Buffer.from(vector.salt, 'hex') - var personal = Buffer.from(vector.personal, 'hex') + var out = Buffer.allocUnsafe(vector.outlen) + var input = Buffer.from(vector.input, 'hex') + var key = Buffer.from(vector.key, 'hex') + var salt = Buffer.from(vector.salt, 'hex') + var personal = Buffer.from(vector.personal, 'hex') - var expected = Buffer.from(vector.out, 'hex') - var actual = blake2b(out, input, key, salt, personal) + var expected = Buffer.from(vector.out, 'hex') + var actual = blake2b(out.length, key, salt, personal).update(input).digest(out) - assert.deepEquals(actual, expected) - assert.end() -}) + assert.deepEquals(actual, expected) + assert.end() + }) -test('streaming', function (t) { - var isntance = blake2b.instance(blake2b.BYTES) - var buf = new Buffer('Hej, Verden') + test('streaming', function (t) { + var isntance = blake2b(blake2b.BYTES) + var buf = new Buffer('Hej, Verden') - for (var i = 0; i < 10; i++) isntance.update(buf) + for (var i = 0; i < 10; i++) isntance.update(buf) - var out = Buffer.alloc(blake2b.BYTES) - isntance.final(out) + var out = Buffer.alloc(blake2b.BYTES) + isntance.digest(out) - t.same(out.toString('hex'), 'cbc20f347f5dfe37dc13231cbf7eaa4ec48e585ec055a96839b213f62bd8ce00', 'streaming hash') - t.end() -}) + t.same(out.toString('hex'), 'cbc20f347f5dfe37dc13231cbf7eaa4ec48e585ec055a96839b213f62bd8ce00', 'streaming hash') + t.end() + }) -test('streaming with key', function (t) { - var key = Buffer.alloc(blake2b.KEYBYTES) - key.fill('lo') + test('streaming with key', function (t) { + var key = Buffer.alloc(blake2b.KEYBYTES) + key.fill('lo') - var instance = blake2b.instance(blake2b.BYTES, key) - var buf = new Buffer('Hej, Verden') + var instance = blake2b(blake2b.BYTES, key) + var buf = new Buffer('Hej, Verden') - for (var i = 0; i < 10; i++) instance.update(buf) + for (var i = 0; i < 10; i++) instance.update(buf) - var out = Buffer.alloc(blake2b.BYTES) - instance.final(out) + var out = Buffer.alloc(blake2b.BYTES) + instance.digest(out) - t.same(out.toString('hex'), '405f14acbeeb30396b8030f78e6a84bab0acf08cb1376aa200a500f669f675dc', 'streaming keyed hash') - t.end() -}) + t.same(out.toString('hex'), '405f14acbeeb30396b8030f78e6a84bab0acf08cb1376aa200a500f669f675dc', 'streaming keyed hash') + t.end() + }) -test('streaming with hash length', function (t) { - var isntance = blake2b.instance(blake2b.BYTES_MIN) - var buf = new Buffer('Hej, Verden') + test('streaming with hash length', function (t) { + var isntance = blake2b(blake2b.BYTES_MIN) + var buf = new Buffer('Hej, Verden') - for (var i = 0; i < 10; i++) isntance.update(buf) + for (var i = 0; i < 10; i++) isntance.update(buf) - var out = Buffer.alloc(blake2b.BYTES_MIN) - isntance.final(out) + var out = Buffer.alloc(blake2b.BYTES_MIN) + isntance.digest(out) - t.same(out.toString('hex'), 'decacdcc3c61948c79d9f8dee5b6aa99', 'streaming short hash') - t.end() -}) + t.same(out.toString('hex'), 'decacdcc3c61948c79d9f8dee5b6aa99', 'streaming short hash') + t.end() + }) -test('streaming with key and hash length', function (t) { - var key = Buffer.alloc(blake2b.KEYBYTES) - key.fill('lo') + test('streaming with key and hash length', function (t) { + var key = Buffer.alloc(blake2b.KEYBYTES) + key.fill('lo') - var instance = blake2b.instance(blake2b.BYTES_MIN, key) - var buf = new Buffer('Hej, Verden') + var instance = blake2b(blake2b.BYTES_MIN, key) + var buf = new Buffer('Hej, Verden') - for (var i = 0; i < 10; i++) instance.update(buf) + for (var i = 0; i < 10; i++) instance.update(buf) - var out = Buffer.alloc(blake2b.BYTES_MIN) - instance.final(out) + var out = Buffer.alloc(blake2b.BYTES_MIN) + instance.digest(out) - t.same(out.toString('hex'), 'fb43f0ab6872cbfd39ec4f8a1bc6fb37', 'streaming short keyed hash') - t.end() -}) + t.same(out.toString('hex'), 'fb43f0ab6872cbfd39ec4f8a1bc6fb37', 'streaming short keyed hash') + t.end() + }) +} function hexWrite (buf, string) { // must be an even number of digits