Skip to content
This repository was archived by the owner on Aug 24, 2021. It is now read-only.

Feat/async iterators #40

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,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
Expand Down
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
"browser": {
"./src/sha.js": "./src/sha.browser.js"
},
"pre-push": [
"lint",
"test"
],
"scripts": {
"test": "aegir test",
"test:browser": "aegir test -t browser",
Expand All @@ -37,9 +41,11 @@
"repository": "github:multiformats/js-multihashing-async",
"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",
Expand Down
17 changes: 7 additions & 10 deletions src/blake.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
67 changes: 32 additions & 35 deletions src/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
52 changes: 24 additions & 28 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -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<Buffer>}
*/
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)
}

/**
Expand All @@ -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<Buffer>}
*/
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]
}

/**
Expand Down
13 changes: 6 additions & 7 deletions src/sha.browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
37 changes: 17 additions & 20 deletions src/sha.js
Original file line number Diff line number Diff line change
@@ -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`)
}
}
46 changes: 44 additions & 2 deletions test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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')
Expand All @@ -40,3 +41,44 @@ describe('multihashing', () => {
)
})
})

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