laksa-core-crypto
Version:
crypto libraries for laksa
313 lines (282 loc) • 8.37 kB
JavaScript
import elliptic from 'elliptic'
import hashjs from 'hash.js'
import BN from 'bn.js'
import { ZilliqaMessage } from '@zilliqa-js/proto'
import { isString } from 'laksa-utils'
import { intToHexArray, hexToByteArray } from './bytes'
import { ZilAddress, AddressType } from './zilAddress'
// const HEX_PREFIX = '0x';
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}
*/
export 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}
*/
export 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}
*/
export 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}
*/
export 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}
*/
export 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
*/
export 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}
*/
export 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}
*/
export const encodeTransactionProto = tx => {
const msg = {
version: tx.version,
nonce: tx.nonce || 0,
toaddr: hexToByteArray(tx.toAddr.replace('0x', '').toLowerCase()),
senderpubkey: ZilliqaMessage.ByteArray.create({
data: hexToByteArray(tx.pubKey || '00')
}),
amount: ZilliqaMessage.ByteArray.create({
data: Uint8Array.from(tx.amount.toArrayLike(Buffer, undefined, 16))
}),
gasprice: 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 = ZilliqaMessage.ProtoTransactionCoreInfo.create(msg)
return Buffer.from(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
*/
export 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}
*/
export const checkValidSignature = sig => {
return sig.r.toString('hex').length + sig.s.toString('hex').length === 128
}
export 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
}
export 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
}
export const getAddress = (address, fromType, toType) => {
if (!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
}
}
export { hashjs }