key.js

const { randomBytes }   = require('crypto')
const createKeccakHash  = require('keccak')
const RLP               = require('rlp')
const secp256k1         = require('secp256k1')

const maxShard    = 4

/** @namespace*/
var key = {


  /**
   * @method
   * @param {number} targetShard Target shard
   * @return {object} PrivateKey and address from shard
   * @example
   * key.spawn(1)
   * // returns
   * // {
   * //   address: '0xcc5d9fb5712c17222686b55cad068ecf1dd4f1f1',
   * //   privateKey: '0x51ec37a9351c96a1886f2d951e7f51f3069d0b4f1373b74fb69f91db76623f63'
   * // }
   */
  spawn : function generateKeypairByShard(targetShard){
    targetShard = targetShard || 1;
    let keypair
    if ( 1 <= targetShard && targetShard <= maxShard ) {
      do{
        keypair = generateKeypair()
      } while (this.shard(keypair.address) != targetShard)
      return keypair
    } else {
      throw "invalid shard"
    }
  },


  /**
   * @method
   * @param {string} address Address whose shard will be calculated
   * @return {number} Shard of address
   * @example
   * key.shard('0xcc5d9fb5712c17222686b55cad068ecf1dd4f1f1')
   * // returns
   * // 1
   */
  shard : function shardOfAddress(address){
    var sum = 0
    var buf = Buffer.from(address.substring(2), 'hex')
    for (const pair of buf.entries()) {if (pair[0] < 18){sum += pair[1]}}
    sum += (buf.readUInt16BE(18) >> 4)
    return (sum % maxShard) + 1
  },


  /**
   * @method
   * @param {string} pri Privatekey whose address will be calculated
   * @return {string} Address of private key
   * @example
   * key.addof('0x51ec37a9351c96a1886f2d951e7f51f3069d0b4f1373b74fb69f91db76623f63')
   * // returns
   * // '0x51ec37a9351c96a1886f2d951e7f51f3069d0b4f1373b74fb69f91db76623f63'
   */
  addof : function addressOf(pri){
      var pub = privateToPublic(pri)
      var add = publicToAddress(pub)
      return add
  },

  /**
   * @method
   * @param {string} add Address to be verified
   * @return {string} Validity of address
   * @example
   * key.valid('0x03bcaf796fe8cffd90d1245667921ab83a3a43e1')
   * // returns
   * // true
   */
  valid : function addressValidity(add){
    if (!(/^0x[0-9a-f]{39}1$/.test(add) || /^0x[0-9A-F]{39}1$/.test(add))) {
      return false;
    }
    return true
  }
}

function privateToPublic(privateKey){
  if (privateKey.length!=66){throw "privatekey string should be of lenth 66"}
  if (privateKey.slice(0,2)!="0x"){throw "privateKey string should start with 0x"}
  const inbuf = Buffer.from(privateKey.slice(2), 'hex');
  if (!secp256k1.privateKeyVerify(inbuf)){throw "invalid privateKey"}
  const oubuf = secp256k1.publicKeyCreate(inbuf, false).slice(1);
  return '0x'+oubuf.toString('hex')
}

function publicToAddress(pub){
  var buf = Buffer.from(pub.slice(2), 'hex');
  var add = "0x" + createKeccakHash('keccak256').update(RLP.encode(buf)).digest().slice(12).toString('hex').replace(/.$/i,"1")
  return add;
}

function generateKeypair(){
    let privKey
    do { privKey = randomBytes(32) } while (!secp256k1.privateKeyVerify(privKey))

    // get the public key in a compressed format
    let pubKey = secp256k1.publicKeyCreate(privKey)
    pubKey = secp256k1.publicKeyConvert(pubKey, false).slice(1)

    // Only take the lower 160bits of the hash
    let address = createKeccakHash('keccak256').update(RLP.encode(pubKey)).digest().slice(-20)
    address[19] = address[19]&0xF0|1

    return {
        "address" : "0x" + address.toString('hex'),
        "privateKey" : "0x" + privKey.toString('hex'),
    }
}

module.exports = key