laksa-core-crypto
Version:
crypto libraries for laksa
1,119 lines (933 loc) • 31.7 kB
JavaScript
/**
* This source code is being disclosed to you solely for the purpose of your participation in
* testing Zilliqa and Laksa. You may view, compile and run the code for that purpose and pursuant to
* the protocols and algorithms that are programmed into, and intended by, the code. You may
* not do anything else with the code without express permission from Zilliqa Research Pte. Ltd.,
* including modifying or publishing the code (or any part of it), and developing or forming
* another public or private blockchain network. This source code is provided ‘as is’ and no
* warranties are given as to title or non-infringement, merchantability or fitness for purpose
* and, to the extent permitted by law, all liability for your use of the code is disclaimed.
* Some programs in this code are governed by the GNU General Public License v3.0 (available at
* https://www.gnu.org/licenses/gpl-3.0.en.html) (‘GPLv3’). The programs that are governed by
* GPLv3.0 are those programs that are located in the folders src/depends and tests/depends
* and which include a reference to GPLv3 in their program files.
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('bn.js'), require('laksa-utils'), require('elliptic'), require('hash.js'), require('@zilliqa-js/proto'), require('hmac-drbg')) :
typeof define === 'function' && define.amd ? define(['exports', 'bn.js', 'laksa-utils', 'elliptic', 'hash.js', '@zilliqa-js/proto', 'hmac-drbg'], factory) :
(factory((global.Laksa = {}),global.BN,global.laksaUtils,global.elliptic,global.hashjs,global.proto,global.DRBG));
}(this, (function (exports,BN,laksaUtils,elliptic,hashjs,proto,DRBG) { 'use strict';
BN = BN && BN.hasOwnProperty('default') ? BN['default'] : BN;
elliptic = elliptic && elliptic.hasOwnProperty('default') ? elliptic['default'] : elliptic;
hashjs = hashjs && hashjs.hasOwnProperty('default') ? hashjs['default'] : hashjs;
DRBG = DRBG && DRBG.hasOwnProperty('default') ? DRBG['default'] : DRBG;
/**
* @function randomBytes
* @description Uses JS-native CSPRNG to generate a specified number of bytes.
* NOTE: this method throws if no PRNG is available.
* @param {Number} bytes bytes number to generate
* @returns {String} ramdom hex string
*/
const randomBytes = bytes => {
let randBz;
if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {
randBz = window.crypto.getRandomValues(new Uint8Array(bytes));
} else if (typeof require !== 'undefined') {
randBz = require('crypto').randomBytes(bytes);
} else {
throw new Error('Unable to generate safe random numbers.');
}
let randStr = '';
for (let i = 0; i < bytes; i += 1) {
randStr += `00${randBz[i].toString(16)}`.slice(-2);
}
return randStr;
};
/**
* @class Signature
*
* @description This replaces `elliptic/lib/elliptic/ec/signature`. This is to avoid
* duplicate code in the final bundle, caused by having to bundle elliptic
* twice due to its circular dependencies. This can be removed once
* https://github.com/indutny/elliptic/pull/157 is resolved, or we find the
* time to fork an optimised version of the library.
*/
class Signature {
constructor(options) {
/**
* @var {BN} r
* @memberof Signature.prototype
*/
this.r = typeof options.r === 'string' ? new BN(options.r, 16) : options.r;
/**
* @var {BN} s
* @memberof Signature.prototype
*/
this.s = typeof options.s === 'string' ? new BN(options.s, 16) : options.s;
}
}
/**
* @function intToHexArray
* @description transform a int to hex array
* @param {Number} int - the number to be converted to hex
* @param {Number} size - the desired width of the hex value. will pad.
* @return {Array<String>} the hex array result
*/
const intToHexArray = (int, size) => {
const hex = [];
const hexRep = [];
const hexVal = int.toString(16); // TODO: this really needs to be refactored.
for (let i = 0; i < hexVal.length; i += 1) {
hexRep[i] = hexVal[i].toString();
}
for (let i = 0; i < size - hexVal.length; i += 1) {
hex.push('0');
}
for (let i = 0; i < hexVal.length; i += 1) {
hex.push(hexRep[i]);
}
return hex;
};
/**
* @function intToByteArray
* @description Converts a number to Uint8Array
* @param {Number} num - input number
* @param {Number} size - size of bytes array
* @returns {Uint8Array} Byte Array result
*/
const intToByteArray = (num, size) => {
let x = num;
const res = [];
while (x > 0) {
res.push(x & 255);
x >>= 8;
}
const pad = size - res.length;
for (let i = 0; i < pad; i += 1) {
res.unshift(0);
}
return Uint8Array.from(res);
};
/**
* @function hexToByteArray
* @description Convers a hex string to a Uint8Array
* @param {string} hex - hex string to convert
* @return {Uint8Array} the ByteArray result
*/
const hexToByteArray = hex => {
const res = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
res[i / 2] = parseInt(hex.substring(i, i + 2), 16);
}
return res;
};
/**
* @function hexToIntArray
* @description convert a hex string to int array
* @param {string} hex - hex string to convert
* @return {Array<Number>} the int array
*/
const hexToIntArray = hex => {
if (!hex || !isHex(hex)) {
return [];
}
const res = [];
for (let i = 0; i < hex.length; i += 1) {
const c = hex.charCodeAt(i);
const hi = c >> 8;
const lo = c & 0xff;
if (hi) {
res.push(hi, lo);
}
res.push(lo);
}
return res;
};
/**
* @function compareBytes
* @description A constant time HMAC comparison function.
* @param {String} a - hex string
* @param {String} b - hex string
* @return {Boolean} test result
*/
const isEqual = (a, b) => {
const bzA = hexToIntArray(a);
const bzB = hexToIntArray(b);
if (bzA.length !== bzB.length) {
return false;
}
let result = 0;
for (let i = 0; i < bzA.length; i += 1) {
result |= bzA[i] ^ bzB[i];
}
return result === 0;
};
/**
* @function isHex
* @description test string if it is hex string
* @param {String} str - string to be tested
* @return {Boolean} test result
*/
const isHex = str => {
const plain = str.replace('0x', '');
return /[0-9a-f]*$/i.test(plain);
};
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
/* eslint-disable no-param-reassign */
const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
const polymod = values => {
let chk = 1; // tslint:disable-next-line
for (let p = 0; p < values.length; p += 1) {
const top = chk >> 25;
chk = (chk & 0x1ffffff) << 5 ^ values[p];
for (let i = 0; i < 5; i += 1) {
if (top >> i & 1) {
chk ^= GENERATOR[i];
}
}
}
return chk;
};
const hrpExpand = hrp => {
const ret = [];
let p;
for (p = 0; p < hrp.length; p += 1) {
ret.push(hrp.charCodeAt(p) >> 5);
}
ret.push(0);
for (p = 0; p < hrp.length; p += 1) {
ret.push(hrp.charCodeAt(p) & 31);
}
return Buffer.from(ret);
};
function verifyChecksum(hrp, data) {
return polymod(Buffer.concat([hrpExpand(hrp), data])) === 1;
}
function createChecksum(hrp, data) {
const values = Buffer.concat([Buffer.from(hrpExpand(hrp)), data, Buffer.from([0, 0, 0, 0, 0, 0])]); // var values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
const mod = polymod(values) ^ 1;
const ret = [];
for (let p = 0; p < 6; p += 1) {
ret.push(mod >> 5 * (5 - p) & 31);
}
return Buffer.from(ret);
}
const encode = (hrp, data) => {
const combined = Buffer.concat([data, createChecksum(hrp, data)]);
let ret = `${hrp}1`; // tslint:disable-next-line
for (let p = 0; p < combined.length; p += 1) {
ret += CHARSET.charAt(combined[p]);
}
return ret;
};
const decode = bechString => {
let p;
let hasLower = false;
let hasUpper = false;
for (p = 0; p < bechString.length; p += 1) {
if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) {
return null;
}
if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) {
hasLower = true;
}
if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) {
hasUpper = true;
}
}
if (hasLower && hasUpper) {
return null;
}
bechString = bechString.toLowerCase();
const pos = bechString.lastIndexOf('1');
if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) {
return null;
}
const hrp = bechString.substring(0, pos);
const data = [];
for (p = pos + 1; p < bechString.length; p += 1) {
const d = CHARSET.indexOf(bechString.charAt(p));
if (d === -1) {
return null;
}
data.push(d);
}
if (!verifyChecksum(hrp, Buffer.from(data))) {
return null;
}
return {
hrp,
data: Buffer.from(data.slice(0, data.length - 6))
};
}; // HRP is the human-readable part of zilliqa bech32 addresses
const HRP = 'zil';
/**
* convertBits
*
* groups buffers of a certain width to buffers of the desired width.
*
* For example, converts byte buffers to buffers of maximum 5 bit numbers,
* padding those numbers as necessary. Necessary for encoding Ethereum-style
* addresses as bech32 ones.
*
* @param {Buffer} data
* @param {number} fromWidth
* @param {number} toWidth
* @param {boolean} pad
* @returns {Buffer|null}
*/
const convertBits = (data, fromWidth, toWidth, pad = true) => {
let acc = 0;
let bits = 0;
const ret = [];
const maxv = (1 << toWidth) - 1; // tslint:disable-next-line
for (let p = 0; p < data.length; p += 1) {
const value = data[p];
if (value < 0 || value >> fromWidth !== 0) {
return null;
}
acc = acc << fromWidth | value;
bits += fromWidth;
while (bits >= toWidth) {
bits -= toWidth;
ret.push(acc >> bits & maxv);
}
}
if (pad) {
if (bits > 0) {
ret.push(acc << toWidth - bits & maxv);
}
} else if (bits >= fromWidth || acc << toWidth - bits & maxv) {
return null;
}
return Buffer.from(ret);
};
/**
* toBech32Address
*
* Encodes a canonical 20-byte Ethereum-style address as a bech32 zilliqa
* address.
*
* The expected format is zil1<address><checksum> where address and checksum
* are the result of bech32 encoding a Buffer containing the address bytes.
*
* @param {string} 20 byte canonical address
* @returns {string} 38 char bech32 encoded zilliqa address
*/
const toBech32Address = address => {
if (!laksaUtils.isAddress(address)) {
throw new Error('Invalid address format.');
}
const addrBz = convertBits(Buffer.from(address.replace('0x', ''), 'hex'), 8, 5);
if (addrBz === null) {
throw new Error('Could not convert byte Buffer to 5-bit Buffer');
}
return encode(HRP, addrBz);
};
/**
* fromBech32Address
*
* @param {string} address - a valid Zilliqa bech32 address
* @returns {string} a canonical 20-byte Ethereum-style address
*/
const fromBech32Address = address => {
const res = decode(address);
if (res === null) {
throw new Error('Invalid bech32 address');
}
const {
hrp,
data
} = res;
if (hrp !== HRP) {
throw new Error(`Expected hrp to be ${HRP} but got ${hrp}`);
}
const buf = convertBits(data, 5, 8, false);
if (buf === null) {
throw new Error('Could not convert buffer to bytes');
}
return toChecksumAddress(buf.toString('hex'));
};
const AddressType = Object.freeze({
bytes20: 'bytes20',
bytes20Hex: 'bytes20Hex',
checkSum: 'checkSum',
base58: 'base58',
bech32: 'bech32'
});
class ZilAddress {
constructor(raw) {
_defineProperty(this, "addressType", void 0);
_defineProperty(this, "bytes20", void 0);
_defineProperty(this, "checkSum", void 0);
_defineProperty(this, "bech32", void 0);
_defineProperty(this, "base58", void 0);
this.raw = raw;
this.getAddressType();
}
getAddressType() {
const addrBool = laksaUtils.isAddress(this.raw);
const base58Bool = laksaUtils.isBase58(this.raw);
const bech32Bool = laksaUtils.isBech32(this.raw);
const checksumBool = isValidChecksumAddress(this.raw);
if (addrBool === true && checksumBool === false) {
this.addressType = AddressType.bytes20;
this.bytes20 = this.raw.startsWith('0x') ? this.raw.substring(2) : this.raw;
this.normalize();
} else if (addrBool === true && checksumBool === true) {
this.addressType = AddressType.checkSum;
this.bytes20 = this.raw.toLowerCase().substring(2);
this.normalize();
} else if (bech32Bool === true && laksaUtils.isAddress(fromBech32Address(this.raw))) {
this.addressType = AddressType.bech32;
const decoded = fromBech32Address(this.raw).toLowerCase();
this.bytes20 = decoded.startsWith('0x') ? decoded.substring(2) : decoded;
this.normalize();
} else if (base58Bool === true && laksaUtils.isAddress(decodeBase58(this.raw))) {
this.addressType = AddressType.base58;
const decoded = decodeBase58(this.raw).toLowerCase();
this.bytes20 = decoded.startsWith('0x') ? decoded.substring(2) : decoded;
this.normalize();
} else {
throw new Error('unknown address');
}
}
normalize() {
this.bytes20Hex = `0x${this.bytes20}`;
this.checkSum = toChecksumAddress(this.bytes20);
this.base58 = encodeBase58(this.checkSum);
this.bech32 = toBech32Address(this.checkSum);
}
}
const secp256k1 = elliptic.ec('secp256k1');
/**
* @function getAddressFromPrivateKey
*
* @description takes a hex-encoded string (private key) and return its corresponding
* 20-byte hex-encoded address.
* @param {String} Key
* @return {String}
*/
const getAddressFromPrivateKey = privateKey => {
const keyPair = secp256k1.keyFromPrivate(privateKey, 'hex');
const pub = keyPair.getPublic(true, 'hex');
return hashjs.sha256().update(pub, 'hex').digest('hex').slice(24);
};
/**
* @function getPubKeyFromPrivateKey
* @description takes a hex-encoded string (private key) and return its corresponding
* hex-encoded 33-byte public key.
*
* @param {String} privateKey
* @return {String}
*/
const getPubKeyFromPrivateKey = privateKey => {
const keyPair = secp256k1.keyFromPrivate(privateKey, 'hex');
return keyPair.getPublic(true, 'hex');
};
/**
* @function compressPublicKey
* @description comporess public key
* @param {String} publicKey - 65-byte public key, a point (x, y)
* @return {String}
*/
const compressPublicKey = publicKey => {
return secp256k1.keyFromPublic(publicKey, 'hex').getPublic(true, 'hex');
};
/**
* @function getAddressFromPublicKey
*
* @description takes hex-encoded string and return the corresponding address
* @param {String} pubKey
* @return {String}
*/
const getAddressFromPublicKey = pubKey => {
return hashjs.sha256().update(pubKey, 'hex').digest('hex').slice(24);
};
/**
* @function verifyPrivateKey
* @description verify private key
* @param {String|Buffer} privateKey
* @return {Boolean}
*/
const verifyPrivateKey = privateKey => {
const keyPair = secp256k1.keyFromPrivate(privateKey, 'hex');
const {
result
} = keyPair.validate();
return result;
};
/**
* @function toChecksumAddress
* @description convert address to checksum
* @param {String} address - address string
* @return {String} checksumed address
*/
const toChecksumAddress = address => {
const newAddress = address.toLowerCase().replace('0x', '');
const hash = hashjs.sha256().update(newAddress, 'hex').digest('hex');
const v = new BN(hash, 'hex', 'be');
let ret = '0x';
for (let i = 0; i < newAddress.length; i += 1) {
if ('0123456789'.indexOf(newAddress[i]) !== -1) {
ret += newAddress[i];
} else {
ret += v.and(new BN(2).pow(new BN(255 - 6 * i))).gte(new BN(1)) ? newAddress[i].toUpperCase() : newAddress[i].toLowerCase();
}
}
return ret;
};
/**
* @function isValidChecksumAddress
*
* @description takes hex-encoded string and return boolean if address is checksumed
* @param {String} address
* @return {Boolean}
*/
const isValidChecksumAddress = address => {
const replacedAddress = address.replace('0x', '');
return !!replacedAddress.match(/^[0-9a-fA-F]{40}$/) && toChecksumAddress(address) === address;
};
/**
* @function encodeTransaction
* @description encode transaction to protobuff standard
* @param {Transaction|any} tx - transaction object or Transaction instance
* @return {Buffer}
*/
const encodeTransactionProto = tx => {
const msg = {
version: tx.version,
nonce: tx.nonce || 0,
toaddr: hexToByteArray(tx.toAddr.replace('0x', '').toLowerCase()),
senderpubkey: proto.ZilliqaMessage.ByteArray.create({
data: hexToByteArray(tx.pubKey || '00')
}),
amount: proto.ZilliqaMessage.ByteArray.create({
data: Uint8Array.from(tx.amount.toArrayLike(Buffer, undefined, 16))
}),
gasprice: proto.ZilliqaMessage.ByteArray.create({
data: Uint8Array.from(tx.gasPrice.toArrayLike(Buffer, undefined, 16))
}),
gaslimit: tx.gasLimit,
code: tx.code && tx.code.length ? Uint8Array.from([...tx.code].map(c => c.charCodeAt(0))) : null,
data: tx.data && tx.data.length ? Uint8Array.from([...tx.data].map(c => c.charCodeAt(0))) : null
};
const serialised = proto.ZilliqaMessage.ProtoTransactionCoreInfo.create(msg);
return Buffer.from(proto.ZilliqaMessage.ProtoTransactionCoreInfo.encode(serialised).finish());
};
/**
* @function getAddressForContract
* @param {Object} param
* @param {Number} param.currentNonce - current nonce number
* @param {String} param.address - deployer's address
* @return {String} Contract address
*/
const getAddressForContract = ({
currentNonce,
address
}) => {
// always subtract 1 from the tx nonce, as contract addresses are computed
// based on the nonce in the global state.
const nonce = currentNonce ? currentNonce - 1 : 0;
return hashjs.sha256().update(address, 'hex').update(intToHexArray(nonce, 64).join(''), 'hex').digest('hex').slice(24);
};
/**
* @function checkValidSignature
* @description verify if signature is length===128
* @param {Signature} sig - Signature
* @return {Boolean}
*/
const checkValidSignature = sig => {
return sig.r.toString('hex').length + sig.s.toString('hex').length === 128;
};
const encodeBase58 = hex => {
const clean = hex.toLowerCase().replace('0x', '');
const tbl = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
const base = new BN(58);
const zero = new BN(0);
let x = new BN(clean, 16);
let res = '';
while (x.gt(zero)) {
const rem = x.mod(base).toNumber(); // safe, always < 58
// big endian
res = tbl[rem] + res; // quotient, remainders thrown away in integer division
x = x.div(base);
} // convert to big endian in case the input hex is little endian
const hexBE = x.toString('hex', clean.length);
for (let i = 0; i < hexBE.length; i += 2) {
if (hex[i] === '0' && hex[i + 1] === '0') {
res = tbl[0] + res;
} else {
break;
}
}
return res;
};
const decodeBase58 = raw => {
const tbl = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
const base = new BN(58);
const zero = new BN(0);
let isBreak = false;
let n = new BN(0);
let leader = '';
for (let i = 0; i < raw.length; i += 1) {
const char = raw.charAt(i);
const weight = new BN(tbl.indexOf(char));
n = n.mul(base).add(weight); // check if padding required
if (!isBreak) {
if (i - 1 > 0 && raw[i - 1] !== '1') {
isBreak = true; // eslint-disable-next-line no-continue
continue;
}
if (char === '1') {
leader += '00';
}
}
}
if (n.eq(zero)) {
return leader;
}
let res = leader + n.toString('hex');
if (res.length % 2 !== 0) {
res = `0${res}`;
}
return res;
};
const getAddress = (address, fromType, toType) => {
if (!laksaUtils.isString(address)) {
throw new Error(`${address} is not string`);
}
const zilAddr = new ZilAddress(address);
const validateType = fromType === undefined || fromType.length === 0 ? [] : fromType;
let total = 0;
total = validateType.length > 0 ? validateType.map(type => {
const value = zilAddr.addressType === type ? 1 : 0;
return value;
}).reduce((pre, cur) => {
return pre + cur;
}) : 0;
if (total === 0 && validateType.length > 0) {
throw new Error('Address format is invalid');
}
switch (toType) {
case AddressType.bytes20:
{
if (!zilAddr.bytes20) {
throw new Error(`can not convert to ${toType}`);
} else {
return zilAddr.bytes20;
}
}
case AddressType.bytes20Hex:
{
if (!zilAddr.bytes20Hex) {
throw new Error(`can not convert to ${toType}`);
} else {
return zilAddr.bytes20Hex;
}
}
case AddressType.base58:
{
if (!zilAddr.base58) {
throw new Error(`can not convert to ${toType}`);
} else {
return zilAddr.base58;
}
}
case AddressType.bech32:
{
if (!zilAddr.bech32) {
throw new Error(`can not convert to ${toType}`);
} else {
return zilAddr.bech32;
}
}
case AddressType.checkSum:
{
if (!zilAddr.checkSum) {
throw new Error(`can not convert to ${toType}`);
} else {
return zilAddr.checkSum;
}
}
default:
return zilAddr.raw;
}
};
const secp256k1$1 = elliptic.ec('secp256k1');
const {
curve
} = secp256k1$1;
const PRIVKEY_SIZE_BYTES = 32; // Public key is a point (x, y) on the curve.
// Each coordinate requires 32 bytes.
// In its compressed form it suffices to store the x co-ordinate
// and the sign for y.
// Hence a total of 33 bytes.
const PUBKEY_COMPRESSED_SIZE_BYTES = 33; // Personalization string used for HMAC-DRBG instantiation.
const ALG = Buffer.from('Schnorr+SHA256 ', 'ascii'); // The length in bytes of the string above.
const ALG_LEN = 16; // The length in bytes of entropy inputs to HMAC-DRBG
const ENT_LEN = 32;
const HEX_ENC = 'hex';
/**
* @function generatePrivateKey
* @description generate a private key
* @return {String} the hex-encoded private key
*/
const generatePrivateKey = () => {
return secp256k1$1.genKeyPair({
entropy: randomBytes(secp256k1$1.curve.n.byteLength()),
entropyEnc: HEX_ENC,
pers: 'zilliqajs+secp256k1+SHA256'
}).getPrivate().toString(16, PRIVKEY_SIZE_BYTES * 2);
};
/**
* @function hash
* @description hash message Hash (r | M).
* @param {Buffer} q
* @param {Buffer} msg
* @param {BN} r
* @return {Buffer}
*/
const hash = (q, pubkey, msg) => {
const sha256 = hashjs.sha256();
const pubSize = PUBKEY_COMPRESSED_SIZE_BYTES * 2;
const totalLength = pubSize + msg.byteLength; // 33 q + 33 pubkey + variable msgLen
const Q = q.toArrayLike(Buffer, 'be', 33);
const B = Buffer.allocUnsafe(totalLength);
Q.copy(B, 0);
pubkey.copy(B, 33);
msg.copy(B, 66);
return new BN(sha256.update(B).digest('hex'), 16);
};
/**
* @function sign
* @description sign method
* @param {Buffer} msg
* @param {Buffer} key
* @param {Buffer} pubkey
* @return {Signature}
*/
const sign = (msg, privKey, pubKey) => {
const prv = new BN(privKey);
const drbg = getDRBG(msg);
const len = curve.n.byteLength();
let sig;
while (!sig) {
const k = new BN(drbg.generate(len));
const trySig = trySign(msg, k, prv, pubKey);
sig = checkValidSignature(trySig) ? trySig : null;
}
return sig;
};
/**
* @function trySign
* @description try sign message with random k
* @param {Buffer} msg - the message to sign over
* @param {BN} k - output of the HMAC-DRBG
* @param {BN} privateKey - the private key
* @param {Buffer} pubKey - the public key
* @return {Signature | null}
*/
const trySign = (msg, k, privKey, pubKey) => {
if (privKey.isZero()) {
throw new Error('Bad private key.');
}
if (privKey.gte(curve.n)) {
throw new Error('Bad private key.');
} // 1a. check that k is not 0
if (k.isZero()) {
return null;
} // 1b. check that k is < the order of the group
if (k.gte(curve.n)) {
return null;
} // 2. Compute commitment Q = kG, where g is the base point
const Q = curve.g.mul(k); // convert the commitment to octets first
const compressedQ = new BN(Q.encodeCompressed()); // 3. Compute the challenge r = H(Q || pubKey || msg)
// mod reduce the r value by the order of secp256k1, n
const r = hash(compressedQ, pubKey, msg).umod(curve.n);
const h = r.clone();
if (h.isZero()) {
return null;
} // 4. Compute s = k - r * prv
// 4a. Compute r * prv
let s = h.imul(privKey).umod(curve.n); // 4b. Compute s = k - r * prv mod n
s = k.isub(s).umod(curve.n);
if (s.isZero()) {
return null;
}
return new Signature({
r,
s
});
};
/**
* @function verify
* @description Verify signature.
* 1. Check if r,s is in [1, ..., order-1]
* 2. Compute Q = sG + r*kpub
* 3. If Q = O (the neutral point), return 0;
* 4. r' = H(Q, kpub, m)
* 5. return r' == r
* @param {Buffer} msg
* @param {Buffer} signature
* @param {Buffer} key
* @return {Boolean}
*
*/
const verify = (msg, signature, key) => {
const sig = new Signature(signature);
if (sig.s.isZero() || sig.r.isZero()) {
throw new Error('Invalid signature');
}
if (sig.s.isNeg() || sig.r.isNeg()) {
throw new Error('Invalid signature');
}
if (sig.s.gte(curve.n) || sig.r.gte(curve.n)) {
throw new Error('Invalid signature');
}
const kpub = curve.decodePoint(key);
if (!curve.validate(kpub)) {
throw new Error('Invalid public key');
}
const l = kpub.mul(sig.r);
const r = curve.g.mul(sig.s);
const Q = l.add(r);
if (Q.isInfinity()) {
throw new Error('Invalid intermediate point.');
}
const compressedQ = new BN(Q.encodeCompressed());
const r1 = hash(compressedQ, key, msg).umod(curve.n);
if (r1.isZero()) {
throw new Error('Invalid hash.');
}
return r1.eq(sig.r);
};
/**
* @function toSignature
* @param {String} serialised serialised Signature string, length == 128
* @return {Signature} Signature instance
*/
const toSignature = serialised => {
const r = serialised.slice(0, 64);
const s = serialised.slice(64);
return new Signature({
r,
s
});
};
/**
* @function getDRBG
* @description generate an HMAC-DRBG.
* @param {Buffer} entropy
* @return {DRBG}
*/
const getDRBG = msg => {
const entropy = randomBytes(ENT_LEN);
const pers = Buffer.allocUnsafe(ALG_LEN + ENT_LEN);
Buffer.from(randomBytes(ENT_LEN)).copy(pers, 0);
ALG.copy(pers, ENT_LEN);
return new DRBG({
hash: hashjs.sha256,
entropy,
nonce: msg,
pers
});
};
/**
* @function signTest
* @description a test sign method using string for browser
* @param {String} msg - message string
* @param {String} k - random k string
* @param {String} prv - private key string
* @param {String} pub - public key string
* @return {Signature | null} Signature result
*/
const signTest = (msg, k, prv, pub) => {
const msgBuffer = Buffer.from(msg, 'hex');
const kBN = new BN(Buffer.from(k, 'hex'));
const privBN = new BN(Buffer.from(prv, 'hex'));
const pubBuffer = Buffer.from(pub, 'hex');
return trySign(msgBuffer, kBN, privBN, pubBuffer);
};
var schnorr = /*#__PURE__*/Object.freeze({
generatePrivateKey: generatePrivateKey,
hash: hash,
sign: sign,
trySign: trySign,
verify: verify,
toSignature: toSignature,
getDRBG: getDRBG,
signTest: signTest
});
const {
generatePrivateKey: generatePrivateKey$1
} = schnorr;
/**
* @function sign
* @description sign method using prviteKey and pubKey
* @param {Buffer} msg message buffer
* @param {String} privateKey private key string
* @param {String} pubKey public key string
* @returns {string} the signature
*/
const sign$1 = (msg, privateKey, pubKey) => {
const sig = sign(msg, Buffer.from(privateKey, 'hex'), Buffer.from(pubKey, 'hex'));
let r = sig.r.toString('hex');
let s = sig.s.toString('hex');
while (r.length < 64) {
r = `0${r}`;
}
while (s.length < 64) {
s = `0${s}`;
}
return r + s;
};
exports.BN = BN;
exports.elliptic = elliptic;
exports.hashjs = hashjs;
exports.generatePrivateKey = generatePrivateKey$1;
exports.sign = sign$1;
exports.schnorr = schnorr;
exports.randomBytes = randomBytes;
exports.getAddressFromPrivateKey = getAddressFromPrivateKey;
exports.getPubKeyFromPrivateKey = getPubKeyFromPrivateKey;
exports.compressPublicKey = compressPublicKey;
exports.getAddressFromPublicKey = getAddressFromPublicKey;
exports.verifyPrivateKey = verifyPrivateKey;
exports.toChecksumAddress = toChecksumAddress;
exports.isValidChecksumAddress = isValidChecksumAddress;
exports.encodeTransactionProto = encodeTransactionProto;
exports.getAddressForContract = getAddressForContract;
exports.checkValidSignature = checkValidSignature;
exports.encodeBase58 = encodeBase58;
exports.decodeBase58 = decodeBase58;
exports.getAddress = getAddress;
exports.intToHexArray = intToHexArray;
exports.intToByteArray = intToByteArray;
exports.hexToByteArray = hexToByteArray;
exports.hexToIntArray = hexToIntArray;
exports.isEqual = isEqual;
exports.isHex = isHex;
exports.Signature = Signature;
exports.ZilAddress = ZilAddress;
exports.AddressType = AddressType;
exports.encode = encode;
exports.decode = decode;
exports.convertBits = convertBits;
exports.toBech32Address = toBech32Address;
exports.fromBech32Address = fromBech32Address;
Object.defineProperty(exports, '__esModule', { value: true });
})));