From 0ee5411bacfd3867fe36cb5f746cda5980a3005e Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Tue, 15 Jan 2019 17:07:02 +0000 Subject: [PATCH 1/6] feat: change api to async/await BREAKING CHANGE: callbacks are not supported!! --- LICENSE | 2 +- README.md | 27 ++++++------ benchmarks/hash.js | 17 +++++--- package.json | 12 +++--- src/blake.js | 18 +++++--- src/crypto-sha1-2-browser.js | 60 -------------------------- src/crypto-sha1-2.js | 22 ---------- src/crypto.js | 83 +++++++++++++++++++++++------------- src/index.js | 67 ++++++++--------------------- src/sha.browser.js | 28 ++++++++++++ src/sha.js | 25 +++++++++++ src/utils.js | 31 +++----------- test/index.spec.js | 64 ++++++--------------------- 13 files changed, 183 insertions(+), 273 deletions(-) delete mode 100644 src/crypto-sha1-2-browser.js delete mode 100644 src/crypto-sha1-2.js create mode 100644 src/sha.browser.js create mode 100644 src/sha.js diff --git a/LICENSE b/LICENSE index f64ffb04..3a48e037 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 Protocol Labs Inc. +Copyright (c) Protocol Labs Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index cdeb9a13..5058e707 100644 --- a/README.md +++ b/README.md @@ -88,20 +88,14 @@ You will need to use Node.js `Buffer` API compatible, if you are running inside const multihashing = require('multihashing-async') const buf = Buffer.from('beep boop') -multihashing(buf, 'sha1', (err, multihash) => { - // by default calls back with a multihash. -}) +const mh = await multihashing(buf, 'sha1') // Use `.digest(...)` if you want only the hash digest (drops the prefix indicating the hash type). -multihashing.digest(buf, 'sha1', (err , digest) => { - // digest is the raw digest -}) +const digest = await multihashing.digest(buf, 'sha1') // Use `.createHash(...)` for the raw hash functions const h = multihashing.createHash('sha1') -h(buf, (err, digest) => { - // digest is a buffer of the sha1 of buf -}) +const digest = await h(buf) ``` ## Examples @@ -109,16 +103,19 @@ h(buf, (err, digest) => { ### Multihash output ```js -> const multihashing = require('multihashing-async') -> const buf = Buffer.from('beep boop') +const multihashing = require('multihashing-async') +const buf = Buffer.from('beep boop') -> multihashing(buf, 'sha1', (err, mh) => console.log(mh)) +const mh = await multihashing(buf, 'sha1') +console.log(mh) // => -> multihashing(buf, 'sha2-256', (err, mh) => console.log(mh)) +const mh = await multihashing(buf, 'sha2-256') +console.log(mh) // => -> multihashing(buf, 'sha2-512', (err, mh) => console.log(mh)) +const mh = await multihashing(buf, 'sha2-512') +console.log(mh) // => ``` @@ -137,4 +134,4 @@ Small note: If editing the README, please conform to the [standard-readme](https ## License -[MIT](LICENSE) © 2016 Protocol Labs Inc. +[MIT](LICENSE) © Protocol Labs Inc. diff --git a/benchmarks/hash.js b/benchmarks/hash.js index dfc31bfc..860216df 100644 --- a/benchmarks/hash.js +++ b/benchmarks/hash.js @@ -22,19 +22,22 @@ const algs = [ 'keccak-384', 'keccak-512', 'murmur3-32', - 'murmur3-128' + 'murmur3-128', + 'dbl-sha2-256', + 'blake2b-256', + 'blake2b-512', + 'blake2s-256' ] algs.forEach((alg) => { suite.add(alg, function (d) { const buf = Buffer.alloc(10 * 1024) buf.fill(Math.ceil(Math.random() * 100)) - - multihashing(buf, alg, (err, res) => { - if (err) throw err - list.push(res) - d.resolve() - }) + multihashing(buf, alg) + .then(res => { + list.push(res) + d.resolve() + }) }, { defer: true }) diff --git a/package.json b/package.json index 3e9c4cda..4d9a8d94 100644 --- a/package.json +++ b/package.json @@ -3,19 +3,22 @@ "version": "0.6.0", "description": "multiple hash functions", "keywords": [ - "multihash" + "multihash", + "hash", + "hashing", + "async" ], "homepage": "https://github.com/multiformats/js-multihashing-async", "bugs": "https://github.com/multiformats/js-multihashing-async/issues", "license": "MIT", - "leadMaintainer": "Hugo Dias ", + "leadMaintainer": "Hugo Dias ", "files": [ "src", "dist" ], "main": "src/index.js", "browser": { - "./src/crypto-sha1-2.js": "./src/crypto-sha1-2-browser.js" + "./src/sha.js": "./src/sha.browser.js" }, "repository": "github:multiformats/js-multihashing-async", "scripts": { @@ -34,8 +37,7 @@ "blakejs": "^1.1.0", "js-sha3": "~0.8.0", "multihashes": "~0.4.13", - "murmurhash3js": "^3.0.1", - "nodeify": "^1.0.1" + "murmurhash3js-revisited": "^3.0.0" }, "devDependencies": { "aegir": "^18.0.3", diff --git a/src/blake.js b/src/blake.js index 2ca0136e..d64fb705 100644 --- a/src/blake.js +++ b/src/blake.js @@ -2,8 +2,6 @@ const blake = require('blakejs') -const toCallback = require('./utils').toCallback - const minB = 0xb201 const minS = 0xb241 @@ -19,11 +17,17 @@ const blake2s = { digest: blake.blake2sFinal } -const makeB2Hash = (size, hf) => toCallback((buf) => { - const ctx = hf.init(size, null) - hf.update(ctx, buf) - return Buffer.from(hf.digest(ctx)) -}) +const makeB2Hash = (size, hf) => (data) => { + return new Promise((resolve, reject) => { + try { + const ctx = hf.init(size, null) + hf.update(ctx, data) + resolve(Buffer.from(hf.digest(ctx))) + } catch (error) { + reject(error) + } + }) +} module.exports = (table) => { for (let i = 0; i < 64; i++) { diff --git a/src/crypto-sha1-2-browser.js b/src/crypto-sha1-2-browser.js deleted file mode 100644 index 83e339da..00000000 --- a/src/crypto-sha1-2-browser.js +++ /dev/null @@ -1,60 +0,0 @@ -/* global self */ - -'use strict' - -const nodeify = require('nodeify') - -const webCrypto = getWebCrypto() - -function getWebCrypto () { - if (self.crypto) { - return self.crypto.subtle || self.crypto.webkitSubtle - } - - if (self.msCrypto) { - return self.msCrypto.subtle - } -} - -function webCryptoHash (type) { - if (!webCrypto) { - throw new Error('Please use a browser with webcrypto support and ensure the code has been delivered securely via HTTPS/TLS and run within a Secure Context') - } - - return (data, callback) => { - const res = webCrypto.digest({ name: type }, data) - - if (typeof res.then !== 'function') { // IE11 - res.onerror = () => { - callback(new Error(`hashing data using ${type}`)) - } - res.oncomplete = (e) => { - callback(null, e.target.result) - } - return - } - - nodeify( - res.then((raw) => Buffer.from(new Uint8Array(raw))), - callback - ) - } -} - -function sha1 (buf, callback) { - webCryptoHash('SHA-1')(buf, callback) -} - -function sha2256 (buf, callback) { - webCryptoHash('SHA-256')(buf, callback) -} - -function sha2512 (buf, callback) { - webCryptoHash('SHA-512')(buf, callback) -} - -module.exports = { - sha1: sha1, - sha2256: sha2256, - sha2512: sha2512 -} diff --git a/src/crypto-sha1-2.js b/src/crypto-sha1-2.js deleted file mode 100644 index 89bb24b7..00000000 --- a/src/crypto-sha1-2.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict' - -const crypto = require('crypto') -const toCallback = require('./utils').toCallback - -const sha1 = toCallback( - (buf) => crypto.createHash('sha1').update(buf).digest() -) - -const sha2256 = toCallback( - (buf) => crypto.createHash('sha256').update(buf).digest() -) - -const sha2512 = toCallback( - (buf) => crypto.createHash('sha512').update(buf).digest() -) - -module.exports = { - sha1: sha1, - sha2256: sha2256, - sha2512: sha2512 -} diff --git a/src/crypto.js b/src/crypto.js index a18d54e3..b6cb9628 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -1,41 +1,64 @@ 'use strict' const sha3 = require('js-sha3') -const murmur3 = require('murmurhash3js') +const mur = require('murmurhash3js-revisited') +const sha = require('./sha') +const { fromNumberTo32BitBuf } = require('./utils') -const utils = require('./utils') -const sha = require('./crypto-sha1-2') +const hash = (algorithm) => (data) => { + return new Promise((resolve, reject) => { + try { + switch (algorithm) { + case 'sha3-224': + return resolve(Buffer.from(sha3.sha3_224.arrayBuffer(data))) + case 'sha3-256': + return resolve(Buffer.from(sha3.sha3_256.arrayBuffer(data))) + case 'sha3-384': + return resolve(Buffer.from(sha3.sha3_384.arrayBuffer(data))) + case 'sha3-512': + return resolve(Buffer.from(sha3.sha3_512.arrayBuffer(data))) + case 'shake-128': + return resolve(Buffer.from(sha3.shake128.create(128).update(data).arrayBuffer())) + case 'shake-256': + return resolve(Buffer.from(sha3.shake256.create(256).update(data).arrayBuffer())) + case 'keccak-224': + return resolve(Buffer.from(sha3.keccak224.arrayBuffer(data))) + case 'keccak-256': + return resolve(Buffer.from(sha3.keccak256.arrayBuffer(data))) + case 'keccak-384': + return resolve(Buffer.from(sha3.keccak384.arrayBuffer(data))) + case 'keccak-512': + return resolve(Buffer.from(sha3.keccak512.arrayBuffer(data))) + case 'murmur3-128': + return resolve(Buffer.from(mur.x64.hash128(data), 'hex')) + case 'murmur3-32': + return resolve(fromNumberTo32BitBuf(mur.x86.hash32(data))) -const toCallback = utils.toCallback -const toBuf = utils.toBuf -const fromString = utils.fromString -const fromNumberTo32BitBuf = utils.fromNumberTo32BitBuf - -const dblSha2256 = (buf, cb) => { - sha.sha2256(buf, (err, firstHash) => { - if (err) { - cb(err) + default: + throw new TypeError(`${algorithm} is not a supported algorithm`) + } + } catch (error) { + return reject(error) } - sha.sha2256((Buffer.from(firstHash)), cb) }) } module.exports = { - sha1: sha.sha1, - sha2256: sha.sha2256, - sha2512: sha.sha2512, - sha3512: toCallback(toBuf(sha3.sha3_512)), - sha3384: toCallback(toBuf(sha3.sha3_384)), - sha3256: toCallback(toBuf(sha3.sha3_256)), - sha3224: toCallback(toBuf(sha3.sha3_224)), - shake128: toCallback(toBuf(sha3.shake_128, 128)), - shake256: toCallback(toBuf(sha3.shake_256, 256)), - keccak224: toCallback(toBuf(sha3.keccak_224)), - keccak256: toCallback(toBuf(sha3.keccak_256)), - keccak384: toCallback(toBuf(sha3.keccak_384)), - keccak512: toCallback(toBuf(sha3.keccak_512)), - murmur3128: toCallback(toBuf(fromString(murmur3.x64.hash128))), - murmur332: toCallback(fromNumberTo32BitBuf(fromString(murmur3.x86.hash32))), - addBlake: require('./blake'), - dblSha2256: dblSha2256 + sha1: sha('sha1'), + sha2256: sha('sha2-256'), + sha2512: sha('sha2-512'), + dblSha2256: sha('dbl-sha2-256'), + sha3224: hash('sha3-224'), + sha3256: hash('sha3-256'), + sha3384: hash('sha3-384'), + sha3512: hash('sha3-512'), + shake128: hash('shake-128'), + shake256: hash('shake-256'), + keccak224: hash('keccak-224'), + keccak256: hash('keccak-256'), + keccak384: hash('keccak-384'), + keccak512: hash('keccak-512'), + murmur3128: hash('murmur3-128'), + murmur332: hash('murmur3-32'), + addBlake: require('./blake') } diff --git a/src/index.js b/src/index.js index 01052f92..00c0716a 100644 --- a/src/index.js +++ b/src/index.js @@ -3,35 +3,17 @@ const multihash = require('multihashes') const crypto = require('./crypto') -module.exports = Multihashing - /** * Hash the given `buf` using the algorithm specified * by `func`. - * * @param {Buffer} buf - The value to hash. * @param {number|string} func - The algorithm to use. * @param {number} [length] - Optionally trim the result to this length. - * @param {function(Error, Buffer)} callback - * @returns {undefined} + * @returns {Promise} */ -function Multihashing (buf, func, length, callback) { - if (typeof length === 'function') { - callback = length - length = undefined - } - - if (!callback) { - throw new Error('Missing callback') - } - - Multihashing.digest(buf, func, length, (err, digest) => { - if (err) { - return callback(err) - } - - callback(null, multihash.encode(digest, func, length)) - }) +function Multihashing (buf, func, length) { + return Multihashing.digest(buf, func, length) + .then(digest => multihash.encode(digest, func, length)) } /** @@ -50,41 +32,25 @@ Multihashing.multihash = multihash * @param {Buffer} buf - The value to hash. * @param {number|string} func - The algorithm to use. * @param {number} [length] - Optionally trim the result to this length. - * @param {function(Error, Buffer)} callback - * @returns {undefined} + * @returns {Promise} */ -Multihashing.digest = function (buf, func, length, callback) { - if (typeof length === 'function') { - callback = length - length = undefined - } - - if (!callback) { - throw new Error('Missing callback') - } - - let cb = callback - if (length) { - cb = (err, digest) => { - if (err) { - return callback(err) - } - - callback(null, digest.slice(0, length)) - } - } - - let hash +Multihashing.digest = (buf, func, length) => { try { - hash = Multihashing.createHash(func) + return Multihashing.createHash(func)(buf) + .then(digest => { + if (length) { + return digest.slice(0, length) + } + return digest + }) } catch (err) { - return cb(err) + return Promise.reject(err) } - - hash(buf, cb) } /** + * Creates a function that hashs with the provided algorithm + * * @param {string|number} func * * @returns {function} - The to `func` corresponding hash function. @@ -147,3 +113,4 @@ Multihashing.validate = (data, hash, callback) => { callback(err, Buffer.compare(hash, newHash) === 0) }) } +module.exports = Multihashing diff --git a/src/sha.browser.js b/src/sha.browser.js new file mode 100644 index 00000000..ad39bb65 --- /dev/null +++ b/src/sha.browser.js @@ -0,0 +1,28 @@ +'use strict' +const crypto = self.crypto || self.msCrypto + +module.exports = (algorithm) => { + if (typeof self === 'undefined' || (!self.crypto && !self.msCrypto)) { + throw new Error( + 'Please use a browser with webcrypto support and ensure the code has been delivered securely via HTTPS/TLS and run within a Secure Context' + ) + } + + return (data) => { + switch (algorithm) { + case 'sha1': + return crypto.subtle.digest({ name: 'SHA-1' }, data).then(Buffer.from) + case 'sha2-256': + return crypto.subtle.digest({ name: 'SHA-256' }, data).then(Buffer.from) + case 'sha2-512': + return crypto.subtle.digest({ name: 'SHA-512' }, data).then(Buffer.from) + case 'dbl-sha2-256': { + return crypto.subtle.digest({ name: 'SHA-256' }, data) + .then(d => crypto.subtle.digest({ name: 'SHA-256' }, d)) + .then(Buffer.from) + } + default: + throw new TypeError(`${algorithm} is not a supported algorithm`) + } + } +} diff --git a/src/sha.js b/src/sha.js new file mode 100644 index 00000000..3516fad9 --- /dev/null +++ b/src/sha.js @@ -0,0 +1,25 @@ +'use strict' +const crypto = require('crypto') + +module.exports = (algorithm) => (data) => { + return new Promise((resolve, reject) => { + try { + switch (algorithm) { + case 'sha1': + return resolve(crypto.createHash('sha1').update(data).digest()) + case 'sha2-256': + return resolve(crypto.createHash('sha256').update(data).digest()) + case 'sha2-512': + return resolve(crypto.createHash('sha512').update(data).digest()) + case 'dbl-sha2-256': { + const first = crypto.createHash('sha256').update(data).digest() + return resolve(crypto.createHash('sha256').update(first).digest()) + } + default: + throw new TypeError(`${algorithm} is not a supported algorithm`) + } + } catch (error) { + return reject(error) + } + }) +} diff --git a/src/utils.js b/src/utils.js index 668501f7..b82b6b4b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,31 +1,6 @@ 'use strict' -exports.toCallback = (doWork) => { - return function (input, callback) { - let res - try { - res = doWork(input) - } catch (err) { - process.nextTick(callback, err) - return - } - - process.nextTick(callback, null, res) - } -} - -exports.toBuf = (doWork, other) => (input) => { - let result = doWork(input, other) - return Buffer.from(result, 'hex') -} - -exports.fromString = (doWork, other) => (_input) => { - const input = Buffer.isBuffer(_input) ? _input.toString() : _input - return doWork(input, other) -} - -exports.fromNumberTo32BitBuf = (doWork, other) => (input) => { - let number = doWork(input, other) +const fromNumberTo32BitBuf = (number) => { const bytes = new Array(4) for (let i = 0; i < 4; i++) { @@ -35,3 +10,7 @@ exports.fromNumberTo32BitBuf = (doWork, other) => (input) => { return Buffer.from(bytes) } + +module.exports = { + fromNumberTo32BitBuf +} diff --git a/test/index.spec.js b/test/index.spec.js index 73411c6b..1b083e62 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -15,65 +15,29 @@ describe('multihashing', () => { const func = fixture[1] const encoded = fixture[2] - it(`encodes in ${func}`, (done) => { - multihashing(Buffer.from(raw), func, (err, digest) => { - if (err) { - return done(err) - } - - expect( - digest.toString('hex') - ).to.eql(encoded) - done() - }) + it(`encodes in ${func}`, async function () { + const digest = await multihashing(Buffer.from(raw), func) + expect(digest.toString('hex')).to.eql(encoded) }) }) - it('cuts the length', (done) => { + it('cuts the length', async () => { const buf = Buffer.from('beep boop') - multihashing(buf, 'sha2-256', 10, (err, digest) => { - if (err) { - return done(err) - } - - expect(digest) - .to.eql(Buffer.from('120a90ea688e275d58056732', 'hex')) - - done() - }) + const digest = await multihashing(buf, 'sha2-256', 10) + expect(digest) + .to.eql(Buffer.from('120a90ea688e275d58056732', 'hex')) }) - it('digest only, without length', (done) => { + it('digest only, without length', async () => { const buf = Buffer.from('beep boop') - multihashing.digest(buf, 'sha2-256', (err, digest) => { - if (err) { - return done(err) - } - - expect( - digest - ).to.eql( - Buffer.from('90ea688e275d580567325032492b597bc77221c62493e76330b85ddda191ef7c', 'hex') - ) - - done() - }) - }) - - describe('invalid arguments', () => { - it('throws on missing callback', () => { - expect( - () => multihashing(Buffer.from('beep'), 'sha3') - ).to.throw(/Missing callback/) - }) - - it('digest only, throws on missing callback', () => { - expect( - () => multihashing.digest(Buffer.from('beep'), 'sha3') - ).to.throw(/Missing callback/) - }) + const digest = await multihashing.digest(buf, 'sha2-256') + expect( + digest + ).to.eql( + Buffer.from('90ea688e275d580567325032492b597bc77221c62493e76330b85ddda191ef7c', 'hex') + ) }) }) From 65c7cd3fc270dc477ea20970c4e95e7ab7ed21c6 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Tue, 15 Jan 2019 17:15:30 +0000 Subject: [PATCH 2/6] fix: add promise catch in benchmarks --- benchmarks/hash.js | 1 + 1 file changed, 1 insertion(+) diff --git a/benchmarks/hash.js b/benchmarks/hash.js index 860216df..b0ad8f92 100644 --- a/benchmarks/hash.js +++ b/benchmarks/hash.js @@ -38,6 +38,7 @@ algs.forEach((alg) => { list.push(res) d.resolve() }) + .catch(err => console.log(err)) }, { defer: true }) From 4d31df2b1c931b97eb99c1bba12c27e78f55ed5f Mon Sep 17 00:00:00 2001 From: dirkmc Date: Thu, 14 Mar 2019 06:53:13 -0400 Subject: [PATCH 3/6] fix: async/await improvements Address comments on [parent PR](https://github.com/multiformats/js-multihashing-async/pull/37) --- README.md | 4 +-- package.json | 4 ++- src/blake.js | 17 +++++------- src/crypto.js | 67 ++++++++++++++++++++++------------------------ src/index.js | 52 +++++++++++++++++------------------ src/sha.browser.js | 13 +++++---- src/sha.js | 37 ++++++++++++------------- test/index.spec.js | 46 +++++++++++++++++++++++++++++-- 8 files changed, 135 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index 5058e707..9df12006 100644 --- a/README.md +++ b/README.md @@ -94,8 +94,8 @@ const mh = await multihashing(buf, 'sha1') const digest = await multihashing.digest(buf, 'sha1') // Use `.createHash(...)` for the raw hash functions -const h = multihashing.createHash('sha1') -const digest = await h(buf) +const hash = multihashing.createHash('sha1') +const digest = await hash(buf) ``` ## Examples diff --git a/package.json b/package.json index 4d9a8d94..5ad2a7a3 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,11 @@ }, "dependencies": { "blakejs": "^1.1.0", + "err-code": "^1.1.2", "js-sha3": "~0.8.0", "multihashes": "~0.4.13", - "murmurhash3js-revisited": "^3.0.0" + "murmurhash3js-revisited": "^3.0.0", + "sinon": "^7.2.7" }, "devDependencies": { "aegir": "^18.0.3", diff --git a/src/blake.js b/src/blake.js index d64fb705..ef55f25b 100644 --- a/src/blake.js +++ b/src/blake.js @@ -17,16 +17,13 @@ const blake2s = { digest: blake.blake2sFinal } -const makeB2Hash = (size, hf) => (data) => { - return new Promise((resolve, reject) => { - try { - const ctx = hf.init(size, null) - hf.update(ctx, data) - resolve(Buffer.from(hf.digest(ctx))) - } catch (error) { - reject(error) - } - }) +// Note that although this function doesn't do any asynchronous work, we mark +// the function as async because it must return a Promise to match the API +// for other functions that do perform asynchronous work (see sha.browser.js) +const makeB2Hash = (size, hf) => async (data) => { + const ctx = hf.init(size, null) + hf.update(ctx, data) + return Buffer.from(hf.digest(ctx)) } module.exports = (table) => { diff --git a/src/crypto.js b/src/crypto.js index b6cb9628..894a9b49 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -5,42 +5,39 @@ const mur = require('murmurhash3js-revisited') const sha = require('./sha') const { fromNumberTo32BitBuf } = require('./utils') -const hash = (algorithm) => (data) => { - return new Promise((resolve, reject) => { - try { - switch (algorithm) { - case 'sha3-224': - return resolve(Buffer.from(sha3.sha3_224.arrayBuffer(data))) - case 'sha3-256': - return resolve(Buffer.from(sha3.sha3_256.arrayBuffer(data))) - case 'sha3-384': - return resolve(Buffer.from(sha3.sha3_384.arrayBuffer(data))) - case 'sha3-512': - return resolve(Buffer.from(sha3.sha3_512.arrayBuffer(data))) - case 'shake-128': - return resolve(Buffer.from(sha3.shake128.create(128).update(data).arrayBuffer())) - case 'shake-256': - return resolve(Buffer.from(sha3.shake256.create(256).update(data).arrayBuffer())) - case 'keccak-224': - return resolve(Buffer.from(sha3.keccak224.arrayBuffer(data))) - case 'keccak-256': - return resolve(Buffer.from(sha3.keccak256.arrayBuffer(data))) - case 'keccak-384': - return resolve(Buffer.from(sha3.keccak384.arrayBuffer(data))) - case 'keccak-512': - return resolve(Buffer.from(sha3.keccak512.arrayBuffer(data))) - case 'murmur3-128': - return resolve(Buffer.from(mur.x64.hash128(data), 'hex')) - case 'murmur3-32': - return resolve(fromNumberTo32BitBuf(mur.x86.hash32(data))) +// Note that although this function doesn't do any asynchronous work, we mark +// the function as async because it must return a Promise to match the API +// for other functions that do perform asynchronous work (see sha.browser.js) +const hash = (algorithm) => async (data) => { + switch (algorithm) { + case 'sha3-224': + return Buffer.from(sha3.sha3_224.arrayBuffer(data)) + case 'sha3-256': + return Buffer.from(sha3.sha3_256.arrayBuffer(data)) + case 'sha3-384': + return Buffer.from(sha3.sha3_384.arrayBuffer(data)) + case 'sha3-512': + return Buffer.from(sha3.sha3_512.arrayBuffer(data)) + case 'shake-128': + return Buffer.from(sha3.shake128.create(128).update(data).arrayBuffer()) + case 'shake-256': + return Buffer.from(sha3.shake256.create(256).update(data).arrayBuffer()) + case 'keccak-224': + return Buffer.from(sha3.keccak224.arrayBuffer(data)) + case 'keccak-256': + return Buffer.from(sha3.keccak256.arrayBuffer(data)) + case 'keccak-384': + return Buffer.from(sha3.keccak384.arrayBuffer(data)) + case 'keccak-512': + return Buffer.from(sha3.keccak512.arrayBuffer(data)) + case 'murmur3-128': + return Buffer.from(mur.x64.hash128(data), 'hex') + case 'murmur3-32': + return fromNumberTo32BitBuf(mur.x86.hash32(data)) - default: - throw new TypeError(`${algorithm} is not a supported algorithm`) - } - } catch (error) { - return reject(error) - } - }) + default: + throw new TypeError(`${algorithm} is not a supported algorithm`) + } } module.exports = { diff --git a/src/index.js b/src/index.js index 00c0716a..c353aac1 100644 --- a/src/index.js +++ b/src/index.js @@ -1,19 +1,19 @@ 'use strict' +const errcode = require('err-code') const multihash = require('multihashes') const crypto = require('./crypto') /** - * Hash the given `buf` using the algorithm specified - * by `func`. + * Hash the given `buf` using the algorithm specified by `alg`. * @param {Buffer} buf - The value to hash. - * @param {number|string} func - The algorithm to use. + * @param {number|string} alg - The algorithm to use eg 'sha1' * @param {number} [length] - Optionally trim the result to this length. * @returns {Promise} */ -function Multihashing (buf, func, length) { - return Multihashing.digest(buf, func, length) - .then(digest => multihash.encode(digest, func, length)) +async function Multihashing (buf, alg, length) { + const digest = await Multihashing.digest(buf, alg, length) + return multihash.encode(digest, alg, length) } /** @@ -30,38 +30,34 @@ Multihashing.multihash = multihash /** * @param {Buffer} buf - The value to hash. - * @param {number|string} func - The algorithm to use. + * @param {number|string} alg - The algorithm to use eg 'sha1' * @param {number} [length] - Optionally trim the result to this length. - * @returns {Promise} + * @returns {Promise} */ -Multihashing.digest = (buf, func, length) => { - try { - return Multihashing.createHash(func)(buf) - .then(digest => { - if (length) { - return digest.slice(0, length) - } - return digest - }) - } catch (err) { - return Promise.reject(err) - } +Multihashing.digest = async (buf, alg, length) => { + const hash = Multihashing.createHash(alg) + const digest = await hash(buf) + return length ? digest.slice(0, length) : digest } /** - * Creates a function that hashs with the provided algorithm + * Creates a function that hashes with the given algorithm * - * @param {string|number} func + * @param {string|number} alg - The algorithm to use eg 'sha1' * - * @returns {function} - The to `func` corresponding hash function. + * @returns {function} - The hash function corresponding to `alg` */ -Multihashing.createHash = function (func) { - func = multihash.coerceCode(func) - if (!Multihashing.functions[func]) { - throw new Error('multihash function ' + func + ' not yet supported') +Multihashing.createHash = function (alg) { + if (!alg) { + throw errcode('hash algorithm must be specified', 'ERR_HASH_ALGORITHM_NOT_SPECIFIED') + } + + alg = multihash.coerceCode(alg) + if (!Multihashing.functions[alg]) { + throw errcode(`multihash function '${alg}' not yet supported`, 'ERR_HASH_ALGORITHM_NOT_SUPPORTED') } - return Multihashing.functions[func] + return Multihashing.functions[alg] } /** diff --git a/src/sha.browser.js b/src/sha.browser.js index ad39bb65..3658dcc5 100644 --- a/src/sha.browser.js +++ b/src/sha.browser.js @@ -8,18 +8,17 @@ module.exports = (algorithm) => { ) } - return (data) => { + return async (data) => { switch (algorithm) { case 'sha1': - return crypto.subtle.digest({ name: 'SHA-1' }, data).then(Buffer.from) + return Buffer.from(await crypto.subtle.digest({ name: 'SHA-1' }, data)) case 'sha2-256': - return crypto.subtle.digest({ name: 'SHA-256' }, data).then(Buffer.from) + return Buffer.from(await crypto.subtle.digest({ name: 'SHA-256' }, data)) case 'sha2-512': - return crypto.subtle.digest({ name: 'SHA-512' }, data).then(Buffer.from) + return Buffer.from(await crypto.subtle.digest({ name: 'SHA-512' }, data)) case 'dbl-sha2-256': { - return crypto.subtle.digest({ name: 'SHA-256' }, data) - .then(d => crypto.subtle.digest({ name: 'SHA-256' }, d)) - .then(Buffer.from) + const d = await crypto.subtle.digest({ name: 'SHA-256' }, data) + return Buffer.from(await crypto.subtle.digest({ name: 'SHA-256' }, d)) } default: throw new TypeError(`${algorithm} is not a supported algorithm`) diff --git a/src/sha.js b/src/sha.js index 3516fad9..aeb866c6 100644 --- a/src/sha.js +++ b/src/sha.js @@ -1,25 +1,22 @@ 'use strict' const crypto = require('crypto') -module.exports = (algorithm) => (data) => { - return new Promise((resolve, reject) => { - try { - switch (algorithm) { - case 'sha1': - return resolve(crypto.createHash('sha1').update(data).digest()) - case 'sha2-256': - return resolve(crypto.createHash('sha256').update(data).digest()) - case 'sha2-512': - return resolve(crypto.createHash('sha512').update(data).digest()) - case 'dbl-sha2-256': { - const first = crypto.createHash('sha256').update(data).digest() - return resolve(crypto.createHash('sha256').update(first).digest()) - } - default: - throw new TypeError(`${algorithm} is not a supported algorithm`) - } - } catch (error) { - return reject(error) +// Note that although this function doesn't do any asynchronous work, we mark +// the function as async because it must return a Promise to match the API +// for other functions that do perform asynchronous work (see sha.browser.js) +module.exports = (algorithm) => async (data) => { + switch (algorithm) { + case 'sha1': + return crypto.createHash('sha1').update(data).digest() + case 'sha2-256': + return crypto.createHash('sha256').update(data).digest() + case 'sha2-512': + return crypto.createHash('sha512').update(data).digest() + case 'dbl-sha2-256': { + const first = crypto.createHash('sha256').update(data).digest() + return crypto.createHash('sha256').update(first).digest() } - }) + default: + throw new TypeError(`${algorithm} is not a supported algorithm`) + } } diff --git a/test/index.spec.js b/test/index.spec.js index 1b083e62..793fe515 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -5,12 +5,13 @@ const chai = require('chai') const dirtyChai = require('dirty-chai') chai.use(dirtyChai) const expect = chai.expect +const sinon = require('sinon') const multihashing = require('../src') const fixtures = require('./fixtures/encodes') describe('multihashing', () => { - fixtures.forEach((fixture) => { + for (const fixture of fixtures) { const raw = fixture[0] const func = fixture[1] const encoded = fixture[2] @@ -19,7 +20,7 @@ describe('multihashing', () => { const digest = await multihashing(Buffer.from(raw), func) expect(digest.toString('hex')).to.eql(encoded) }) - }) + } it('cuts the length', async () => { const buf = Buffer.from('beep boop') @@ -64,3 +65,44 @@ describe('validate', () => { }) }) }) + +describe('error handling', () => { + const methods = { + multihashing: multihashing, + digest: multihashing.digest, + createHash: (buff, alg) => multihashing.createHash(alg) + } + + for (const [name, fn] of Object.entries(methods)) { + describe(name, () => { + it('throws an error when there is no hashing algorithm specified', async () => { + const buf = Buffer.from('beep boop') + + try { + await fn(buf) + } catch (err) { + expect(err).to.exist() + expect(err.code).to.eql('ERR_HASH_ALGORITHM_NOT_SPECIFIED') + return + } + expect.fail('Did not throw') + }) + + it('throws an error when the hashing algorithm is not supported', async () => { + const buf = Buffer.from('beep boop') + + const stub = sinon.stub(require('multihashes'), 'coerceCode').returns('snake-oil') + try { + await fn(buf, 'snake-oil') + } catch (err) { + expect(err).to.exist() + expect(err.code).to.eql('ERR_HASH_ALGORITHM_NOT_SUPPORTED') + return + } finally { + stub.restore() + } + expect.fail('Did not throw') + }) + }) + } +}) From a91a159889593039efccd6a0255e5da889655288 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Thu, 14 Mar 2019 10:55:28 +0000 Subject: [PATCH 4/6] chore: cleanup --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5ad2a7a3..5cb5e763 100644 --- a/package.json +++ b/package.json @@ -38,10 +38,10 @@ "err-code": "^1.1.2", "js-sha3": "~0.8.0", "multihashes": "~0.4.13", - "murmurhash3js-revisited": "^3.0.0", - "sinon": "^7.2.7" + "murmurhash3js-revisited": "^3.0.0" }, "devDependencies": { + "sinon": "^7.2.7", "aegir": "^18.0.3", "benchmark": "^2.1.4", "chai": "^4.1.2", From bf592869e5339f2a8d46435c14a5d9aa603bf0ae Mon Sep 17 00:00:00 2001 From: dirkmc Date: Fri, 15 Mar 2019 13:27:21 -0400 Subject: [PATCH 5/6] chore: use async-await for benchmarks (#41) --- benchmarks/hash.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/benchmarks/hash.js b/benchmarks/hash.js index b0ad8f92..20409ec9 100644 --- a/benchmarks/hash.js +++ b/benchmarks/hash.js @@ -30,15 +30,12 @@ const algs = [ ] algs.forEach((alg) => { - suite.add(alg, function (d) { + suite.add(alg, async function (d) { const buf = Buffer.alloc(10 * 1024) buf.fill(Math.ceil(Math.random() * 100)) - multihashing(buf, alg) - .then(res => { - list.push(res) - d.resolve() - }) - .catch(err => console.log(err)) + const res = await multihashing(buf, alg) + list.push(res) + d.resolve() }, { defer: true }) From d6889e9d0e8f4bb1892b0b5fa8af6e4f4172e0e3 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Tue, 16 Apr 2019 15:21:08 +0100 Subject: [PATCH 6/6] fix: add validate function and more --- package.json | 7 ++++--- src/blake.js | 1 + src/crypto.js | 1 + src/index.js | 12 ++++++------ src/sha.browser.js | 5 ++++- src/sha.js | 2 +- src/utils.js | 2 ++ test/index.spec.js | 28 ++++++++++------------------ 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index 5cb5e763..45eec938 100644 --- a/package.json +++ b/package.json @@ -35,17 +35,18 @@ }, "dependencies": { "blakejs": "^1.1.0", + "buffer": "^5.2.1", "err-code": "^1.1.2", "js-sha3": "~0.8.0", "multihashes": "~0.4.13", "murmurhash3js-revisited": "^3.0.0" }, "devDependencies": { - "sinon": "^7.2.7", - "aegir": "^18.0.3", + "aegir": "^18.2.2", "benchmark": "^2.1.4", "chai": "^4.1.2", - "dirty-chai": "^2.0.1" + "dirty-chai": "^2.0.1", + "sinon": "^7.2.7" }, "engines": { "node": ">=6.0.0", diff --git a/src/blake.js b/src/blake.js index ef55f25b..292e07dc 100644 --- a/src/blake.js +++ b/src/blake.js @@ -1,5 +1,6 @@ 'use strict' +const { Buffer } = require('buffer') const blake = require('blakejs') const minB = 0xb201 diff --git a/src/crypto.js b/src/crypto.js index 894a9b49..847e8ad0 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -1,5 +1,6 @@ 'use strict' +const { Buffer } = require('buffer') const sha3 = require('js-sha3') const mur = require('murmurhash3js-revisited') const sha = require('./sha') diff --git a/src/index.js b/src/index.js index c353aac1..33886715 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ 'use strict' +const { Buffer } = require('buffer') const errcode = require('err-code') const multihash = require('multihashes') const crypto = require('./crypto') @@ -102,11 +103,10 @@ Multihashing.functions = { // add blake functions crypto.addBlake(Multihashing.functions) -Multihashing.validate = (data, hash, callback) => { - let algo = multihash.decode(hash).name - Multihashing(data, algo, (err, newHash) => { - if (err) return callback(err) - callback(err, Buffer.compare(hash, newHash) === 0) - }) +Multihashing.validate = async (buf, hash) => { + const newHash = await Multihashing(buf, multihash.decode(hash).name) + + return Buffer.compare(hash, newHash) === 0 } + module.exports = Multihashing diff --git a/src/sha.browser.js b/src/sha.browser.js index 3658dcc5..996eba08 100644 --- a/src/sha.browser.js +++ b/src/sha.browser.js @@ -1,4 +1,7 @@ 'use strict' + +const { Buffer } = require('buffer') + const crypto = self.crypto || self.msCrypto module.exports = (algorithm) => { @@ -21,7 +24,7 @@ module.exports = (algorithm) => { return Buffer.from(await crypto.subtle.digest({ name: 'SHA-256' }, d)) } default: - throw new TypeError(`${algorithm} is not a supported algorithm`) + throw new Error(`${algorithm} is not a supported algorithm`) } } } diff --git a/src/sha.js b/src/sha.js index aeb866c6..94e609d4 100644 --- a/src/sha.js +++ b/src/sha.js @@ -17,6 +17,6 @@ module.exports = (algorithm) => async (data) => { return crypto.createHash('sha256').update(first).digest() } default: - throw new TypeError(`${algorithm} is not a supported algorithm`) + throw new Error(`${algorithm} is not a supported algorithm`) } } diff --git a/src/utils.js b/src/utils.js index b82b6b4b..03415260 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,7 @@ 'use strict' +const { Buffer } = require('buffer') + const fromNumberTo32BitBuf = (number) => { const bytes = new Array(4) diff --git a/test/index.spec.js b/test/index.spec.js index 793fe515..e292af37 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -1,6 +1,7 @@ /* eslint-env mocha */ 'use strict' +const { Buffer } = require('buffer') const chai = require('chai') const dirtyChai = require('dirty-chai') chai.use(dirtyChai) @@ -43,26 +44,17 @@ describe('multihashing', () => { }) describe('validate', () => { - it('true on pass', done => { - multihashing(Buffer.from('test'), 'sha2-256', (err, hash) => { - if (err) throw done(err) - multihashing.validate(Buffer.from('test'), hash, (err, bool) => { - if (err) throw done(err) - expect(bool).to.eql(true) - done() - }) - }) + it('true on pass', async () => { + const hash = await multihashing(Buffer.from('test'), 'sha2-256') + const validation = await multihashing.validate(Buffer.from('test'), hash) + + return expect(validation).to.eql(true) }) - it('false on fail', done => { - multihashing(Buffer.from('test'), 'sha2-256', (err, hash) => { - if (err) throw done(err) - multihashing.validate(Buffer.from('test-fail'), hash, (err, bool) => { - if (err) throw done(err) - expect(bool).to.eql(false) - done() - }) - }) + it('false on fail', async () => { + const hash = await multihashing(Buffer.from('test'), 'sha2-256') + const validation = await multihashing.validate(Buffer.from('test-fail'), hash) + return expect(validation).to.eql(false) }) })