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.
		
		
		
		
		
			
		
			
				
	
	
		
			135 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			135 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
const hash = require('js-sha512');
 | 
						|
const bb = require('bytebuffer');
 | 
						|
const { BigInteger } = require('jsbn');
 | 
						|
 | 
						|
module.exports = {
 | 
						|
  calcTarget,
 | 
						|
  incrementNonce,
 | 
						|
  bufferToBase64,
 | 
						|
  bigIntToUint8Array,
 | 
						|
  greaterThan,
 | 
						|
};
 | 
						|
 | 
						|
const NONCE_LEN = 8;
 | 
						|
// Modify this value for difficulty scaling
 | 
						|
const DEV_NONCE_TRIALS = 10;
 | 
						|
const PROD_NONCE_TRIALS = 1000;
 | 
						|
let development = true;
 | 
						|
 | 
						|
// Increment Uint8Array nonce by 1 with carrying
 | 
						|
function incrementNonce(nonce) {
 | 
						|
  let idx = NONCE_LEN - 1;
 | 
						|
  const newNonce = new Uint8Array(nonce);
 | 
						|
  newNonce[idx] += 1;
 | 
						|
  // Nonce will just reset to 0 if all values are 255 causing infinite loop
 | 
						|
  while (newNonce[idx] === 0 && idx > 0) {
 | 
						|
    idx -= 1;
 | 
						|
    newNonce[idx] += 1;
 | 
						|
  }
 | 
						|
  return newNonce;
 | 
						|
}
 | 
						|
 | 
						|
// Convert a Uint8Array to a base64 string
 | 
						|
function bufferToBase64(buf) {
 | 
						|
  function mapFn(ch) {
 | 
						|
    return String.fromCharCode(ch);
 | 
						|
  }
 | 
						|
  const binaryString = Array.prototype.map.call(buf, mapFn).join('');
 | 
						|
  return bb.btoa(binaryString);
 | 
						|
}
 | 
						|
 | 
						|
// Convert BigInteger to Uint8Array of length NONCE_LEN
 | 
						|
function 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 uint8Val = bigInt
 | 
						|
      .divide(new BigInteger('256').pow(n))
 | 
						|
      .mod(new BigInteger('256'));
 | 
						|
    arr[idx] = uint8Val.intValue();
 | 
						|
  }
 | 
						|
  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
 | 
						|
function calcPoW(timestamp, ttl, pubKey, data) {
 | 
						|
  const payload = new Uint8Array(
 | 
						|
    bb.wrap(timestamp.toString() + ttl.toString() + pubKey + data, 'binary').toArrayBuffer()
 | 
						|
  );
 | 
						|
 | 
						|
  const target = calcTarget(ttl, payload.length);
 | 
						|
 | 
						|
  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 innerPayload = new Uint8Array(initialHash.length + NONCE_LEN);
 | 
						|
  innerPayload.set(initialHash, NONCE_LEN);
 | 
						|
  let resultHash;
 | 
						|
  while (greaterThan(trialValue, target)) {
 | 
						|
    nonce = incrementNonce(nonce);
 | 
						|
    innerPayload.set(nonce);
 | 
						|
    resultHash = hash(innerPayload);
 | 
						|
    trialValue = new Uint8Array(
 | 
						|
      bb.wrap(resultHash, 'hex').toArrayBuffer()
 | 
						|
    ).slice(0, NONCE_LEN);
 | 
						|
  }
 | 
						|
  return bufferToBase64(nonce);
 | 
						|
}
 | 
						|
 | 
						|
function calcTarget(ttl, payloadLen) {
 | 
						|
  // payloadLength + NONCE_LEN
 | 
						|
  const totalLen = new BigInteger(payloadLen.toString()).add(
 | 
						|
    new BigInteger(NONCE_LEN.toString())
 | 
						|
  );
 | 
						|
  // ttl * totalLen
 | 
						|
  const ttlMult = new BigInteger(ttl.toString()).multiply(totalLen);
 | 
						|
  // ttlMult / (2^16 - 1)
 | 
						|
  const innerFrac = ttlMult.divide(
 | 
						|
    new BigInteger('2').pow(16).subtract(new BigInteger('1'))
 | 
						|
  );
 | 
						|
  // totalLen + innerFrac
 | 
						|
  const lenPlusInnerFrac = totalLen.add(innerFrac);
 | 
						|
  // nonceTrials * lenPlusInnerFrac
 | 
						|
  const nonceTrials = development ? DEV_NONCE_TRIALS : PROD_NONCE_TRIALS;
 | 
						|
  const denominator = new BigInteger(nonceTrials.toString()).multiply(
 | 
						|
    lenPlusInnerFrac
 | 
						|
  );
 | 
						|
  // 2^64 - 1
 | 
						|
  const two64 = new BigInteger('2').pow(64).subtract(new BigInteger('1'));
 | 
						|
  // two64 / denominator
 | 
						|
  const targetNum = two64.divide(denominator);
 | 
						|
  return bigIntToUint8Array(targetNum);
 | 
						|
}
 | 
						|
 | 
						|
// Start calculation in child process when main process sends message data
 | 
						|
process.on('message', msg => {
 | 
						|
  ({ development } = msg);
 | 
						|
  process.send({
 | 
						|
    nonce: calcPoW(
 | 
						|
      msg.timestamp,
 | 
						|
      msg.ttl,
 | 
						|
      msg.pubKey,
 | 
						|
      msg.data
 | 
						|
    ),
 | 
						|
  });
 | 
						|
});
 |