react-native-scryptsy
Version:
Pure JavaScript implementation of the scrypt key deriviation function that is fully compatible with Node.js and the browser.
230 lines (192 loc) • 5.53 kB
JavaScript
const crypto = require("./crypto");
const MAX_VALUE = 0x7fffffff;
const DEFAULT_PROMISE_INTERVAL = 5000;
/* eslint-disable camelcase */
function checkAndInit(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");
let XY = Buffer.alloc(256 * r);
let V = Buffer.alloc(128 * r * N);
// pseudo global
let B32 = new Int32Array(16); // salsa20_8
let x = new Int32Array(16); // salsa20_8
let _X = Buffer.alloc(64); // blockmix_salsa8
// pseudo global
let B = crypto.pbkdf2Sync(key, salt, 1, p * 128 * r, "sha256");
let tickCallback;
if (progressCallback) {
let totalOps = p * N * 2;
let currentOp = 0;
tickCallback = function () {
++currentOp;
// send progress notifications once every 1,000 ops
if (currentOp % 1000 === 0) {
progressCallback({
current: currentOp,
total: totalOps,
percent: (currentOp / totalOps) * 100.0,
});
}
};
}
return {
XY,
V,
B32,
x,
_X,
B,
tickCallback,
};
}
async function smix(
B,
Bi,
r,
N,
V,
XY,
_X,
B32,
x,
tickCallback,
promiseInterval
) {
promiseInterval = promiseInterval || DEFAULT_PROMISE_INTERVAL;
let Xi = 0;
let Yi = 128 * r;
let i;
B.copy(XY, Xi, Bi, Bi + Yi);
for (i = 0; i < N; i++) {
XY.copy(V, i * Yi, Xi, Xi + Yi);
if (i % promiseInterval === 0) {
await new Promise((resolve) => setImmediate(resolve));
}
blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x);
if (tickCallback) tickCallback();
}
for (i = 0; i < N; i++) {
let offset = Xi + (2 * r - 1) * 64;
let j = XY.readUInt32LE(offset) & (N - 1);
blockxor(V, j * Yi, XY, Xi, Yi);
if (i % promiseInterval === 0) {
await new Promise((resolve) => setImmediate(resolve));
}
blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x);
if (tickCallback) tickCallback();
}
XY.copy(B, Bi, Xi, Xi + Yi);
}
function smixSync(B, Bi, r, N, V, XY, _X, B32, x, tickCallback) {
let Xi = 0;
let Yi = 128 * r;
let 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, _X, B32, x);
if (tickCallback) tickCallback();
}
for (i = 0; i < N; i++) {
let offset = Xi + (2 * r - 1) * 64;
let j = XY.readUInt32LE(offset) & (N - 1);
blockxor(V, j * Yi, XY, Xi, Yi);
blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x);
if (tickCallback) tickCallback();
}
XY.copy(B, Bi, Xi, Xi + Yi);
}
function blockmix_salsa8(BY, Bi, Yi, r, _X, B32, x) {
let 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, B32, 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, B32, x) {
let 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++) {
let 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 (let 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 = {
checkAndInit,
smix,
smixSync,
};