syscoinjs-lib
Version:
A transaction creation library interfacing with coin selection for Syscoin.
387 lines (327 loc) • 11.3 kB
JavaScript
// BIP84 replacement implementation using existing dependencies
// This replaces the bip84 package to avoid pulling in legacy bitcoinjs-lib
const bitcoin = require('bitcoinjs-lib')
const { BIP32Factory } = require('bip32')
const ecc = require('@bitcoinerlab/secp256k1')
const bip32 = BIP32Factory(ecc)
const bip39 = require('bip39')
const bs58Module = require('bs58')
const bs58 = bs58Module.default || bs58Module
const { createHash } = require('crypto')
// Custom bs58check implementation to avoid adding another dependency
const bs58check = {
decode: function (string) {
const buffer = Buffer.from(bs58.decode(string))
const payload = buffer.slice(0, -4)
const checksum = buffer.slice(-4)
const newChecksum = createHash('sha256')
.update(createHash('sha256').update(payload).digest())
.digest()
.slice(0, 4)
if (!Buffer.from(checksum).equals(Buffer.from(newChecksum))) {
throw new Error('Invalid checksum')
}
return payload
},
encode: function (payload) {
const checksum = createHash('sha256')
.update(createHash('sha256').update(payload).digest())
.digest()
.slice(0, 4)
return bs58.encode(Buffer.concat([payload, checksum]))
}
}
const bitcoinPubTypes = { mainnet: { zprv: '04b2430c', zpub: '04b24746' }, testnet: { vprv: '045f18bc', vpub: '045f1cf6' } }
const bitcoinNetworks = { mainnet: bitcoin.networks.bitcoin, testnet: bitcoin.networks.testnet }
/**
* Constructor
* Derive accounts from a mnemonic.
* @param {string} mnemonic
* @param {string} password
* @param {boolean} isTestnet
* @param {number} coinType - slip44
* @param {object} pubTypes
* @param {object} network
* @return
*/
function fromMnemonic (mnemonic, password, isTestnet, coinType, pubTypes, network) {
if (!bip39.validateMnemonic(mnemonic)) throw new Error('could not validate mnemonic words')
this.seed = bip39.mnemonicToSeedSync(mnemonic, password || '')
this.isTestnet = isTestnet === true
this.coinType = this.isTestnet ? 1 : coinType || 0 // 0 is for Bitcoin and 1 is testnet for all coins
this.pubTypes = pubTypes || bitcoinPubTypes
this.network = network || (this.isTestnet ? bitcoinNetworks.testnet : bitcoinNetworks.mainnet)
}
/**
* Get root master private key
* @return {string}
*/
fromMnemonic.prototype.getRootPrivateKey = function () {
const prv = bip32.fromSeed(this.seed, this.network).toBase58()
const masterPrv = this.isTestnet
? b58Encode(prv, this.pubTypes.testnet.vprv)
: b58Encode(prv, this.pubTypes.mainnet.zprv)
return masterPrv
}
/**
* Get root master public key
* @return {string}
*/
fromMnemonic.prototype.getRootPublicKey = function () {
const pub = bip32.fromSeed(this.seed, this.network).neutered().toBase58()
const masterPub = this.isTestnet
? b58Encode(pub, this.pubTypes.testnet.vpub)
: b58Encode(pub, this.pubTypes.mainnet.zpub)
return masterPub
}
/**
* Derive a new master private key
* @param {number} number
* @param {number} changePurpose
* @return {string}
*/
fromMnemonic.prototype.deriveAccount = function (number, changePurpose) {
const purpose = changePurpose || 84
const keypath = 'm/' + purpose + "'/" + this.coinType + "'/" + number + "'"
const account = bip32.fromSeed(this.seed, this.network).derivePath(keypath).toBase58()
const masterPrv = this.isTestnet
? b58Encode(account, this.pubTypes.testnet.vprv)
: b58Encode(account, this.pubTypes.mainnet.zprv)
return masterPrv
}
/**
* Constructor
* Create key pairs from a private master key of mainnet and testnet.
* @param {string} zprv/vprv
* @param {object} pubTypes
* @param {object} networks
*/
function fromZPrv (zprv, pubTypes, networks) {
this.pubTypes = pubTypes || bitcoinPubTypes
this.networks = networks || bitcoinNetworks
this.zprv = this.toNode(zprv)
}
const byteToHex = (byte) => {
const key = '0123456789abcdef'
const bytes = new Uint8Array(byte)
let newHex = ''
let currentChar = 0
for (let i = 0; i < bytes.length; i++) { // Go over each 8-bit byte
currentChar = (bytes[i] >> 4) // First 4-bits for first hex char
newHex += key[currentChar] // Add first hex char to string
currentChar = (bytes[i] & 15) // Erase first 4-bits, get last 4-bits for second hex char
newHex += key[currentChar] // Add second hex char to string
}
return newHex
}
fromZPrv.prototype.toNode = function (zprv) {
const payload = bs58check.decode(zprv)
const prefix = byteToHex(payload.slice(0, 4))
const key = payload.slice(4)
let buffer
if (!Object.values(this.pubTypes.mainnet).includes(prefix) && !Object.values(this.pubTypes.testnet).includes(prefix)) {
throw new Error('prefix is not supported')
}
if (Object.values(this.pubTypes.mainnet).includes(prefix)) {
const buf = Buffer.allocUnsafe(4)
buf.writeInt32BE(this.networks.mainnet.bip32.private, 0)
buffer = Buffer.concat([buf, key]) // zprv
this.network = this.networks.mainnet
this.isTestnet = false
}
if (Object.values(this.pubTypes.testnet).includes(prefix)) {
const buf = Buffer.allocUnsafe(4)
buf.writeInt32BE(this.networks.testnet.bip32.private, 0)
buffer = Buffer.concat([buf, key]) // vprv
this.network = this.networks.testnet
this.isTestnet = true
}
return bs58check.encode(buffer)
}
/**
* Get account master private key
* @return {string}
*/
fromZPrv.prototype.getAccountPrivateKey = function () {
const pub = bip32.fromBase58(this.zprv, this.network).toBase58()
const masterPrv = this.isTestnet
? b58Encode(pub, this.pubTypes.testnet.vprv)
: b58Encode(pub, this.pubTypes.mainnet.zprv)
return masterPrv
}
/**
* Get account master public key
* @return {string}
*/
fromZPrv.prototype.getAccountPublicKey = function () {
const pub = bip32.fromBase58(this.zprv, this.network).neutered().toBase58()
const masterPub = this.isTestnet
? b58Encode(pub, this.pubTypes.testnet.vpub)
: b58Encode(pub, this.pubTypes.mainnet.zpub)
return masterPub
}
/**
* Get private key
* @param {number} index
* @param {boolean} isChange
* @return {string}
*/
fromZPrv.prototype.getPrivateKey = function (index, isChange) {
const change = isChange === true ? 1 : 0
const prvkey = bip32.fromBase58(this.zprv, this.network).derive(change).derive(index)
return prvkey.toWIF()
}
/**
* Get public key
* @param {number} index
* @param {boolean} isChange
* @return {string}
*/
fromZPrv.prototype.getPublicKey = function (index, isChange) {
const change = isChange === true ? 1 : 0
const prvkey = bip32.fromBase58(this.zprv, this.network).derive(change).derive(index)
return Buffer.from(prvkey.publicKey).toString('hex')
}
/**
* Get address
* @param {number} index
* @param {boolean} isChange
* @param {number} purpose
* @return {string}
*/
fromZPrv.prototype.getAddress = function (index, isChange, purpose) {
const change = isChange === true ? 1 : 0
const pubkey = bip32.fromBase58(this.zprv, this.network).derive(change).derive(index).publicKey
let payment = {}
purpose = purpose || 84
if (purpose === 44) {
payment = bitcoin.payments.p2pkh({ pubkey, network: this.network })
}
if (purpose === 49) {
payment = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({ pubkey, network: this.network }),
network: this.network
})
}
if (purpose === 84) {
payment = bitcoin.payments.p2wpkh({ pubkey, network: this.network })
}
return payment.address
}
fromZPrv.prototype.getKeypair = function (index, isChange) {
const change = isChange === true ? 1 : 0
const prvkey = bip32.fromBase58(this.zprv, this.network).derive(change).derive(index)
return prvkey
}
/**
* Constructor
* Create public keys and addresses from a public master key of mainnet and testnet.
* @param {string} zpub/vpub
* @param {object} pubTypes
* @param {object} networks
*/
function fromZPub (zpub, pubTypes, networks) {
this.pubTypes = pubTypes || bitcoinPubTypes
this.networks = networks || bitcoinNetworks
this.zpub = this.toNode(zpub)
}
fromZPub.prototype.toNode = function (zpub) {
const payload = bs58check.decode(zpub)
const prefix = byteToHex(payload.slice(0, 4))
const key = payload.slice(4)
let buffer
if (!Object.values(this.pubTypes.mainnet).includes(prefix) && !Object.values(this.pubTypes.testnet).includes(prefix)) {
throw new Error('prefix is not supported')
}
if (Object.values(this.pubTypes.mainnet).includes(prefix)) {
const buf = Buffer.allocUnsafe(4)
buf.writeInt32BE(this.networks.mainnet.bip32.public, 0)
buffer = Buffer.concat([buf, key]) // zpub
this.network = this.networks.mainnet
this.isTestnet = false
}
if (Object.values(this.pubTypes.testnet).includes(prefix)) {
const buf = Buffer.allocUnsafe(4)
buf.writeInt32BE(this.networks.testnet.bip32.public, 0)
buffer = Buffer.concat([buf, key]) // vpub
this.network = this.networks.testnet
this.isTestnet = true
}
return bs58check.encode(buffer)
}
/**
* Get account master public key
* @return {string}
*/
fromZPub.prototype.getAccountPublicKey = function () {
const pub = bip32.fromBase58(this.zpub, this.network).neutered().toBase58()
const masterPub = this.isTestnet
? b58Encode(pub, this.pubTypes.testnet.vpub)
: b58Encode(pub, this.pubTypes.mainnet.zpub)
return masterPub
}
/**
* Get public key
* @param {number} index
* @param {boolean} isChange
* @return {string}
*/
fromZPub.prototype.getPublicKey = function (index, isChange) {
const change = isChange === true ? 1 : 0
const zpub = bip32.fromBase58(this.zpub, this.network).derive(change).derive(index)
return Buffer.from(zpub.publicKey).toString('hex')
}
/**
* Get address
* @param {number} index
* @param {boolean} isChange
* @param {number} purpose
* @return {string}
*/
fromZPub.prototype.getAddress = function (index, isChange, purpose) {
const change = isChange === true ? 1 : 0
const pubkey = bip32.fromBase58(this.zpub, this.network).derive(change).derive(index).publicKey
let payment = {}
purpose = purpose || 84
if (purpose === 44) {
payment = bitcoin.payments.p2pkh({ pubkey, network: this.network })
}
if (purpose === 49) {
payment = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({ pubkey, network: this.network }),
network: this.network
})
}
if (purpose === 84) {
payment = bitcoin.payments.p2wpkh({ pubkey, network: this.network })
}
return payment.address
}
/**
* Get address
* @param {number} index
* @param {boolean} isChange
* @return {string}
*/
fromZPub.prototype.getPayment = function (index, isChange) {
const change = isChange === true ? 1 : 0
const pubkey = bip32.fromBase58(this.zpub, this.network).derive(change).derive(index).publicKey
const payment = bitcoin.payments.p2wpkh({
pubkey,
network: this.network
})
return payment
}
function b58Encode (pub, data) {
const payload = bs58check.decode(pub)
const key = payload.slice(4)
return bs58check.encode(Buffer.concat([Buffer.from(data, 'hex'), key]))
}
module.exports = {
generateMnemonic: bip39.generateMnemonic,
entropyToMnemonic: bip39.entropyToMnemonic,
validateMnemonic: bip39.validateMnemonic,
fromMnemonic,
fromZPrv,
fromZPub
}