Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a new noble-based implementation #31

Merged
merged 1 commit into from
Nov 16, 2024
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
9 changes: 8 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
},
],
Expand Down
40 changes: 8 additions & 32 deletions browser.js
Original file line number Diff line number Diff line change
@@ -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));
};
}
64 changes: 64 additions & 0 deletions browser.noble.js
Original file line number Diff line number Diff line change
@@ -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());
};
35 changes: 35 additions & 0 deletions browser.old.js
Original file line number Diff line number Diff line change
@@ -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));
};
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
86 changes: 63 additions & 23 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}