From c2d3c0d8b287b26ed435c21ee81b45cee28e718a Mon Sep 17 00:00:00 2001 From: Calvin Metcalf Date: Wed, 26 Dec 2018 08:34:59 -0500 Subject: [PATCH 1/6] move things out of closure, switch to non crypto pbkdf2 --- lib/scrypt.js | 223 ++++++++++++++++++++++++-------------------------- package.json | 4 +- 2 files changed, 110 insertions(+), 117 deletions(-) diff --git a/lib/scrypt.js b/lib/scrypt.js index 45a3fe8..bc70c49 100644 --- a/lib/scrypt.js +++ b/lib/scrypt.js @@ -1,4 +1,4 @@ -var crypto = require('crypto') +var pbkdf2 = require('pbkdf2') /* eslint-disable camelcase */ var MAX_VALUE = 0x7fffffff @@ -10,8 +10,8 @@ function scrypt (key, salt, N, r, p, dkLen, progressCallback) { if (N > MAX_VALUE / 128 / r) throw Error('Parameter N is too large') if (r > MAX_VALUE / 128 / p) throw Error('Parameter r is too large') - var XY = new Buffer(256 * r) - var V = new Buffer(128 * r * N) + var XY = Buffer.alloc(256 * r) + var V = Buffer(128 * r * N) // pseudo global var B32 = new Int32Array(16) // salsa20_8 @@ -19,7 +19,7 @@ function scrypt (key, salt, N, r, p, dkLen, progressCallback) { var _X = new Buffer(64) // blockmix_salsa8 // pseudo global - var B = crypto.pbkdf2Sync(key, salt, 1, p * 128 * r, 'sha256') + var B = pbkdf2.pbkdf2Sync(key, salt, 1, p * 128 * r, 'sha256') var tickCallback if (progressCallback) { @@ -41,140 +41,131 @@ function scrypt (key, salt, N, r, p, dkLen, progressCallback) { } for (var i = 0; i < p; i++) { - smix(B, i * 128 * r, r, N, V, XY) + smix(B, i * 128 * r, r, N, V, XY, _X, B32, x, tickCallback) } - return crypto.pbkdf2Sync(key, B, 1, dkLen, 'sha256') - - // all of these functions are actually moved to the top - // due to function hoisting - - function smix (B, Bi, r, N, V, XY) { - var Xi = 0 - var Yi = 128 * r - var i - - B.copy(XY, Xi, Bi, Bi + Yi) + return pbkdf2.pbkdf2Sync(key, B, 1, dkLen, 'sha256') +} - for (i = 0; i < N; i++) { - XY.copy(V, i * Yi, Xi, Xi + Yi) - blockmix_salsa8(XY, Xi, Yi, r) +function R (a, b) { + return (a << b) | (a >>> (32 - b)) +} +function salsa20_8 (B, B32, x) { + var i + + for (i = 0; i < 16; i++) { + B32[i] = (B[i * 4 + 0] & 0xff) << 0 + B32[i] |= (B[i * 4 + 1] & 0xff) << 8 + B32[i] |= (B[i * 4 + 2] & 0xff) << 16 + B32[i] |= (B[i * 4 + 3] & 0xff) << 24 + // B32[i] = B.readUInt32LE(i*4) <--- this is signficantly slower even in Node.js + } - if (tickCallback) tickCallback() - } + arraycopy(B32, 0, x, 0, 16) + + for (i = 8; i > 0; i -= 2) { + x[4] ^= R(x[0] + x[12], 7) + x[8] ^= R(x[4] + x[0], 9) + x[12] ^= R(x[8] + x[4], 13) + x[0] ^= R(x[12] + x[8], 18) + x[9] ^= R(x[5] + x[1], 7) + x[13] ^= R(x[9] + x[5], 9) + x[1] ^= R(x[13] + x[9], 13) + x[5] ^= R(x[1] + x[13], 18) + x[14] ^= R(x[10] + x[6], 7) + x[2] ^= R(x[14] + x[10], 9) + x[6] ^= R(x[2] + x[14], 13) + x[10] ^= R(x[6] + x[2], 18) + x[3] ^= R(x[15] + x[11], 7) + x[7] ^= R(x[3] + x[15], 9) + x[11] ^= R(x[7] + x[3], 13) + x[15] ^= R(x[11] + x[7], 18) + x[1] ^= R(x[0] + x[3], 7) + x[2] ^= R(x[1] + x[0], 9) + x[3] ^= R(x[2] + x[1], 13) + x[0] ^= R(x[3] + x[2], 18) + x[6] ^= R(x[5] + x[4], 7) + x[7] ^= R(x[6] + x[5], 9) + x[4] ^= R(x[7] + x[6], 13) + x[5] ^= R(x[4] + x[7], 18) + x[11] ^= R(x[10] + x[9], 7) + x[8] ^= R(x[11] + x[10], 9) + x[9] ^= R(x[8] + x[11], 13) + x[10] ^= R(x[9] + x[8], 18) + x[12] ^= R(x[15] + x[14], 7) + x[13] ^= R(x[12] + x[15], 9) + x[14] ^= R(x[13] + x[12], 13) + x[15] ^= R(x[14] + x[13], 18) + } - for (i = 0; i < N; i++) { - var offset = Xi + (2 * r - 1) * 64 - var j = XY.readUInt32LE(offset) & (N - 1) - blockxor(V, j * Yi, XY, Xi, Yi) - blockmix_salsa8(XY, Xi, Yi, r) + for (i = 0; i < 16; ++i) B32[i] = x[i] + B32[i] - if (tickCallback) tickCallback() + for (i = 0; i < 16; i++) { + var bi = i * 4 + B[bi + 0] = (B32[i] >> 0 & 0xff) + B[bi + 1] = (B32[i] >> 8 & 0xff) + B[bi + 2] = (B32[i] >> 16 & 0xff) + B[bi + 3] = (B32[i] >> 24 & 0xff) + // B.writeInt32LE(B32[i], i*4) //<--- this is signficantly slower even in Node.js + } +} +function arraycopy (src, srcPos, dest, destPos, length) { + if (Buffer.isBuffer(src) && Buffer.isBuffer(dest)) { + src.copy(dest, destPos, srcPos, srcPos + length) + } else { + while (length--) { + dest[destPos++] = src[srcPos++] } - - XY.copy(B, Bi, Xi, Xi + Yi) } +} +function smix (B, Bi, r, N, V, XY, _X, B32, x, tickCallback) { + var Xi = 0 + var Yi = 128 * r + var i - function blockmix_salsa8 (BY, Bi, Yi, r) { - var i + B.copy(XY, Xi, Bi, Bi + Yi) - arraycopy(BY, Bi + (2 * r - 1) * 64, _X, 0, 64) + for (i = 0; i < N; i++) { + XY.copy(V, i * Yi, Xi, Xi + Yi) + blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x) - for (i = 0; i < 2 * r; i++) { - blockxor(BY, i * 64, _X, 0, 64) - salsa20_8(_X) - arraycopy(_X, 0, BY, Yi + (i * 64), 64) - } + if (tickCallback) tickCallback() + } - for (i = 0; i < r; i++) { - arraycopy(BY, Yi + (i * 2) * 64, BY, Bi + (i * 64), 64) - } + for (i = 0; i < N; i++) { + var offset = Xi + (2 * r - 1) * 64 + var j = XY.readUInt32LE(offset) & (N - 1) + blockxor(V, j * Yi, XY, Xi, Yi) + blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x) - for (i = 0; i < r; i++) { - arraycopy(BY, Yi + (i * 2 + 1) * 64, BY, Bi + (i + r) * 64, 64) - } + if (tickCallback) tickCallback() } - function R (a, b) { - return (a << b) | (a >>> (32 - b)) + XY.copy(B, Bi, Xi, Xi + Yi) +} +// naive approach... going back to loop unrolling may yield additional performance +function blockxor (S, Si, D, Di, len) { + for (var i = 0; i < len; i++) { + D[Di + i] ^= S[Si + i] } +} +function blockmix_salsa8 (BY, Bi, Yi, r, _X, B32, x) { + var i - function salsa20_8 (B) { - var i - - for (i = 0; i < 16; i++) { - B32[i] = (B[i * 4 + 0] & 0xff) << 0 - B32[i] |= (B[i * 4 + 1] & 0xff) << 8 - B32[i] |= (B[i * 4 + 2] & 0xff) << 16 - B32[i] |= (B[i * 4 + 3] & 0xff) << 24 - // B32[i] = B.readUInt32LE(i*4) <--- this is signficantly slower even in Node.js - } - - arraycopy(B32, 0, x, 0, 16) - - for (i = 8; i > 0; i -= 2) { - x[4] ^= R(x[0] + x[12], 7) - x[8] ^= R(x[4] + x[0], 9) - x[12] ^= R(x[8] + x[4], 13) - x[0] ^= R(x[12] + x[8], 18) - x[9] ^= R(x[5] + x[1], 7) - x[13] ^= R(x[9] + x[5], 9) - x[1] ^= R(x[13] + x[9], 13) - x[5] ^= R(x[1] + x[13], 18) - x[14] ^= R(x[10] + x[6], 7) - x[2] ^= R(x[14] + x[10], 9) - x[6] ^= R(x[2] + x[14], 13) - x[10] ^= R(x[6] + x[2], 18) - x[3] ^= R(x[15] + x[11], 7) - x[7] ^= R(x[3] + x[15], 9) - x[11] ^= R(x[7] + x[3], 13) - x[15] ^= R(x[11] + x[7], 18) - x[1] ^= R(x[0] + x[3], 7) - x[2] ^= R(x[1] + x[0], 9) - x[3] ^= R(x[2] + x[1], 13) - x[0] ^= R(x[3] + x[2], 18) - x[6] ^= R(x[5] + x[4], 7) - x[7] ^= R(x[6] + x[5], 9) - x[4] ^= R(x[7] + x[6], 13) - x[5] ^= R(x[4] + x[7], 18) - x[11] ^= R(x[10] + x[9], 7) - x[8] ^= R(x[11] + x[10], 9) - x[9] ^= R(x[8] + x[11], 13) - x[10] ^= R(x[9] + x[8], 18) - x[12] ^= R(x[15] + x[14], 7) - x[13] ^= R(x[12] + x[15], 9) - x[14] ^= R(x[13] + x[12], 13) - x[15] ^= R(x[14] + x[13], 18) - } - - for (i = 0; i < 16; ++i) B32[i] = x[i] + B32[i] + arraycopy(BY, Bi + (2 * r - 1) * 64, _X, 0, 64) - for (i = 0; i < 16; i++) { - var bi = i * 4 - B[bi + 0] = (B32[i] >> 0 & 0xff) - B[bi + 1] = (B32[i] >> 8 & 0xff) - B[bi + 2] = (B32[i] >> 16 & 0xff) - B[bi + 3] = (B32[i] >> 24 & 0xff) - // B.writeInt32LE(B32[i], i*4) //<--- this is signficantly slower even in Node.js - } + for (i = 0; i < 2 * r; i++) { + blockxor(BY, i * 64, _X, 0, 64) + salsa20_8(_X, B32, x) + arraycopy(_X, 0, BY, Yi + (i * 64), 64) } - // naive approach... going back to loop unrolling may yield additional performance - function blockxor (S, Si, D, Di, len) { - for (var i = 0; i < len; i++) { - D[Di + i] ^= S[Si + i] - } + for (i = 0; i < r; i++) { + arraycopy(BY, Yi + (i * 2) * 64, BY, Bi + (i * 64), 64) } -} -function arraycopy (src, srcPos, dest, destPos, length) { - if (Buffer.isBuffer(src) && Buffer.isBuffer(dest)) { - src.copy(dest, destPos, srcPos, srcPos + length) - } else { - while (length--) { - dest[destPos++] = src[srcPos++] - } + for (i = 0; i < r; i++) { + arraycopy(BY, Yi + (i * 2 + 1) * 64, BY, Bi + (i + r) * 64, 64) } } - module.exports = scrypt diff --git a/package.json b/package.json index 8ef097f..b577fc7 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "mochify": "^2.1.0", "standard": "^7.1.1" }, - "dependencies": {}, + "dependencies": { + "pbkdf2": "^3.0.17" + }, "repository": { "url": "git@github.com:cryptocoinjs/scryptsy.git", "type": "git" From 86d0748ac31fbdec1e618ec56ad63d86daca329b Mon Sep 17 00:00:00 2001 From: Calvin Metcalf Date: Wed, 26 Dec 2018 08:38:07 -0500 Subject: [PATCH 2/6] switch to newer buffer stuff and export the smix part --- lib/scrypt.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/scrypt.js b/lib/scrypt.js index bc70c49..af6a3b6 100644 --- a/lib/scrypt.js +++ b/lib/scrypt.js @@ -10,15 +10,13 @@ function scrypt (key, salt, N, r, p, dkLen, progressCallback) { if (N > MAX_VALUE / 128 / r) throw Error('Parameter N is too large') if (r > MAX_VALUE / 128 / p) throw Error('Parameter r is too large') - var XY = Buffer.alloc(256 * r) - var V = Buffer(128 * r * N) + var XY = Buffer.allocUnsafe(256 * r) + var V = Buffer.allocUnsafe(128 * r * N) - // pseudo global var B32 = new Int32Array(16) // salsa20_8 var x = new Int32Array(16) // salsa20_8 - var _X = new Buffer(64) // blockmix_salsa8 + var _X = Buffer.allocUnsafe(64) // blockmix_salsa8 - // pseudo global var B = pbkdf2.pbkdf2Sync(key, salt, 1, p * 128 * r, 'sha256') var tickCallback @@ -169,3 +167,4 @@ function blockmix_salsa8 (BY, Bi, Yi, r, _X, B32, x) { } } module.exports = scrypt +module.exports.smix = smix From dbd9a0d3d3a74becf452367ced7e14056bd5a310 Mon Sep 17 00:00:00 2001 From: Calvin Metcalf Date: Wed, 26 Dec 2018 08:47:54 -0500 Subject: [PATCH 3/6] update versions of dependencies --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index b577fc7..9b8813b 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,11 @@ "author": "", "license": "MIT", "devDependencies": { - "coveralls": "^2.10.0", - "istanbul": "^0.3.5", - "mocha": "^2.2.0", - "mochify": "^2.1.0", - "standard": "^7.1.1" + "coveralls": "^3.0.2", + "istanbul": "^0.4.5", + "mocha": "^5.2.0", + "mochify": "^6.0.4", + "standard": "^12.0.1" }, "dependencies": { "pbkdf2": "^3.0.17" From 680d6fe1ae073c4642a32f849b101c38884359bc Mon Sep 17 00:00:00 2001 From: Calvin Metcalf Date: Wed, 26 Dec 2018 09:38:05 -0500 Subject: [PATCH 4/6] 100% coverage --- test/scrypt.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/test/scrypt.js b/test/scrypt.js index 45b04e6..5458e10 100644 --- a/test/scrypt.js +++ b/test/scrypt.js @@ -22,4 +22,47 @@ describe('scrypt', function () { }) }) }) + describe('progress callback', function () { + var f = fixtures.valid[1] + it('should callback for ' + f.description, function () { + var called = []; + var data = scrypt(f.key, f.salt, f.iterations, f.memory, f.parallel, f.keyLen, function (d) { + called.push(d) + }) + assert.equal(called.length, 32) + assert.deepEqual(called[5], { + current: 6000, + percent: 18.310546875, + total: 32768 + }) + assert.deepEqual(called[31], { + current: 32000, + percent: 97.65625, + total: 32768 + }) + assert.equal(data.toString('hex'), f.result) + }) + }) + describe('handle bad options', function () { + it('rejects N that is too low', function () { + assert.throws(function () { + scrypt('nothing', 'nothing', -1) + }, /N must be > 0 and a power of 2/) + }) + it('rejects N that is not a power of 2', function () { + assert.throws(function () { + scrypt('nothing', 'nothing', 3) + }, /N must be > 0 and a power of 2/) + }) + it('rejects N that too large', function () { + assert.throws(function () { + scrypt('nothing', 'nothing', 0x80000000 * 256, 1) + }, /Parameter N is too large/) + }) + it('rejects r that too large', function () { + assert.throws(function () { + scrypt('nothing', 'nothing', 4, 8, 0x80000000) + }, /Parameter r is too large/) + }) + }) }) From 9aee803bc60aaf3b8cc04f78d911ad923dfcec2a Mon Sep 17 00:00:00 2001 From: Calvin Metcalf Date: Wed, 26 Dec 2018 11:46:13 -0500 Subject: [PATCH 5/6] linting --- test/scrypt.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/scrypt.js b/test/scrypt.js index 5458e10..94a4db2 100644 --- a/test/scrypt.js +++ b/test/scrypt.js @@ -18,29 +18,29 @@ describe('scrypt', function () { it('should compute for ' + f.description, function () { var data = scrypt(f.key, f.salt, f.iterations, f.memory, f.parallel, f.keyLen) - assert.equal(data.toString('hex'), f.result) + assert.strictEqual(data.toString('hex'), f.result) }) }) }) describe('progress callback', function () { var f = fixtures.valid[1] it('should callback for ' + f.description, function () { - var called = []; + var called = [] var data = scrypt(f.key, f.salt, f.iterations, f.memory, f.parallel, f.keyLen, function (d) { called.push(d) }) - assert.equal(called.length, 32) - assert.deepEqual(called[5], { + assert.strictEqual(called.length, 32) + assert.deepStrictEqual(called[5], { current: 6000, percent: 18.310546875, total: 32768 }) - assert.deepEqual(called[31], { + assert.deepStrictEqual(called[31], { current: 32000, percent: 97.65625, total: 32768 }) - assert.equal(data.toString('hex'), f.result) + assert.strictEqual(data.toString('hex'), f.result) }) }) describe('handle bad options', function () { From 57a1f540b238ee9fb96e6bf50fb9a1a209eed523 Mon Sep 17 00:00:00 2001 From: Calvin Metcalf Date: Wed, 26 Dec 2018 11:46:42 -0500 Subject: [PATCH 6/6] update .gitingore to include coverage --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 40b878d..25fbf5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules/ \ No newline at end of file +node_modules/ +coverage/