edge-core-js
Version:
Edge account & wallet management library
171 lines (140 loc) • 4.18 kB
JavaScript
import { combinePixies, stopUpdates, } from 'redux-pixies'
import { base16 } from 'rfc4648'
import { utf8 } from '../../util/encoding'
/**
* Prevents a function from running in parallel.
* The currently-running operation must finish before the new one starts.
*/
function serialize(
f
) {
let lastTask = Promise.resolve()
return function serialize( ...args) {
const onDone = () => f.apply(this, args)
const out = lastTask.then(onDone, onDone)
lastTask = out
return out
}
}
export function calcSnrpForTarget(
salt,
benchMs,
targetMs
) {
const snrp = {
salt_hex: salt,
n: 16384,
r: 8,
p: 1
}
if (benchMs === 0) {
snrp.n = 131072
snrp.r = 8
snrp.p = 64
return snrp
}
let timeUsed = benchMs // Estimated time in ms the current setting will take on current device
//
// Add additional r value first. This increases memory usage
// Each additional increment of 'r' is approximately a linear increase in time.
//
const STARTING_R = 8
const MAX_R = 8
const REMAINING_R = MAX_R - STARTING_R
const perRValue = benchMs / STARTING_R // The amount of ms delay each increment of 'r' creates
let addR = (targetMs - timeUsed) / perRValue
addR = addR > 0 ? addR : 0
if (addR > REMAINING_R) {
addR = REMAINING_R
}
addR = Math.floor(addR)
snrp.r = STARTING_R + addR
timeUsed += addR * perRValue
//
// Add additional N value in powers of 2. Each power of 2 doubles the amount of time it takes
// to calculate the hash
//
let nPow = 14 // 2^14 = 16384 which is the minimum safe N value
// Iteratively calculate the amount of additional N values we can add
// Max out at N = 17
let addN = (targetMs - timeUsed) / timeUsed
addN = addN > 0 ? addN : 0
if (addN > 3) {
addN = 3
}
addN = Math.floor(addN)
nPow += addN >= 0 ? addN : 0
timeUsed += addN * timeUsed
snrp.n = Math.pow(2, nPow)
//
// Add additional p value which increases parallelization factor
// Max out at p = 64
//
let addP = (targetMs - timeUsed) / timeUsed
addP = addP > 0 ? addP : 0
if (addP > 64) {
addP = 64
}
addP = Math.floor(addP)
snrp.p = addP >= 1 ? addP : 1
timeUsed += addP * timeUsed
return snrp
}
export const scrypt = combinePixies({
makeSnrp: (input) => () => {
const { io, log } = input.props
let benchmark
async function makeSnrp(targetMs) {
// Run the benchmark if needed:
if (benchmark == null) {
benchmark = input.props.output.scrypt
.timeScrypt(utf8.parse('1reallyJunkiePasswordToCheck'), {
salt_hex: base16.parse(
'b5865ffb9fa7b3bfe4b2384d47ce831ee22a4a9d5c34c7ef7d21467cc758f81b'
),
n: 16384,
r: 8,
p: 1
})
.then(result => result.time)
}
// Calculate an SNRP value:
const benchMs = await benchmark
const snrp = calcSnrpForTarget(io.random(32), benchMs, targetMs)
log(
`snrp for ${targetMs}ms target: ${snrp.n} ${snrp.r} ${snrp.p} based on ${benchMs}ms benchmark`
)
return snrp
}
input.onOutput(makeSnrp)
return stopUpdates
},
timeScrypt: (input) => () => {
const { io, log } = input.props
// Find the best timer on this platform:
const getTime =
typeof window !== 'undefined' &&
window.performance != null &&
typeof window.performance.now === 'function'
? () => window.performance.now()
: () => Date.now()
// Performs an scrypt calculation, recording the elapsed time:
function timeScrypt(
data,
snrp,
dklen = 32
) {
const salt = snrp.salt_hex
const startTime = getTime()
log(`starting scrypt n=${snrp.n} r=${snrp.r} p=${snrp.p}`)
return io.scrypt(data, salt, snrp.n, snrp.r, snrp.p, dklen).then(hash => {
const time = getTime() - startTime
log(`finished scrypt n=${snrp.n} r=${snrp.r} p=${snrp.p} in ${time}ms`)
return { hash, time }
})
}
// We only allow one scrypt calculation to occur at once:
input.onOutput(serialize(timeScrypt))
return stopUpdates
}
})