Merge pull request #21 from loki-project/bugfix/pow-nonce-calc

Bugfix/pow nonce calc
pull/23/head
sachaaaaa 7 years ago committed by GitHub
commit d91f04930c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -17,10 +17,10 @@ function initialize({ url }) {
function connect() { function connect() {
return { return {
sendMessage sendMessage,
}; };
function getPoWNonce(timestamp, ttl, pub_key, data) { function getPoWNonce(timestamp, ttl, pubKey, data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Create forked node process to calculate PoW without blocking main process // Create forked node process to calculate PoW without blocking main process
const child = fork('./libloki/proof-of-work.js'); const child = fork('./libloki/proof-of-work.js');
@ -29,8 +29,8 @@ function initialize({ url }) {
child.send({ child.send({
timestamp, timestamp,
ttl, ttl,
pub_key, pubKey,
data data: Array.from(data),
}); });
// Handle child process error (should never happen) // Handle child process error (should never happen)
@ -49,14 +49,14 @@ function initialize({ url }) {
}); });
}); });
}; }
async function sendMessage(pub_key, data, ttl) { async function sendMessage(pubKey, data, ttl) {
const timestamp = Math.floor(Date.now() / 1000); const timestamp = Math.floor(Date.now() / 1000);
// Nonce is returned as a base64 string to include in header // Nonce is returned as a base64 string to include in header
let nonce; let nonce;
try { try {
nonce = await getPoWNonce(timestamp, ttl, pub_key, data); nonce = await getPoWNonce(timestamp, ttl, pubKey, data);
} catch(err) { } catch(err) {
// Something went horribly wrong // Something went horribly wrong
// TODO: Handle gracefully // TODO: Handle gracefully
@ -67,7 +67,7 @@ function initialize({ url }) {
url: `${url}/send_message`, url: `${url}/send_message`,
type: 'POST', type: 'POST',
responseType: undefined, responseType: undefined,
timeout: undefined timeout: undefined,
}; };
log.info(options.type, options.url); log.info(options.type, options.url);
@ -79,7 +79,7 @@ function initialize({ url }) {
'X-Loki-pow-nonce': nonce, 'X-Loki-pow-nonce': nonce,
'X-Loki-timestamp': timestamp.toString(), 'X-Loki-timestamp': timestamp.toString(),
'X-Loki-ttl': ttl.toString(), 'X-Loki-ttl': ttl.toString(),
'X-Loki-recipient': pub_key, 'X-Loki-recipient': pubKey,
'Content-Length': data.byteLength, 'Content-Length': data.byteLength,
}, },
timeout: options.timeout, timeout: options.timeout,

@ -1,67 +1,104 @@
const hash = require('js-sha512'); const hash = require('js-sha512');
const bb = require('bytebuffer'); const bb = require('bytebuffer');
const BigInteger = require('jsbn').BigInteger;
const NONCE_LEN = 8;
// Modify this value for difficulty scaling
const NONCE_TRIALS = 1000;
// Increment Uint8Array nonce by 1 with carrying // Increment Uint8Array nonce by 1 with carrying
function incrementNonce(nonce) { function incrementNonce(nonce) {
let idx = nonce.length - 1; let idx = NONCE_LEN - 1;
nonce[idx] += 1; const newNonce = new Uint8Array(nonce);
// Nonce will just reset to 0 if all values are 255 causing infinite loop, should never happen newNonce[idx] += 1;
while (nonce[idx] == 0 && idx >= 0) { // Nonce will just reset to 0 if all values are 255 causing infinite loop
nonce[--idx] += 1; while (newNonce[idx] === 0 && idx > 0) {
idx -= 1;
newNonce[idx] += 1;
} }
return nonce; return newNonce;
} }
// Convert a Uint8Array to a base64 string // Convert a Uint8Array to a base64 string
function bufferToBase64(buf) { function bufferToBase64(buf) {
let binstr = Array.prototype.map.call(buf, function (ch) { function mapFn(ch) {
return String.fromCharCode(ch); return String.fromCharCode(ch);
}).join(''); };
return bb.btoa(binstr); const binaryString = Array.prototype.map.call(buf, mapFn).join('');
return bb.btoa(binaryString);
} }
// Convert javascript number to Uint8Array of length 8 // Convert BigInteger to Uint8Array of length NONCE_LEN
function numberToUintArr(numberVal) { function bigIntToUint8Array(bigInt) {
let arr = new Uint8Array(8); const arr = new Uint8Array(NONCE_LEN);
for (let idx = 7; idx >= 0; idx--) { let n;
let n = 8 - (idx + 1); 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 // 256 ** n is the value of one bit in arr[idx], modulus to carry over
arr[idx] = (numberVal / 256**n) % 256; // (bigInt / 256**n) % 256;
const uint8Val = (bigInt.divide((new BigInteger('256')).pow(n))).mod(new BigInteger('256'));
arr[idx] = uint8Val.intValue();
} }
return arr; return arr;
} }
// Compare two Uint8Arrays, return true if arr1 is > arr2
function 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 // Return nonce that hashes together with payload lower than the target
function calcPoW(timestamp, ttl, pub_key, data) { function calcPoW(timestamp, ttl, pubKey, data) {
const leadingString = timestamp.toString() + ttl.toString() + pub_key; const leadingString = timestamp.toString() + ttl.toString() + pubKey;
const leadingArray = new Uint8Array(bb.wrap(leadingString, 'binary').toArrayBuffer()); const leadingArray = new Uint8Array(bb.wrap(leadingString, 'binary').toArrayBuffer());
// Payload constructed from concatenating timestamp, ttl and pubkey strings, converting to Uint8Array // Payload constructed from concatenating timestamp, ttl and pubkey strings,
// and then appending to the message data array // converting to Uint8Array and then appending to the message data array
const payload = leadingArray + data; const payload = new Uint8Array(leadingArray.length + data.length);
const nonceLen = 8; payload.set(leadingArray);
// Modify this value for difficulty scaling payload.set(data, leadingArray.length);
// TODO: Have more informed reason for setting this to 100
const nonceTrialsPerByte = 1000; // payloadLength + NONCE_LEN
let nonce = new Uint8Array(nonceLen); const totalLen = (new BigInteger(payload.length.toString())).add(new BigInteger(NONCE_LEN.toString()));
let trialValue = numberToUintArr(Number.MAX_SAFE_INTEGER); // ttl * totalLen
// Target is converter to Uint8Array for simple comparison with trialValue const ttlMult = (new BigInteger(ttl.toString())).multiply(totalLen);
const target = numberToUintArr(Math.floor(Math.pow(2, 64) / ( // ttlMult / (2^16 - 1)
nonceTrialsPerByte * ( const innerFrac = ttlMult.divide((new BigInteger('2').pow(16)).subtract(new BigInteger('1')));
payload.length + nonceLen + ( // totalLen + innerFrac
(ttl * ( payload.length + nonceLen )) const lenPlusInnerFrac = totalLen.add(innerFrac);
/ Math.pow(2, 16) // NONCE_TRIALS * lenPlusInnerFrac
) const denominator = (new BigInteger(NONCE_TRIALS.toString())).multiply(lenPlusInnerFrac);
) // 2^64 - 1
))); const two64 = (new BigInteger('2').pow(64)).subtract(new BigInteger('1'));
// two64 / denominator
const targetNum = two64.divide(denominator);
const target = bigIntToUint8Array(targetNum);
let nonce = new Uint8Array(NONCE_LEN);
let trialValue = bigIntToUint8Array(new BigInteger(Number.MAX_SAFE_INTEGER.toString()));
const initialHash = new Uint8Array(bb.wrap(hash(payload), 'hex').toArrayBuffer()); const initialHash = new Uint8Array(bb.wrap(hash(payload), 'hex').toArrayBuffer());
while (trialValue > target) { const innerPayload = new Uint8Array(initialHash.length + NONCE_LEN);
innerPayload.set(initialHash, NONCE_LEN);
let resultHash;
while (greaterThan(trialValue, target)) {
nonce = incrementNonce(nonce); nonce = incrementNonce(nonce);
trialValue = (new Uint8Array(bb.wrap(hash(nonce + initialHash), 'hex').toArrayBuffer())).slice(0, 8); innerPayload.set(nonce);
resultHash = hash(innerPayload);
trialValue = (new Uint8Array(bb.wrap(resultHash, 'hex').toArrayBuffer())).slice(0, NONCE_LEN);
} }
return bufferToBase64(nonce); return bufferToBase64(nonce);
} }
// Start calculation in child process when main process sends message data // Start calculation in child process when main process sends message data
process.on('message', (msg) => { process.on('message', (msg) => {
process.send({nonce: calcPoW(msg.timestamp, msg.ttl, msg.pub_key, msg.data)}); process.send({nonce: calcPoW(msg.timestamp, msg.ttl, msg.pubKey, new Uint8Array(msg.data))});
}); });
Loading…
Cancel
Save