diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..0a4cc4b --- /dev/null +++ b/.babelrc @@ -0,0 +1,34 @@ +{ + "env": { + "development": { + "sourceMaps": "inline", + "comments": false, + "presets": [ + [ + "env", + { + "targets": { + "node": "current" + } + } + ], + "flow-node" + ] + }, + "umd": { + "comments": false, + "presets": [ + [ + "env", + { + "modules": false, + "targets": { + "browsers": "last 2 versions" + } + } + ], + "flow-node" + ] + } + } +} diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..57c6722 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,9 @@ +[ignore] +.*/node_modules/documentation/* + +[libs] + +[include] + +[options] +suppress_comment= \\(.\\|\n\\)*\\@FlowIgnore diff --git a/package.json b/package.json index e2c7f82..11ed80f 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,10 @@ "description": "multiple hash functions", "main": "src/index.js", "scripts": { - "lint": "aegir lint", - "build": "aegir build", - "test": "aegir test -t node -t browser -t webworker", + "lint": "npm run type-check && aegir lint", + "type-check": "flow check", + "build": "npm run build:node && BABEL_ENV=umd aegir build", + "test": "npm run build:node && aegir test -t node -t browser -t webworker", "test:node": "aegir test -t node", "test:browser": "aegir test -t browser", "test:webworker": "aegir test -t webworker", @@ -14,12 +15,21 @@ "release-minor": "aegir release --type minor -t node -t browser", "release-major": "aegir release --type major -t node -t browser", "coverage": "aegir coverage", - "coverage-publish": "aegir coverage --provider coveralls" + "coverage-publish": "aegir coverage --provider coveralls", + "build:types": "flow-copy-source --verbose src lib", + "build:lib": "babel --out-dir lib src", + "build:node": "npm run build:types && npm run build:lib", + "start": "flow-copy-source --watch --verbose src lib & babel --watch --out-dir lib src" }, "pre-commit": [ "lint", "test" ], + "standard": { + "ignore": [ + "dist" + ] + }, "repository": { "type": "git", "url": "git://github.com/multiformats/js-multihashing.git" @@ -33,16 +43,25 @@ "url": "https://github.com/multiformats/js-multihashing/issues" }, "dependencies": { + "babel-cli": "^6.26.0", + "babel-core": "^6.26.0", + "babel-loader": "^7.1.4", "blakejs": "^1.1.0", "js-sha3": "^0.7.0", "multihashes": "~0.4.12", "webcrypto": "~0.1.1" }, "devDependencies": { - "aegir": "^12.3.0", + "aegir": "git+https://github.com/ipfs/aegir.git#flow", + "babel-preset-flow-node": "^2.0.1", "chai": "^4.1.2", "dirty-chai": "^2.0.1", - "pre-commit": "^1.2.2" + "flow-bin": "^0.69.0", + "flow-copy-source": "^1.3.0", + "lint-staged": "^7.0.2", + "pre-commit": "^1.2.2", + "rollup.config.flow": "^1.0.0", + "source-map-support": "^0.5.4" }, "homepage": "https://github.com/multiformats/js-multihashing", "contributors": [ diff --git a/src/blake.js b/src/blake.js index 1f7af54..55603de 100644 --- a/src/blake.js +++ b/src/blake.js @@ -1,28 +1,51 @@ -'use strict' +// @flow +import * as blake from 'blakejs' +import type { Hash, HashUpdate, HashTable, HashBuilder } from './types' +import type { Code } from 'multihashes/lib/constants' -const blake = require('blakejs') -const minB = 0xb201 -const minS = 0xb241 +const minB: Code = 0xb201 +const minS: Code = 0xb241 -var blake2b = { +type BlakeCtx = { + b: Uint8Array, + h: Uint32Array, + t: number, + c: number, + outlen: number +} +type Blake2Hash = Buffer +type BlakeHasher = { + init(size: number, key: ?number): BlakeCtx, + update(ctx: BlakeCtx, input: Uint8Array): void, + digest(ctx: BlakeCtx): Uint8Array +} + +const blake2b: BlakeHasher = { init: blake.blake2bInit, update: blake.blake2bUpdate, digest: blake.blake2bFinal } -var blake2s = { +const blake2s: BlakeHasher = { init: blake.blake2sInit, update: blake.blake2sUpdate, digest: blake.blake2sFinal } -class B2Hash { +class B2Hash implements Hash { + ctx: BlakeCtx | null + hf: BlakeHasher + constructor (size, hashFunc) { this.hf = hashFunc this.ctx = this.hf.init(size, null) } - update (buf) { + static new (size, hashFunc): HashUpdate { + return new B2Hash(size, hashFunc) + } + + update (buf: Buffer): Hash { if (this.ctx === null) { throw new Error('blake2 context is null. (already called digest?)') } @@ -30,27 +53,27 @@ class B2Hash { return this } - digest () { + digest (): Blake2Hash { const ctx = this.ctx this.ctx = null + if (ctx === null) { + throw Error('blake2 context is null. (already called digest?)') + } return Buffer.from(this.hf.digest(ctx)) } } -function addFuncs (table) { - function mkFunc (size, hashFunc) { - return () => new B2Hash(size, hashFunc) +export const addFuncs = (table: HashTable) => { + const mkFunc = (size: number, hashFunc: BlakeHasher): HashBuilder => { + return (): HashUpdate => B2Hash.new(size, hashFunc) } - var i + // I don't like using any here but the only way I could get the types to work here. + let i for (i = 0; i < 64; i++) { - table[minB + i] = mkFunc(i + 1, blake2b) + table[(minB + i: any)] = mkFunc(i + 1, blake2b) } for (i = 0; i < 32; i++) { - table[minS + i] = mkFunc(i + 1, blake2s) + table[(minS + i: any)] = mkFunc(i + 1, blake2s) } } - -module.exports = { - addFuncs: addFuncs -} diff --git a/src/index.js b/src/index.js index 1624e61..5b564d8 100644 --- a/src/index.js +++ b/src/index.js @@ -1,23 +1,34 @@ -'use strict' +// @flow -const multihash = require('multihashes') -const blake = require('./blake') -const sha3 = require('./sha3') -const crypto = require('webcrypto') +import * as multihash from 'multihashes' +import type { Multihash } from 'multihashes' +import type { Name, Code } from 'multihashes/lib/constants' +import type { HashUpdate, HashTable } from './types' +import * as blake from './blake' +import * as sha3 from './sha3' +import * as crypto from 'webcrypto' -const mh = module.exports = Multihashing +const mh = Multihashing +export default mh mh.Buffer = Buffer // for browser things -function Multihashing (buf, func, length) { +function Multihashing ( + buf: Buffer, + func: Name | Code, + length: number +): Multihash { return multihash.encode(mh.digest(buf, func, length), func, length) } // expose multihash itself, to avoid silly double requires. mh.multihash = multihash -mh.digest = function (buf, func, length) { - let digest = mh.createHash(func).update(buf).digest() +mh.digest = function (buf: Buffer, func: Name | Code, length: ?number): Buffer { + let digest = mh + .createHash(func) + .update(buf) + .digest() if (length) { digest = digest.slice(0, length) @@ -26,7 +37,7 @@ mh.digest = function (buf, func, length) { return digest } -mh.createHash = function (func, length) { +mh.createHash = function (func: Name | Code): HashUpdate { func = multihash.coerceCode(func) if (!mh.functions[func]) { throw new Error('multihash function ' + func + ' not yet supported') @@ -35,29 +46,31 @@ mh.createHash = function (func, length) { return mh.functions[func]() } -mh.verify = function (hash, buf) { +mh.verify = function verify (hash: Multihash, buf: Buffer): boolean { const decoded = multihash.decode(hash) const encoded = mh(buf, decoded.name, decoded.length) return encoded.equals(hash) } -mh.functions = { - 0x11: gsha1, - 0x12: gsha2256, - 0x13: gsha2512 -} +/* eslint-disable no-useless-computed-key */ +mh.functions = ({ + [0x11]: gsha1, + [0x12]: gsha2256, + [0x13]: gsha2512 +}: HashTable) +/* eslint-enable no-useless-computed-key */ blake.addFuncs(mh.functions) sha3.addFuncs(mh.functions) -function gsha1 () { +function gsha1 (): HashUpdate { return crypto.createHash('sha1') } -function gsha2256 () { +function gsha2256 (): HashUpdate { return crypto.createHash('sha256') } -function gsha2512 () { +function gsha2512 (): HashUpdate { return crypto.createHash('sha512') } diff --git a/src/sha3.js b/src/sha3.js index b7a9695..2d673cb 100644 --- a/src/sha3.js +++ b/src/sha3.js @@ -1,6 +1,6 @@ -'use strict' - -const sha3 = require('js-sha3') +// @flow +import * as sha3 from 'js-sha3' +import type { Hash, HashTable, HashUpdate } from './types' const functions = [ [0x14, sha3.sha3_512], @@ -9,44 +9,57 @@ const functions = [ [0x17, sha3.sha3_224], [0x18, sha3.shake128, 256], [0x19, sha3.shake256, 512], - [0x1A, sha3.keccak224], - [0x1B, sha3.keccak256], - [0x1C, sha3.keccak384], - [0x1D, sha3.keccak512] + [0x1a, sha3.keccak224], + [0x1b, sha3.keccak256], + [0x1c, sha3.keccak384], + [0x1d, sha3.keccak512] ] -class Hasher { - constructor (hashFunc, arg) { +type HexString = string +type Sha3Hash = Buffer +type ShaHasher = (input: string | Buffer, length?: number) => HexString + +class ShaHash implements Hash { + hf: ShaHasher + input: Buffer | null + arg: number + + constructor (hashFunc, arg?: number) { this.hf = hashFunc - this.arg = arg + if (arg) { + this.arg = arg + } this.input = null } - update (buf) { + static new (hashFunc, arg?: number): HashUpdate { + return new ShaHash(hashFunc, arg) + } + + update (buf: Buffer): Hash { this.input = buf return this } - digest () { + digest (): Sha3Hash { + if (!this.input) { + throw Error('Missing an input to hash') + } const input = this.input const arg = this.arg return Buffer.from(this.hf(input, arg), 'hex') } } -function addFuncs (table) { +export const addFuncs = (table: HashTable) => { for (const info of functions) { const code = info[0] const fn = info[1] if (info.length === 3) { - table[code] = () => new Hasher(fn, info[2]) + table[code] = () => ShaHash.new(fn, info[2]) } else { - table[code] = () => new Hasher(fn) + table[code] = () => ShaHash.new(fn) } } } - -module.exports = { - addFuncs: addFuncs -} diff --git a/src/types.js b/src/types.js new file mode 100644 index 0000000..973bd99 --- /dev/null +++ b/src/types.js @@ -0,0 +1,23 @@ +// @flow +import type { Code } from 'multihashes/lib/constants' + +// We break up the Hash Class into Hash and HashUpdate +// this is a really nice use of types to avoid the user +// calling digest on the hash class before doing an update +// and adding an input. This works by only returning an interface +// that supports update. Once update is called a full hash interface with +// the digest function exposed is available. +// This will only work for a code also using flow. + +/* eslint-disable no-use-before-define */ +export interface HashUpdate { + update(buf: Buffer): Hash; +} + +export interface Hash extends HashUpdate { + digest(): Buffer; +} +/* eslint-enable no-use-before-define */ + +export type HashBuilder = () => HashUpdate +export type HashTable = { [Code]: HashBuilder } diff --git a/test/index.spec.js b/test/index.spec.js index ef59261..5737474 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -1,63 +1,94 @@ /* eslint-env mocha */ -'use strict' const chai = require('chai') const dirtyChai = require('dirty-chai') chai.use(dirtyChai) const expect = chai.expect -const multihashing = require('../src') +const multihashing = require('../lib') const tests = { - sha1: [ - ['beep boop', '11147c8357577f51d4f0a8d393aa1aaafb28863d9421'] - ], + sha1: [['beep boop', '11147c8357577f51d4f0a8d393aa1aaafb28863d9421']], 'sha2-256': [ - ['beep boop', '122090ea688e275d580567325032492b597bc77221c62493e76330b85ddda191ef7c'] + [ + 'beep boop', + '122090ea688e275d580567325032492b597bc77221c62493e76330b85ddda191ef7c' + ] ], 'sha2-512': [ - ['beep boop', '134014f301f31be243f34c5668937883771fa381002f1aaa5f31b3f78e500b66ff2f4f8ea5e3c9f5a61bd073e2452c480484b02e030fb239315a2577f7ae156af177'] + [ + 'beep boop', + '134014f301f31be243f34c5668937883771fa381002f1aaa5f31b3f78e500b66ff2f4f8ea5e3c9f5a61bd073e2452c480484b02e030fb239315a2577f7ae156af177' + ] ], 'blake2b-512': [ - ['beep boop', 'c0e402400eac6255ba822373a0948122b8d295008419a8ab27842ee0d70eca39855621463c03ec75ac3610aacfdff89fa989d8d61fc00450148f289eb5b12ad1a954f659'] + [ + 'beep boop', + 'c0e402400eac6255ba822373a0948122b8d295008419a8ab27842ee0d70eca39855621463c03ec75ac3610aacfdff89fa989d8d61fc00450148f289eb5b12ad1a954f659' + ] ], 'blake2b-160': [ ['beep boop', '94e40214fe303247293e54e0a7ea48f9408ca68b36b08442'] ], 'blake2s-256': [ - ['beep boop', 'e0e402204542eaca484e4311def8af74b546edd7fceb49eeb3cdcfd8a4a72ed0dc81d4c0'] - ], - 'blake2s-40': [ - ['beep boop', 'c5e402059ada01bb57'] + [ + 'beep boop', + 'e0e402204542eaca484e4311def8af74b546edd7fceb49eeb3cdcfd8a4a72ed0dc81d4c0' + ] ], + 'blake2s-40': [['beep boop', 'c5e402059ada01bb57']], 'sha3-512': [ - ['beep bop', '144038122bf54c0e1eeff013e9b8c735af7c08ff8a7bb2b55b5abe9d97f9653e19f388cd9719ffb4ab8ccb9330a1fd27929b0a9ee6d7b8b9884c6f787f11088219bc'] + [ + 'beep bop', + '144038122bf54c0e1eeff013e9b8c735af7c08ff8a7bb2b55b5abe9d97f9653e19f388cd9719ffb4ab8ccb9330a1fd27929b0a9ee6d7b8b9884c6f787f11088219bc' + ] ], 'sha3-384': [ - ['beep bop', '15302f0456d702d4cafa23c091f8da2dc878b1536fd592c47c2d239f5de67f0a306f36e0197dbbf1c5409292aa92e326b16e'] + [ + 'beep bop', + '15302f0456d702d4cafa23c091f8da2dc878b1536fd592c47c2d239f5de67f0a306f36e0197dbbf1c5409292aa92e326b16e' + ] ], 'sha3-256': [ - ['beep bop', '16204de761fac0b163270f59056e1c5d1038348fcb960c03610bde24565473600dfe'] + [ + 'beep bop', + '16204de761fac0b163270f59056e1c5d1038348fcb960c03610bde24565473600dfe' + ] ], 'sha3-224': [ ['beep bop', '171cda7cce25c2f6248dc88a2fdfca56bb97bf39b100089cacfdf68b7b50'] ], 'shake-128': [ - ['beep boop', '18205fe422311f770743c2e0d86bcca092111cbce85487212829739c3c3723776e5a'] + [ + 'beep boop', + '18205fe422311f770743c2e0d86bcca092111cbce85487212829739c3c3723776e5a' + ] ], 'shake-256': [ - ['beep boop', '194059feb5565e4f924baef74708649fed376d63948a862322ed763ecf093b63b38b0955908c099c63dda73ee469c31b1456cec95e325bd868d0ce0c0135f5a54411'] + [ + 'beep boop', + '194059feb5565e4f924baef74708649fed376d63948a862322ed763ecf093b63b38b0955908c099c63dda73ee469c31b1456cec95e325bd868d0ce0c0135f5a54411' + ] ], 'keccak-224': [ ['beep bop', '1a1c45f2663794752c0a2292985b476a6aec407cfd78ad6f14f56d7060d9'] ], 'keccak-256': [ - ['beep bop', '1b209e7c2fee63c4065cbaa989bd2655a288f877aa788484fb0640d125417796b5e9'] + [ + 'beep bop', + '1b209e7c2fee63c4065cbaa989bd2655a288f877aa788484fb0640d125417796b5e9' + ] ], 'keccak-384': [ - ['beep bop', '1c305a1da4b6e329884e88b66d82961d6518d9cec66eae6ff6900344a30a742a044b6eb584ab6e8a1c609e04f2f235f5cab3'] + [ + 'beep bop', + '1c305a1da4b6e329884e88b66d82961d6518d9cec66eae6ff6900344a30a742a044b6eb584ab6e8a1c609e04f2f235f5cab3' + ] ], 'keccak-512': [ - ['beep bop', '1d403c22f68c98a6c8ea9fa7819721fba90f905d3c29390d4a94fefc4f8348ed04829a7521b44af4aeea707b32fe63e28322666ae26a59feb3493fafa11873383f6c'] + [ + 'beep bop', + '1d403c22f68c98a6c8ea9fa7819721fba90f905d3c29390d4a94fefc4f8348ed04829a7521b44af4aeea707b32fe63e28322666ae26a59feb3493fafa11873383f6c' + ] ] } @@ -75,18 +106,13 @@ describe('multihashing', () => { it('cuts the length', () => { const buf = Buffer.from('beep boop') - - expect( - multihashing(buf, 'sha2-256', 10) - ).to.be.eql( + expect(multihashing(buf, 'sha2-256', 10)).to.be.eql( Buffer.from('120a90ea688e275d58056732', 'hex') ) }) it('throws on non implemented func', () => { - expect( - () => multihashing(Buffer.from('beep boop'), 'sha3') - ).to.throw( + expect(() => multihashing(Buffer.from('beep boop'), 'sha3')).to.throw( /Unrecognized hash function named:/ ) }) diff --git a/test/node.js b/test/node.js new file mode 100644 index 0000000..f58bab2 --- /dev/null +++ b/test/node.js @@ -0,0 +1,4 @@ +// TODO: Instead mocha should be passed additional arg +// `--require source-map-support/register` but as things stand now there is +// no way to do it without changes to aegir so we do this instead for now. +require('source-map-support/register')