@fioprotocol/fiojs
Version:
FioJS is a Utility SDK for packing, signing and encryption functionality for the FIO blockchain. It is used by the FIO TypeScript SDK
316 lines (267 loc) • 9.13 kB
JavaScript
const ecurve = require('ecurve');
const Point = ecurve.Point;
const secp256k1 = ecurve.getCurveByName('secp256k1');
const BigInteger = require('bigi');
const assert = require('assert');
const hash = require('./hash');
const PublicKey = require('./key_public');
const keyUtils = require('./key_utils');
const createHash = require('create-hash')
const promiseAsync = require('./promise-async')
const G = secp256k1.G
const n = secp256k1.n
module.exports = PrivateKey;
/**
@typedef {string} wif - https://en.bitcoin.it/wiki/Wallet_import_format
@typedef {string} pubkey - FIOKey..
@typedef {ecurve.Point} Point
*/
/**
@param {BigInteger} d
*/
function PrivateKey(d) {
if(typeof d === 'string') {
return PrivateKey.fromString(d)
} else if(Buffer.isBuffer(d)) {
return PrivateKey.fromBuffer(d)
} else if(typeof d === 'object' && BigInteger.isBigInteger(d.d)) {
return PrivateKey(d.d)
}
if(!BigInteger.isBigInteger(d)) {
throw new TypeError('Invalid private key')
}
/** @return {string} private key like PVT_K1_base58privatekey.. */
function toString() {
// todo, use PVT_K1_
// return 'PVT_K1_' + keyUtils.checkEncode(toBuffer(), 'K1')
return toWif()
}
/**
@return {wif}
*/
function toWif() {
var private_key = toBuffer();
// checksum includes the version
private_key = Buffer.concat([new Buffer([0x80]), private_key]);
return keyUtils.checkEncode(private_key, 'sha256x2')
}
let public_key;
/**
@return {Point}
*/
function toPublic() {
if (public_key) {
// cache
// S L O W in the browser
return public_key
}
const Q = secp256k1.G.multiply(d);
return public_key = PublicKey.fromPoint(Q);
}
function toBuffer() {
return d.toBuffer(32);
}
/**
ECIES
@arg {string|Object} pubkey wif, PublicKey object
@return {Buffer} 64 byte shared secret
*/
function getSharedSecret(public_key) {
public_key = PublicKey(public_key)
let KB = public_key.toUncompressed().toBuffer()
let KBP = Point.fromAffine(
secp256k1,
BigInteger.fromBuffer( KB.slice( 1,33 )), // x
BigInteger.fromBuffer( KB.slice( 33,65 )) // y
)
let r = toBuffer()
let P = KBP.multiply(BigInteger.fromBuffer(r))
let S = P.affineX.toBuffer({size: 32})
// SHA512 used in ECIES
return hash.sha512(S)
}
// /** ECIES TODO unit test
// @arg {string|Object} pubkey wif, PublicKey object
// @return {Buffer} 64 byte shared secret
// */
// function getSharedSecret(public_key) {
// public_key = PublicKey(public_key).toUncompressed()
// var P = public_key.Q.multiply( d );
// var S = P.affineX.toBuffer({size: 32});
// // ECIES, adds an extra sha512
// return hash.sha512(S);
// }
/**
@arg {string} name - child key name.
@return {PrivateKey}
@example activePrivate = masterPrivate.getChildKey('owner').getChildKey('active')
@example activePrivate.getChildKey('mycontract').getChildKey('myperm')
*/
function getChildKey(name) {
// console.error('WARNING: getChildKey untested against eosd'); // no eosd impl yet
const index = createHash('sha256').update(toBuffer()).update(name).digest()
return PrivateKey(index)
}
function toHex() {
return toBuffer().toString('hex');
}
return {
d,
toWif,
toString,
toPublic,
toBuffer,
getSharedSecret,
getChildKey
}
}
/** @private */
function parseKey(privateStr) {
assert.equal(typeof privateStr, 'string', 'privateStr')
const match = privateStr.match(/^PVT_([A-Za-z0-9]+)_([A-Za-z0-9]+)$/)
if(match === null) {
// legacy WIF - checksum includes the version
const versionKey = keyUtils.checkDecode(privateStr, 'sha256x2')
const version = versionKey.readUInt8(0);
assert.equal(0x80, version, `Expected version ${0x80}, instead got ${version}`)
const privateKey = PrivateKey.fromBuffer(versionKey.slice(1))
const keyType = 'K1'
const format = 'WIF'
return {privateKey, format, keyType}
}
assert(match.length === 3, 'Expecting private key like: PVT_K1_base58privateKey..')
const [, keyType, keyString] = match
assert.equal(keyType, 'K1', 'K1 private key expected')
const privateKey = PrivateKey.fromBuffer(keyUtils.checkDecode(keyString, keyType))
return {privateKey, format: 'PVT', keyType}
}
PrivateKey.fromHex = function(hex) {
return PrivateKey.fromBuffer(new Buffer(hex, 'hex'));
}
PrivateKey.fromBuffer = function(buf) {
if (!Buffer.isBuffer(buf)) {
throw new Error("Expecting parameter to be a Buffer type");
}
if(buf.length === 33 && buf[32] === 1) {
// remove compression flag
buf = buf.slice(0, -1)
}
if (32 !== buf.length) {
throw new Error(`Expecting 32 bytes, instead got ${buf.length}`);
}
return PrivateKey(BigInteger.fromBuffer(buf));
}
/**
@arg {string} seed - any length string. This is private, the same seed
produces the same private key every time.
@return {PrivateKey}
*/
PrivateKey.fromSeed = function(seed) { // generate_private_key
if (!(typeof seed === 'string')) {
throw new Error('seed must be of type string');
}
return PrivateKey.fromBuffer(hash.sha256(seed));
}
/**
@arg {wif} key
@return {boolean} true if key is in the Wallet Import Format
*/
PrivateKey.isWif = function(text) {
try {
assert(parseKey(text).format === 'WIF')
return true
} catch(e) {
return false
}
}
/**
@arg {wif|Buffer|PrivateKey} key
@return {boolean} true if key is convertable to a private key object.
*/
PrivateKey.isValid = function(key) {
try {
PrivateKey(key)
return true
} catch(e) {
return false
}
}
/** @deprecated */
PrivateKey.fromWif = function(str) {
console.log('PrivateKey.fromWif is deprecated, please use PrivateKey.fromString');
return PrivateKey.fromString(str)
}
/**
@throws {AssertError|Error} parsing key
@arg {string} privateStr Eosio or Wallet Import Format (wif) -- a secret
*/
PrivateKey.fromString = function(privateStr) {
return parseKey(privateStr).privateKey
}
/**
Create a new random private key.
Call initialize() first to run some self-checking code and gather some CPU
entropy.
@arg {number} [cpuEntropyBits = 0] - additional CPU entropy, this already
happens once so it should not be needed again.
@return {Promise<PrivateKey>} - random private key
*/
PrivateKey.randomKey = function(cpuEntropyBits = 0) {
return PrivateKey.initialize().then(() => (
PrivateKey.fromBuffer(keyUtils.random32ByteBuffer({cpuEntropyBits}))
))
}
/**
@return {Promise<PrivateKey>} for testing, does not require initialize().
*/
PrivateKey.unsafeRandomKey = function() {
return Promise.resolve(
PrivateKey.fromBuffer(keyUtils.random32ByteBuffer({safe: false}))
)
}
let initialized = false, unitTested = false
/**
Run self-checking code and gather CPU entropy.
Initialization happens once even if called multiple times.
@return {Promise}
*/
function initialize() {
if(initialized) {
return
}
unitTest()
keyUtils.addEntropy(...keyUtils.cpuEntropy())
assert(keyUtils.entropyCount() >= 128, 'insufficient entropy')
initialized = true
}
PrivateKey.initialize = promiseAsync(initialize)
/**
Unit test basic private and public key functionality.
@throws {AssertError}
*/
function unitTest() {
const pvt = PrivateKey(hash.sha256(''))
const pvtError = 'key comparison test failed on a known private key'
assert.equal(pvt.toWif(), '5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss', pvtError)
assert.equal(pvt.toString(), '5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss', pvtError)
// assert.equal(pvt.toString(), 'PVT_K1_2jH3nnhxhR3zPUcsKaWWZC9ZmZAnKm3GAnFD1xynGJE1Znuvjd', pvtError)
const pub = pvt.toPublic()
const pubError = 'pubkey string comparison test failed on a known public key'
assert.equal(pub.toString(), 'FIO859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2HqhToVM', pubError)
// assert.equal(pub.toString(), 'PUB_K1_859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2Ht7beeX', pubError)
// assert.equal(pub.toStringLegacy(), 'FIO859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2HqhToVM', pubError)
doesNotThrow(() => PrivateKey.fromString(pvt.toWif()), 'converting known wif from string')
doesNotThrow(() => PrivateKey.fromString(pvt.toString()), 'converting known pvt from string')
doesNotThrow(() => PublicKey.fromString(pub.toString()), 'converting known public key from string')
// doesNotThrow(() => PublicKey.fromString(pub.toStringLegacy()), 'converting known public key from string')
unitTested = true
}
/** @private */
const doesNotThrow = (cb, msg) => {
try {
cb()
} catch(error) {
error.message = `${msg} ==> ${error.message}`
throw error
}
}