diff --git a/.eslintrc b/.eslintrc index 19181f2..4f29db2 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,12 +8,19 @@ }, "overrides": [ + { + "files": "browser.js", + "rules": { + "global-require": "off", + }, + }, { "files": [ - "browser.js", + "browser.*.js", ], "rules": { "no-underscore-dangle": "warn", + "sort-keys": "off", }, }, ], diff --git a/browser.js b/browser.js index d8f9cbf..71ebe5a 100644 --- a/browser.js +++ b/browser.js @@ -1,35 +1,11 @@ 'use strict'; -var inherits = require('inherits'); -var MD5 = require('md5.js'); -var RIPEMD160 = require('ripemd160'); -var sha = require('sha.js'); -var Base = require('cipher-base'); - -function Hash(hash) { - Base.call(this, 'digest'); - - this._hash = hash; -} - -inherits(Hash, Base); - -Hash.prototype._update = function (data) { - this._hash.update(data); -}; - -Hash.prototype._final = function () { - return this._hash.digest(); -}; - -module.exports = function createHash(algorithm) { - var alg = algorithm.toLowerCase(); - if (alg === 'md5') { - return new MD5(); - } - if (alg === 'rmd160' || alg === 'ripemd160') { - return new RIPEMD160(); +if (typeof BigInt === 'undefined') { + module.exports = require('./browser.old.js'); +} else { + try { + module.exports = require('./browser.noble.js'); + } catch (err) { + module.exports = require('./browser.old.js'); } - - return new Hash(sha(alg)); -}; +} diff --git a/browser.noble.js b/browser.noble.js new file mode 100644 index 0000000..2032499 --- /dev/null +++ b/browser.noble.js @@ -0,0 +1,64 @@ +'use strict'; + +var sha1 = require('@noble/hashes/sha1'); +var ripemd160 = require('@noble/hashes/ripemd160'); +var sha2 = require('@noble/hashes/sha2'); +var sha3 = require('@noble/hashes/sha3'); +var blake2b = require('@noble/hashes/blake2b'); +var blake2s = require('@noble/hashes/blake2s'); +var inherits = require('inherits'); +var MD5 = require('md5.js'); +var Base = require('cipher-base'); +var Buffer = require('safe-buffer').Buffer; + +function Hash(hash) { + Base.call(this, 'digest'); + + this._hash = hash; +} + +inherits(Hash, Base); + +Hash.prototype._update = function (data) { + this._hash.update(data); +}; + +Hash.prototype._final = function () { + var uarr = this._hash.digest(); + return Buffer.from(uarr.buffer, uarr.byteOffset, uarr.byteLength); +}; + +var hashes = { + // Supported by browser.old.js + sha1: sha1.sha1, + sha224: sha2.sha224, + sha256: sha2.sha256, + sha384: sha2.sha384, + sha512: sha2.sha512, + ripemd160: ripemd160.ripemd160, + rmd160: ripemd160.ripemd160, + + // Not supported by browser.old.js (until sha.js updates?) + 'sha512-224': sha2.sha512_224, // for browser.old.js: https://github.com/browserify/sha.js/pull/67 + 'sha512-256': sha2.sha512_256, // for browser.old.js: https://github.com/browserify/sha.js/pull/67 + 'sha3-224': sha3.sha3_224, + 'sha3-256': sha3.sha3_256, + 'sha3-384': sha3.sha3_384, + 'sha3-512': sha3.sha3_512, + blake2b512: blake2b.blake2b, // 512 is the default size + blake2s256: blake2s.blake2s // 256 is the default size +}; + +module.exports = function createHash(algorithm) { + var alg = algorithm.toLowerCase(); + + if (alg === 'md5') { + return new MD5(); + } + + if (!Object.prototype.hasOwnProperty.call(hashes, alg)) { + throw new Error('Digest method not supported'); + } + + return new Hash(hashes[alg].create()); +}; diff --git a/browser.old.js b/browser.old.js new file mode 100644 index 0000000..d8f9cbf --- /dev/null +++ b/browser.old.js @@ -0,0 +1,35 @@ +'use strict'; + +var inherits = require('inherits'); +var MD5 = require('md5.js'); +var RIPEMD160 = require('ripemd160'); +var sha = require('sha.js'); +var Base = require('cipher-base'); + +function Hash(hash) { + Base.call(this, 'digest'); + + this._hash = hash; +} + +inherits(Hash, Base); + +Hash.prototype._update = function (data) { + this._hash.update(data); +}; + +Hash.prototype._final = function () { + return this._hash.digest(); +}; + +module.exports = function createHash(algorithm) { + var alg = algorithm.toLowerCase(); + if (alg === 'md5') { + return new MD5(); + } + if (alg === 'rmd160' || alg === 'ripemd160') { + return new RIPEMD160(); + } + + return new Hash(sha(alg)); +}; diff --git a/package.json b/package.json index bdc270f..8acf100 100644 --- a/package.json +++ b/package.json @@ -31,13 +31,16 @@ "md5.js": "^1.3.4", "readable-stream": "^2.3.8", "ripemd160": "^2.0.2", + "safe-buffer": "^5.0.1", "sha.js": "^2.4.11" }, + "optionalDependencies": { + "@noble/hashes": "^1.3.3" + }, "devDependencies": { "@ljharb/eslint-config": "^21.1.1", "eslint": "=8.8.0", "hash-test-vectors": "^1.3.2", - "safe-buffer": "^5.2.1", "tape": "^5.9.0" }, "engines": { diff --git a/test/index.js b/test/index.js index dacff62..2eec2ca 100644 --- a/test/index.js +++ b/test/index.js @@ -10,35 +10,75 @@ vectors.forEach(function (vector) { // eslint-disable-next-line no-param-reassign vector.ripemd160 = vector.rmd160; }); -var createHash = require('../browser'); - -algorithms.forEach(function (algorithm) { - test('test ' + algorithm + ' against test vectors', function (t) { - vectors.forEach(function (obj, i) { - var input = Buffer.from(obj.input, 'base64'); - var node = obj[algorithm]; - var js = createHash(algorithm).update(input).digest('hex'); - t.equal(js, node, algorithm + '(testVector[' + i + ']) == ' + node); - }); +var createHashOld = require('../browser.old.js'); +var createHashAuto = require('../browser.js'); + +var implementations = [createHashOld]; +if (createHashAuto !== createHashOld) { implementations.push(createHashAuto); } - encodings.forEach(function (encoding) { +implementations.forEach(function (createHash) { + algorithms.forEach(function (algorithm) { + test('test ' + algorithm + ' against test vectors', function (t) { vectors.forEach(function (obj, i) { - var input = Buffer.from(obj.input, 'base64').toString(encoding); + var input = Buffer.from(obj.input, 'base64'); var node = obj[algorithm]; - var js = createHash(algorithm).update(input, encoding).digest('hex'); - t.equal(js, node, algorithm + '(testVector[' + i + '], ' + encoding + ') == ' + node); + var js = createHash(algorithm).update(input).digest('hex'); + t.equal(js, node, algorithm + '(testVector[' + i + ']) == ' + node); + }); + + encodings.forEach(function (encoding) { + vectors.forEach(function (obj, i) { + var input = Buffer.from(obj.input, 'base64').toString(encoding); + var node = obj[algorithm]; + var js = createHash(algorithm).update(input, encoding).digest('hex'); + t.equal(js, node, algorithm + '(testVector[' + i + '], ' + encoding + ') == ' + node); + }); + }); + + vectors.forEach(function (obj, i) { + var input = Buffer.from(obj.input, 'base64'); + var node = obj[algorithm]; + var hash = createHash(algorithm); + hash.end(input); + var js = hash.read().toString('hex'); + t.equal(js, node, algorithm + '(testVector[' + i + ']) == ' + node); }); - }); - vectors.forEach(function (obj, i) { - var input = Buffer.from(obj.input, 'base64'); - var node = obj[algorithm]; - var hash = createHash(algorithm); - hash.end(input); - var js = hash.read().toString('hex'); - t.equal(js, node, algorithm + '(testVector[' + i + ']) == ' + node); + t.end(); }); + }); +}); +var createHashNode = require('crypto').createHash; +var randomBytes = require('crypto').randomBytes; + +function crossTest(createHashTest, createHashBase, algs) { + var data = randomBytes(32); + test('test against base implementation', function (t) { + algs.forEach(function (algorithm) { + var a = createHashTest(algorithm).update(data).digest('hex'); + var b; + try { + b = createHashBase(algorithm).update(data).digest('hex'); + } catch (err) {} + var label = algorithm + '(' + data.toString('hex') + ')'; + if (b) { + t.equal(a, b, label); + } else { + t.skip(label); // Node.js version doesn't support it + } + }); t.end(); }); -}); +} + +var baseHashes = ['sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'ripemd160', 'rmd160']; +var extraHashes = ['sha512-224', 'sha512-256', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512', 'blake2b512', 'blake2s256']; + +crossTest(createHashOld, createHashNode, baseHashes); + +// Only new version supports additional hashes +if (createHashAuto !== createHashOld) { + crossTest(createHashAuto, createHashNode, baseHashes); + crossTest(createHashAuto, createHashNode, extraHashes); +}