2018-04-28 13:13:36 +01:00
|
|
|
/* eslint-disable camelcase */
|
|
|
|
let pbkdf2 = require('pbkdf2')
|
|
|
|
|
|
|
|
var MAX_VALUE = 0x7fffffff
|
|
|
|
|
|
|
|
// N = Cpu cost, r = Memory cost, p = parallelization cost
|
|
|
|
async function scrypt (key, salt, N, r, p, dkLen, progressCallback) {
|
|
|
|
if (N === 0 || (N & (N - 1)) !== 0) throw Error('N must be > 0 and a power of 2')
|
|
|
|
|
|
|
|
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')
|
|
|
|
|
2020-04-20 14:43:58 +01:00
|
|
|
var XY = Buffer.alloc(256 * r)
|
|
|
|
var V = Buffer.alloc(128 * r * N)
|
2018-04-28 13:13:36 +01:00
|
|
|
|
|
|
|
// pseudo global
|
|
|
|
var B32 = new Int32Array(16) // salsa20_8
|
|
|
|
var x = new Int32Array(16) // salsa20_8
|
2020-04-20 14:43:58 +01:00
|
|
|
var _X = Buffer.alloc(64) // blockmix_salsa8
|
2018-04-28 13:13:36 +01:00
|
|
|
|
|
|
|
// pseudo global
|
|
|
|
var B = pbkdf2.pbkdf2Sync(key, salt, 1, p * 128 * r, 'sha256')
|
|
|
|
|
|
|
|
var tickCallback
|
|
|
|
if (progressCallback) {
|
|
|
|
var totalOps = p * N * 2
|
|
|
|
var currentOp = 0
|
|
|
|
|
|
|
|
tickCallback = function () {
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
++currentOp
|
|
|
|
|
|
|
|
// send progress notifications once every 1,000 ops
|
|
|
|
if (currentOp % 1000 === 0) {
|
|
|
|
progressCallback({
|
|
|
|
current: currentOp,
|
|
|
|
total: totalOps,
|
|
|
|
percent: (currentOp / totalOps) * 100.0
|
|
|
|
})
|
|
|
|
setTimeout(resolve, 10)
|
|
|
|
} else {
|
|
|
|
resolve()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var i = 0; i < p; i++) {
|
|
|
|
await smix(B, i * 128 * r, r, N, V, XY)
|
|
|
|
if (typeof shold_stop_bip38 !== 'undefined') break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pbkdf2.pbkdf2Sync(key, B, 1, dkLen, 'sha256')
|
|
|
|
|
|
|
|
// all of these functions are actually moved to the top
|
|
|
|
// due to function hoisting
|
|
|
|
|
|
|
|
async function smix (B, Bi, r, N, V, XY) {
|
|
|
|
var Xi = 0
|
|
|
|
var Yi = 128 * r
|
|
|
|
var i
|
|
|
|
|
|
|
|
B.copy(XY, Xi, Bi, Bi + Yi)
|
|
|
|
|
|
|
|
for (i = 0; i < N; i++) {
|
|
|
|
XY.copy(V, i * Yi, Xi, Xi + Yi)
|
|
|
|
blockmix_salsa8(XY, Xi, Yi, r)
|
|
|
|
|
|
|
|
if (tickCallback) {
|
|
|
|
await tickCallback()
|
|
|
|
if (typeof shold_stop_bip38 !== 'undefined') break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
if (tickCallback) {
|
|
|
|
await tickCallback()
|
|
|
|
if (typeof shold_stop_bip38 !== 'undefined') break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
XY.copy(B, Bi, Xi, Xi + Yi)
|
|
|
|
}
|
|
|
|
|
|
|
|
function blockmix_salsa8 (BY, Bi, Yi, r) {
|
|
|
|
var i
|
|
|
|
|
|
|
|
arraycopy(BY, Bi + (2 * r - 1) * 64, _X, 0, 64)
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < r; i++) {
|
|
|
|
arraycopy(BY, Yi + (i * 2) * 64, BY, Bi + (i * 64), 64)
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < r; i++) {
|
|
|
|
arraycopy(BY, Yi + (i * 2 + 1) * 64, BY, Bi + (i + r) * 64, 64)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function R (a, b) {
|
|
|
|
return (a << b) | (a >>> (32 - b))
|
|
|
|
}
|
|
|
|
|
|
|
|
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]
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 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++]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = scrypt
|