You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-desktop/libloki/proof-of-work.js

126 lines
4.3 KiB
JavaScript

/* global dcodeIO, crypto, JSBI */
const NONCE_LEN = 8;
// Modify this value for difficulty scaling
const FALLBACK_DIFFICULTY = 10;
const pow = {
// Increment Uint8Array nonce by '_increment' with carrying
incrementNonce(nonce, _increment = 1) {
let idx = NONCE_LEN - 1;
const newNonce = new Uint8Array(nonce);
let increment = _increment;
do {
const sum = newNonce[idx] + increment;
newNonce[idx] = sum % 256;
increment = Math.floor(sum / 256);
idx -= 1;
} while (increment > 0 && idx >= 0);
return newNonce;
},
// Convert a Uint8Array to a base64 string
bufferToBase64(buf) {
function mapFn(ch) {
return String.fromCharCode(ch);
}
const binaryString = Array.prototype.map.call(buf, mapFn).join('');
return dcodeIO.ByteBuffer.btoa(binaryString);
},
// Convert BigInteger to Uint8Array of length NONCE_LEN
bigIntToUint8Array(bigInt) {
const arr = new Uint8Array(NONCE_LEN);
let n;
for (let idx = NONCE_LEN - 1; idx >= 0; idx -= 1) {
n = NONCE_LEN - (idx + 1);
// 256 ** n is the value of one bit in arr[idx], modulus to carry over
// (bigInt / 256**n) % 256;
const denominator = JSBI.exponentiate(JSBI.BigInt('256'), JSBI.BigInt(n));
const fraction = JSBI.divide(bigInt, denominator);
const uint8Val = JSBI.remainder(fraction, JSBI.BigInt(256));
arr[idx] = JSBI.toNumber(uint8Val);
}
return arr;
},
// Compare two Uint8Arrays, return true if arr1 is > arr2
greaterThan(arr1, arr2) {
// Early exit if lengths are not equal. Should never happen
if (arr1.length !== arr2.length) {
return false;
}
for (let i = 0, len = arr1.length; i < len; i += 1) {
if (arr1[i] > arr2[i]) {
return true;
}
if (arr1[i] < arr2[i]) {
return false;
}
}
return false;
},
// Return nonce that hashes together with payload lower than the target
async calcPoW(timestamp, ttl, pubKey, data, _difficulty = null, increment = 1, startNonce = 0) {
const payload = new Uint8Array(
dcodeIO.ByteBuffer.wrap(
timestamp.toString() + ttl.toString() + pubKey + data,
'binary'
).toArrayBuffer()
);
const difficulty = _difficulty || FALLBACK_DIFFICULTY;
const target = pow.calcTarget(ttl, payload.length, difficulty);
let nonce = new Uint8Array(NONCE_LEN);
nonce = pow.incrementNonce(nonce, startNonce); // initial value
let trialValue = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255]);
const initialHash = new Uint8Array(await crypto.subtle.digest('SHA-512', payload));
const innerPayload = new Uint8Array(initialHash.length + NONCE_LEN);
innerPayload.set(initialHash, NONCE_LEN);
let resultHash;
let nextNonce = nonce;
while (pow.greaterThan(trialValue, target)) {
nonce = nextNonce;
nextNonce = pow.incrementNonce(nonce, increment);
innerPayload.set(nonce);
// eslint-disable-next-line no-await-in-loop
resultHash = await crypto.subtle.digest('SHA-512', innerPayload);
trialValue = new Uint8Array(dcodeIO.ByteBuffer.wrap(resultHash, 'hex').toArrayBuffer()).slice(
0,
NONCE_LEN
);
}
return pow.bufferToBase64(nonce);
},
calcTarget(ttl, payloadLen, difficulty = FALLBACK_DIFFICULTY) {
// payloadLength + NONCE_LEN
const totalLen = JSBI.add(JSBI.BigInt(payloadLen), JSBI.BigInt(NONCE_LEN));
// ttl converted to seconds
const ttlSeconds = JSBI.divide(JSBI.BigInt(ttl), JSBI.BigInt(1000));
// ttl * totalLen
const ttlMult = JSBI.multiply(ttlSeconds, JSBI.BigInt(totalLen));
// 2^16 - 1
const two16 = JSBI.subtract(
JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(16)), // 2^16
JSBI.BigInt(1)
);
// ttlMult / two16
const innerFrac = JSBI.divide(ttlMult, two16);
// totalLen + innerFrac
const lenPlusInnerFrac = JSBI.add(totalLen, innerFrac);
// difficulty * lenPlusInnerFrac
const denominator = JSBI.multiply(JSBI.BigInt(difficulty), lenPlusInnerFrac);
// 2^64 - 1
const two64 = JSBI.subtract(
JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(64)), // 2^64
JSBI.BigInt(1)
);
// two64 / denominator
const targetNum = JSBI.divide(two64, denominator);
return pow.bigIntToUint8Array(targetNum);
},
};