Skip to content

Commit 53fa3fa

Browse files
ChALkeRljharb
authored andcommitted
[New] add a new noble-based implementation in modern browsers
1 parent abf9b29 commit 53fa3fa

6 files changed

Lines changed: 182 additions & 57 deletions

File tree

.eslintrc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,19 @@
88
},
99

1010
"overrides": [
11+
{
12+
"files": "browser.js",
13+
"rules": {
14+
"global-require": "off",
15+
},
16+
},
1117
{
1218
"files": [
13-
"browser.js",
19+
"browser.*.js",
1420
],
1521
"rules": {
1622
"no-underscore-dangle": "warn",
23+
"sort-keys": "off",
1724
},
1825
},
1926
],

browser.js

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,11 @@
11
'use strict';
22

3-
var inherits = require('inherits');
4-
var MD5 = require('md5.js');
5-
var RIPEMD160 = require('ripemd160');
6-
var sha = require('sha.js');
7-
var Base = require('cipher-base');
8-
9-
function Hash(hash) {
10-
Base.call(this, 'digest');
11-
12-
this._hash = hash;
13-
}
14-
15-
inherits(Hash, Base);
16-
17-
Hash.prototype._update = function (data) {
18-
this._hash.update(data);
19-
};
20-
21-
Hash.prototype._final = function () {
22-
return this._hash.digest();
23-
};
24-
25-
module.exports = function createHash(algorithm) {
26-
var alg = algorithm.toLowerCase();
27-
if (alg === 'md5') {
28-
return new MD5();
29-
}
30-
if (alg === 'rmd160' || alg === 'ripemd160') {
31-
return new RIPEMD160();
3+
if (typeof BigInt === 'undefined') {
4+
module.exports = require('./browser.old.js');
5+
} else {
6+
try {
7+
module.exports = require('./browser.noble.js');
8+
} catch (err) {
9+
module.exports = require('./browser.old.js');
3210
}
33-
34-
return new Hash(sha(alg));
35-
};
11+
}

browser.noble.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
'use strict';
2+
3+
var sha1 = require('@noble/hashes/sha1');
4+
var ripemd160 = require('@noble/hashes/ripemd160');
5+
var sha2 = require('@noble/hashes/sha2');
6+
var sha3 = require('@noble/hashes/sha3');
7+
var blake2b = require('@noble/hashes/blake2b');
8+
var blake2s = require('@noble/hashes/blake2s');
9+
var inherits = require('inherits');
10+
var MD5 = require('md5.js');
11+
var Base = require('cipher-base');
12+
var Buffer = require('safe-buffer').Buffer;
13+
14+
function Hash(hash) {
15+
Base.call(this, 'digest');
16+
17+
this._hash = hash;
18+
}
19+
20+
inherits(Hash, Base);
21+
22+
Hash.prototype._update = function (data) {
23+
this._hash.update(data);
24+
};
25+
26+
Hash.prototype._final = function () {
27+
var uarr = this._hash.digest();
28+
return Buffer.from(uarr.buffer, uarr.byteOffset, uarr.byteLength);
29+
};
30+
31+
var hashes = {
32+
// Supported by browser.old.js
33+
sha1: sha1.sha1,
34+
sha224: sha2.sha224,
35+
sha256: sha2.sha256,
36+
sha384: sha2.sha384,
37+
sha512: sha2.sha512,
38+
ripemd160: ripemd160.ripemd160,
39+
rmd160: ripemd160.ripemd160,
40+
41+
// Not supported by browser.old.js (until sha.js updates?)
42+
'sha512-224': sha2.sha512_224, // for browser.old.js: https://github.com/browserify/sha.js/pull/67
43+
'sha512-256': sha2.sha512_256, // for browser.old.js: https://github.com/browserify/sha.js/pull/67
44+
'sha3-224': sha3.sha3_224,
45+
'sha3-256': sha3.sha3_256,
46+
'sha3-384': sha3.sha3_384,
47+
'sha3-512': sha3.sha3_512,
48+
blake2b512: blake2b.blake2b, // 512 is the default size
49+
blake2s256: blake2s.blake2s // 256 is the default size
50+
};
51+
52+
module.exports = function createHash(algorithm) {
53+
var alg = algorithm.toLowerCase();
54+
55+
if (alg === 'md5') {
56+
return new MD5();
57+
}
58+
59+
if (!Object.prototype.hasOwnProperty.call(hashes, alg)) {
60+
throw new Error('Digest method not supported');
61+
}
62+
63+
return new Hash(hashes[alg].create());
64+
};

browser.old.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
3+
var inherits = require('inherits');
4+
var MD5 = require('md5.js');
5+
var RIPEMD160 = require('ripemd160');
6+
var sha = require('sha.js');
7+
var Base = require('cipher-base');
8+
9+
function Hash(hash) {
10+
Base.call(this, 'digest');
11+
12+
this._hash = hash;
13+
}
14+
15+
inherits(Hash, Base);
16+
17+
Hash.prototype._update = function (data) {
18+
this._hash.update(data);
19+
};
20+
21+
Hash.prototype._final = function () {
22+
return this._hash.digest();
23+
};
24+
25+
module.exports = function createHash(algorithm) {
26+
var alg = algorithm.toLowerCase();
27+
if (alg === 'md5') {
28+
return new MD5();
29+
}
30+
if (alg === 'rmd160' || alg === 'ripemd160') {
31+
return new RIPEMD160();
32+
}
33+
34+
return new Hash(sha(alg));
35+
};

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,16 @@
3131
"md5.js": "^1.3.4",
3232
"readable-stream": "^2.3.8",
3333
"ripemd160": "^2.0.2",
34+
"safe-buffer": "^5.0.1",
3435
"sha.js": "^2.4.11"
3536
},
37+
"optionalDependencies": {
38+
"@noble/hashes": "^1.3.3"
39+
},
3640
"devDependencies": {
3741
"@ljharb/eslint-config": "^21.1.1",
3842
"eslint": "=8.8.0",
3943
"hash-test-vectors": "^1.3.2",
40-
"safe-buffer": "^5.2.1",
4144
"tape": "^5.9.0"
4245
},
4346
"engines": {

test/index.js

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,75 @@ vectors.forEach(function (vector) {
1010
// eslint-disable-next-line no-param-reassign
1111
vector.ripemd160 = vector.rmd160;
1212
});
13-
var createHash = require('../browser');
14-
15-
algorithms.forEach(function (algorithm) {
16-
test('test ' + algorithm + ' against test vectors', function (t) {
17-
vectors.forEach(function (obj, i) {
18-
var input = Buffer.from(obj.input, 'base64');
19-
var node = obj[algorithm];
20-
var js = createHash(algorithm).update(input).digest('hex');
21-
t.equal(js, node, algorithm + '(testVector[' + i + ']) == ' + node);
22-
});
13+
var createHashOld = require('../browser.old.js');
14+
var createHashAuto = require('../browser.js');
15+
16+
var implementations = [createHashOld];
17+
if (createHashAuto !== createHashOld) { implementations.push(createHashAuto); }
2318

24-
encodings.forEach(function (encoding) {
19+
implementations.forEach(function (createHash) {
20+
algorithms.forEach(function (algorithm) {
21+
test('test ' + algorithm + ' against test vectors', function (t) {
2522
vectors.forEach(function (obj, i) {
26-
var input = Buffer.from(obj.input, 'base64').toString(encoding);
23+
var input = Buffer.from(obj.input, 'base64');
2724
var node = obj[algorithm];
28-
var js = createHash(algorithm).update(input, encoding).digest('hex');
29-
t.equal(js, node, algorithm + '(testVector[' + i + '], ' + encoding + ') == ' + node);
25+
var js = createHash(algorithm).update(input).digest('hex');
26+
t.equal(js, node, algorithm + '(testVector[' + i + ']) == ' + node);
27+
});
28+
29+
encodings.forEach(function (encoding) {
30+
vectors.forEach(function (obj, i) {
31+
var input = Buffer.from(obj.input, 'base64').toString(encoding);
32+
var node = obj[algorithm];
33+
var js = createHash(algorithm).update(input, encoding).digest('hex');
34+
t.equal(js, node, algorithm + '(testVector[' + i + '], ' + encoding + ') == ' + node);
35+
});
36+
});
37+
38+
vectors.forEach(function (obj, i) {
39+
var input = Buffer.from(obj.input, 'base64');
40+
var node = obj[algorithm];
41+
var hash = createHash(algorithm);
42+
hash.end(input);
43+
var js = hash.read().toString('hex');
44+
t.equal(js, node, algorithm + '(testVector[' + i + ']) == ' + node);
3045
});
31-
});
3246

33-
vectors.forEach(function (obj, i) {
34-
var input = Buffer.from(obj.input, 'base64');
35-
var node = obj[algorithm];
36-
var hash = createHash(algorithm);
37-
hash.end(input);
38-
var js = hash.read().toString('hex');
39-
t.equal(js, node, algorithm + '(testVector[' + i + ']) == ' + node);
47+
t.end();
4048
});
49+
});
50+
});
4151

52+
var createHashNode = require('crypto').createHash;
53+
var randomBytes = require('crypto').randomBytes;
54+
55+
function crossTest(createHashTest, createHashBase, algs) {
56+
var data = randomBytes(32);
57+
test('test against base implementation', function (t) {
58+
algs.forEach(function (algorithm) {
59+
var a = createHashTest(algorithm).update(data).digest('hex');
60+
var b;
61+
try {
62+
b = createHashBase(algorithm).update(data).digest('hex');
63+
} catch (err) {}
64+
var label = algorithm + '(' + data.toString('hex') + ')';
65+
if (b) {
66+
t.equal(a, b, label);
67+
} else {
68+
t.skip(label); // Node.js version doesn't support it
69+
}
70+
});
4271
t.end();
4372
});
44-
});
73+
}
74+
75+
var baseHashes = ['sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'ripemd160', 'rmd160'];
76+
var extraHashes = ['sha512-224', 'sha512-256', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512', 'blake2b512', 'blake2s256'];
77+
78+
crossTest(createHashOld, createHashNode, baseHashes);
79+
80+
// Only new version supports additional hashes
81+
if (createHashAuto !== createHashOld) {
82+
crossTest(createHashAuto, createHashNode, baseHashes);
83+
crossTest(createHashAuto, createHashNode, extraHashes);
84+
}

0 commit comments

Comments
 (0)