Browse Source

Support WASM backend and friendlier API

* support wasm backend

* update docs

* run on node 8

* add WASM to readme
master
Mathias Buus 7 years ago
committed by Emil Bay
parent
commit
978220e433
  1. 2
      .travis.yml
  2. 26
      README.md
  3. 11
      example.js
  4. 107
      index.js
  5. 4
      package.json
  6. 140
      test.js

2
.travis.yml

@ -3,7 +3,7 @@ notifications:
language: node_js
node_js:
- node
- 7
- 8
- 6
- 4
- 0.12

26
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

11
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'))
})

107
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
}
})

4
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"
},

140
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

Loading…
Cancel
Save