@faast/tron-payments
Version:
Library to assist in processing tron payments, such as deriving addresses and sweeping funds
204 lines (163 loc) • 5.12 kB
text/typescript
// Many parts of this code are snippets from tronWeb:
// https://github.com/tronprotocol/tron-web/blob/master/src/index.js
import { BIP32Interface as HDNode, fromBase58, fromSeed } from 'bip32'
import { keccak256 } from 'js-sha3'
import jsSHA from 'jssha'
import { ec as EC } from 'elliptic'
import crypto from 'crypto'
import { encode58 } from './base58'
import { isValidXpub, isValidXprv } from './helpers'
const ec = new EC('secp256k1')
export const derivationPath = "m/44'/195'/0'"
const derivationPathParts = derivationPath.split('/').slice(1)
type HDKey<K> = {
depth: number
derive: (path: string | number, hardened?: boolean) => K
}
export function deriveAddress(xpub: string, index: number): string {
if (!isValidXpub(xpub)) {
throw new Error('Invalid xpub')
}
const key = fromBase58(xpub)
const derived = deriveBasePath(key)
.derive(0)
.derive(index)
return hdPublicKeyToAddress(derived)
}
export function derivePrivateKey(xprv: string, index: number): string {
if (!isValidXprv(xprv)) {
throw new Error('Invalid xprv')
}
const key = fromBase58(xprv)
const derived = deriveBasePath(key)
.derive(0)
.derive(index)
return hdPrivateKeyToPrivateKey(derived)
}
export function xprvToXpub(xprv: string | HDNode): string {
const key = typeof xprv === 'string' ? fromBase58(xprv) : xprv
const derivedPubKey = deriveBasePath(key)
return derivedPubKey.neutered().toBase58()
}
export function generateNewKeys(): { xpub: string; xprv: string } {
const key = fromSeed(crypto.randomBytes(32))
const xprv = key.toBase58()
const xpub = xprvToXpub(xprv)
return {
xprv,
xpub,
}
}
// HELPER FUNCTIONS
function deriveBasePath(key: HDNode): HDNode {
const parts = derivationPathParts.slice(key.depth)
if (parts.length > 0) {
return key.derivePath(`m/${parts.join('/')}`)
}
return key
}
function hdPublicKeyToAddress(key: HDNode): string {
return addressBytesToB58CheckAddress(pubBytesToTronBytes(bip32PublicToTronPublic(key.publicKey)))
}
function hdPrivateKeyToPrivateKey(key: HDNode): string {
if (key.isNeutered() || typeof key.privateKey === 'undefined') {
throw new Error('Invalid HD private key, must not be neutered')
}
return bip32PrivateToTronPrivate(key.privateKey)
}
function bip32PublicToTronPublic(pubKey: any): number[] {
const pubkey = ec.keyFromPublic(pubKey).getPublic()
const x = pubkey.getX()
const y = pubkey.getY()
let xHex = x.toString('hex')
while (xHex.length < 64) {
xHex = `0${xHex}`
}
let yHex = y.toString('hex')
while (yHex.length < 64) {
yHex = `0${yHex}`
}
const pubkeyHex = `04${xHex}${yHex}`
const pubkeyBytes = hexStr2byteArray(pubkeyHex)
return pubkeyBytes
}
function bip32PrivateToTronPrivate(priKeyBytes: Buffer): string {
const key = ec.keyFromPrivate(priKeyBytes, 'bytes')
let priKeyHex = key.getPrivate('hex')
while (priKeyHex.length < 64) {
priKeyHex = `0${priKeyHex}`
}
let privArray = hexStr2byteArray(priKeyHex)
return byteArray2hexStr(privArray)
}
// Borrowed from tronweb: https://github.com/tronprotocol/tron-web/blob/master/src/utils/code.js
const ADDRESS_PREFIX = '41'
function byte2hexStr(byte: number): string {
const hexByteMap = '0123456789ABCDEF'
let str = ''
str += hexByteMap.charAt(byte >> 4)
str += hexByteMap.charAt(byte & 0x0f)
return str
}
function hexStr2byteArray(str: string): number[] {
const byteArray = Array()
let d = 0
let j = 0
let k = 0
for (let i = 0; i < str.length; i++) {
const c = str.charAt(i)
if (isHexChar(c)) {
d <<= 4
d += hexChar2byte(c)
j++
if (0 === j % 2) {
byteArray[k++] = d
d = 0
}
}
}
return byteArray
}
function isHexChar(c: string): boolean {
return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9')
}
function hexChar2byte(c: string): number {
let d = 0
if (c >= 'A' && c <= 'F') {
d = c.charCodeAt(0) - 'A'.charCodeAt(0) + 10
} else if (c >= 'a' && c <= 'f') {
d = c.charCodeAt(0) - 'a'.charCodeAt(0) + 10
} else if (c >= '0' && c <= '9') {
d = c.charCodeAt(0) - '0'.charCodeAt(0)
}
return d
}
function byteArray2hexStr(byteArray: number[]): string {
let str = ''
for (let i = 0; i < byteArray.length; i++) {
str += byte2hexStr(byteArray[i])
}
return str
}
function pubBytesToTronBytes(pubBytes: number[]): number[] {
if (pubBytes.length === 65) {
pubBytes = pubBytes.slice(1)
}
const hash = keccak256(pubBytes).toString()
const addressHex = ADDRESS_PREFIX + hash.substring(24)
return hexStr2byteArray(addressHex)
}
function addressBytesToB58CheckAddress(addressBytes: number[]) {
const hash0 = SHA256(addressBytes)
const hash1 = SHA256(hash0)
let checkSum = hash1.slice(0, 4)
checkSum = addressBytes.concat(checkSum)
return encode58(checkSum)
}
function SHA256(msgBytes: number[]): number[] {
const shaObj = new jsSHA('SHA-256', 'HEX')
const msgHex = byteArray2hexStr(msgBytes)
shaObj.update(msgHex)
const hashHex = shaObj.getHash('HEX')
return hexStr2byteArray(hashHex)
}