Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ts/add coins #30

Merged
merged 6 commits into from
Mar 4, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,21 @@
"preinstall": "node -e \"if(process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('nash-protocol must be installed with Yarn: https://yarnpkg.com/')\""
},
"engines": {
"node": ">=14.3"
"node": ">=10"
localhuman marked this conversation as resolved.
Show resolved Hide resolved
},
"dependencies": {
"@elrondnetwork/elrond-core-js": "^2.1.0",
"@polkadot/keyring": "^5.8.1",
"@polkadot/util-crypto": "^5.8.1",
"bchaddrjs": "^0.5.2",
"bignumber.js": "8.1.1",
"bip32": "2.0.3",
"bip39": "2.5.0",
"bitcoinjs-lib": "5.0.5",
"bn.js": "5.1.1",
"browserify-aes": "1.2.0",
"bs58": "4.0.1",
"coininfo": "^5.1.0",
"crypto-js": "3.1.9-1",
"elliptic": "6.4.0",
"ethereumjs-util": "6.1.0",
Expand Down Expand Up @@ -84,4 +89,4 @@
"**/*.spec.js"
]
}
}
}
60 changes: 59 additions & 1 deletion src/__tests__/testVectors.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,65 @@
"publicKey": "03a41d39a4209e18eb79245ea368674edf335a4ebeb8ed344b87d3ccaf3a2c730e",
"address": "ANwZ2RFRKrASBvZifGgjqqJNUbfJUW6gbn"
}
]
],
"ltc": {
"MainNet": {
"address": "MCPi7QVw4DGJMapjUNtArnMHCsRQpHPo1e",
"index": 0,
"privateKey": "febfcd1fbd52b4a5c5e1d54284875fec98f3810f14273f41921da4c94e7c66a2",
"publicKey": "0300b3c811a09ee947e9fe9727f92d8d32fdc9cdcc3084f4dce2eb65141aae6247"
},
"TestNet": {
"address": "QR6XzGtEjeyJu3wRfjYijnXaEuUxYAk6qX",
"index": 0,
"privateKey": "febfcd1fbd52b4a5c5e1d54284875fec98f3810f14273f41921da4c94e7c66a2",
"publicKey": "0300b3c811a09ee947e9fe9727f92d8d32fdc9cdcc3084f4dce2eb65141aae6247"
}
},
"doge": {
"MainNet": {
"address": "DGx1o7YAWukpfsWKbj6p2ihDdaJBnD5gQM",
"index": 0,
"privateKey": "229138899669a83627a9328d8629233f1e906ed0f1c92cbee35efb77e1433931",
"publicKey": "023c3b135e0e3eeeaa4fff825fd712bdddb75e0594a8e96b1598af8804fdc946e2"
},
"TestNet": {
"address": "ng15X8H5StDYYr5WdYkGH8HWsSgUoz3gMy",
"index": 0,
"privateKey": "229138899669a83627a9328d8629233f1e906ed0f1c92cbee35efb77e1433931",
"publicKey": "023c3b135e0e3eeeaa4fff825fd712bdddb75e0594a8e96b1598af8804fdc946e2"
}
},
"bch": {
"MainNet": {
"address": "bitcoincash:qpmqltp5krmqqvxwppvem6xw99fdnqz3rugdqw39w4",
"index": 0,
"privateKey": "6bdb92cc545a2da68473eda031db2e77961ba14938ae505df786e1021f40a018",
"publicKey": "02e0b806db8ab79814a5009885b12de754d09f8f419d0ca0a6406ce767cb58a14d"
},
"TestNet": {
"address": "bchtest:qpmqltp5krmqqvxwppvem6xw99fdnqz3ruvlyfnjff",
"index": 0,
"privateKey": "6bdb92cc545a2da68473eda031db2e77961ba14938ae505df786e1021f40a018",
"publicKey": "02e0b806db8ab79814a5009885b12de754d09f8f419d0ca0a6406ce767cb58a14d"
}
},
"dot": {
"MainNet": {
"address": "12Gez5b4gsvD2RbPqjD7jX9bvSG5BNoyuxjZ51XeTxyXnSMm",
"index": 0,
"privateKey": "d85ab13b994b71d81c45187427ab0f33db7f99ae3f4a8f0eb17b74656b80c217",
"publicKey": "382cc7cd04190bff63bf3e47e01539992ea7e208e527f4769a84f510534fa23f"
}
},
"erd": {
"MainNet": {
"address": "erd1lqzc8aam9gpxxc7xqmg4pyw8gl25jw8vmzmmx84d8glepalf6gpsuhj4py",
"index": 0,
"privateKey": "a555541a7eab5081098c10d47b2a076b999e8561d787d3362f0854981b3d9ab1",
"publicKey": "f80583f7bb2a026363c606d15091c747d54938ecd8b7b31ead3a3f90f7e9d203"
}
}
}
}
]
90 changes: 90 additions & 0 deletions src/generateWallet/generateWallet.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { generateNashPayloadSigningKey, generateWallet, CoinType } from './generateWallet'
import _ from 'lodash'
import testVectors from '../__tests__/testVectors.json'
import { cryptoWaitReady } from '@polkadot/util-crypto'

test('generates deterministic BIP44 ETH keys', async () => {
for (const vector of testVectors) {
Expand Down Expand Up @@ -44,6 +45,95 @@ test('generates deterministic BIP44 BTC keys', async () => {
}
})

test('generates deterministic BIP44 LTC keys', async () => {
for (const vector of testVectors) {
const masterSeed = Buffer.from(vector.masterSeed, 'hex')
const ltc = vector.wallets.ltc.MainNet

const genWallet = generateWallet(masterSeed, CoinType.LTC, ltc.index, 'MainNet')
expect(genWallet.address).toBe(ltc.address)
expect(genWallet.publicKey).toBe(ltc.publicKey)
expect(genWallet.privateKey).toBe(ltc.privateKey)
expect(genWallet.index).toBe(ltc.index)

const ltcTestnet = vector.wallets.ltc.TestNet
const testnetWallet = generateWallet(masterSeed, CoinType.LTC, ltc.index, 'TestNet')
expect(testnetWallet.address).toBe(ltcTestnet.address)
expect(testnetWallet.publicKey).toBe(ltcTestnet.publicKey)
expect(testnetWallet.privateKey).toBe(ltcTestnet.privateKey)
expect(testnetWallet.index).toBe(ltcTestnet.index)
}
})

test('generates deterministic BIP44 doge keys', async () => {
for (const vector of testVectors) {
const masterSeed = Buffer.from(vector.masterSeed, 'hex')
const wallet = vector.wallets.doge.MainNet

const genWallet = generateWallet(masterSeed, CoinType.DOGE, wallet.index, 'MainNet')
expect(genWallet.address).toBe(wallet.address)
expect(genWallet.publicKey).toBe(wallet.publicKey)
expect(genWallet.privateKey).toBe(wallet.privateKey)
expect(genWallet.index).toBe(wallet.index)

const testWallet = vector.wallets.doge.TestNet
const testnetWallet = generateWallet(masterSeed, CoinType.DOGE, testWallet.index, 'TestNet')
expect(testnetWallet.address).toBe(testWallet.address)
expect(testnetWallet.publicKey).toBe(testWallet.publicKey)
expect(testnetWallet.privateKey).toBe(testWallet.privateKey)
expect(testnetWallet.index).toBe(testWallet.index)
}
})

test('generates deterministic BIP44 bitcoincash keys', async () => {
for (const vector of testVectors) {
const masterSeed = Buffer.from(vector.masterSeed, 'hex')
const wallet = vector.wallets.bch.MainNet

const genWallet = generateWallet(masterSeed, CoinType.BCH, wallet.index, 'MainNet')
expect(genWallet.address).toBe(wallet.address)
expect(genWallet.publicKey).toBe(wallet.publicKey)
expect(genWallet.privateKey).toBe(wallet.privateKey)
expect(genWallet.index).toBe(wallet.index)

const testWallet = vector.wallets.bch.TestNet
const testnetWallet = generateWallet(masterSeed, CoinType.BCH, testWallet.index, 'TestNet')
expect(testnetWallet.address).toBe(testWallet.address)
expect(testnetWallet.publicKey).toBe(testWallet.publicKey)
expect(testnetWallet.privateKey).toBe(testWallet.privateKey)
expect(testnetWallet.index).toBe(testWallet.index)
}
})

test('generates deterministic BIP44 dot keys', async () => {
await cryptoWaitReady()

for (const vector of testVectors) {
const masterSeed = Buffer.from(vector.masterSeed, 'hex')
const wallet = vector.wallets.dot.MainNet

const genWallet = generateWallet(masterSeed, CoinType.DOT, wallet.index, 'MainNet')
expect(genWallet.address).toBe(wallet.address)
expect(genWallet.publicKey).toBe(wallet.publicKey)
expect(genWallet.privateKey).toBe(wallet.privateKey)
expect(genWallet.index).toBe(wallet.index)
}
})

test('generates deterministic BIP44 erd keys', async () => {
await cryptoWaitReady()

for (const vector of testVectors) {
const masterSeed = Buffer.from(vector.masterSeed, 'hex')
const wallet = vector.wallets.erd.MainNet
const genWallet = generateWallet(masterSeed, CoinType.ERD, wallet.index, 'MainNet')
expect(genWallet.address).toBe(wallet.address)
expect(genWallet.publicKey).toBe(wallet.publicKey)
expect(genWallet.privateKey).toBe(wallet.privateKey)
expect(genWallet.index).toBe(wallet.index)
}
})

test('generates deterministic payload signing key', async () => {
for (const vector of testVectors) {
const masterSeed = Buffer.from(vector.masterSeed, 'hex')
Expand Down
116 changes: 103 additions & 13 deletions src/generateWallet/generateWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { Wallet } from '../types'
import { reverseHex } from '../utils/getNEOScriptHash/getNEOScripthash'
import * as EthUtil from 'ethereumjs-util'
import * as Bitcoin from 'bitcoinjs-lib'
import bchaddr from 'bchaddrjs'
import * as tiny from 'tiny-secp256k1'
import * as coininfo from 'coininfo'
import Keyring from '@polkadot/keyring'
import { account as erdAccount } from '@elrondnetwork/elrond-core-js'

import base58 from 'bs58'
import hexEncoding from 'crypto-js/enc-hex'
import RIPEMD160 from 'crypto-js/ripemd160'
Expand All @@ -16,10 +21,23 @@ const nashPurpose = 1337

export enum CoinType {
BTC = 0,
LTC = 2,
DOGE = 3,
ETH = 60,
NEO = 888
ETC = 61,
BCH = 145,
DOT = 354,
ERD = 508,
NEO = 888,
AVAX = 9000
}

const NON_SEGWIT = [CoinType.BCH, CoinType.DOGE]

interface DotKeyPair {
publicKey: Uint8Array
address: string
}
/**
* Creates a wallet for a given token via the
* [BIP-44 protocol]((https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki).
Expand Down Expand Up @@ -75,8 +93,15 @@ export function deriveIndex(extendedKey: bip32.BIP32Interface, index: number): b

export const coinTypeFromString = (s: string): CoinType => {
const m: Record<string, CoinType> = {
avax: CoinType.AVAX,
bch: CoinType.BCH,
btc: CoinType.BTC,
doge: CoinType.DOGE,
dot: CoinType.DOT,
erd: CoinType.ERD,
etc: CoinType.ETC,
eth: CoinType.ETH,
ltc: CoinType.LTC,
neo: CoinType.NEO
}

Expand Down Expand Up @@ -153,6 +178,8 @@ function generateWalletForCoinType(key: bip32.BIP32Interface, coinType: CoinType
publicKey
}
case CoinType.ETH:
case CoinType.ETC:
case CoinType.AVAX:
// TODO: can we replace this with the elliptic package which we already
// use to trim bundle size?
const pubkey = tiny.pointFromScalar(key.privateKey, false)
Expand All @@ -163,38 +190,101 @@ function generateWalletForCoinType(key: bip32.BIP32Interface, coinType: CoinType
publicKey: pubkey.toString('hex')
}
case CoinType.BTC:
case CoinType.BCH:
case CoinType.LTC:
case CoinType.DOGE:
return {
address: bitcoinAddressFromPublicKey(key.publicKey, net!),
address: bitcoinAddressFromPublicKey(key.publicKey, coinType, net!),
index,
privateKey: key.privateKey.toString('hex'),
publicKey: key.publicKey.toString('hex')
}
case CoinType.DOT:
const keypair = dotKeypairFromSeed(key.privateKey)
return {
address: keypair.address,
index,
privateKey: key.privateKey.toString('hex'),
publicKey: new Buffer(keypair.publicKey).toString('hex')
}
case CoinType.ERD:
const account = new erdAccount()
account.loadFromSeed(key.privateKey)
return {
address: account.address(),
index,
privateKey: key.privateKey.toString('hex'),
publicKey: account.publicKeyAsString()
}
default:
throw new Error(`invalid coin type ${coinType} for generating a wallet`)
}
}

const bitcoinAddressFromPublicKey = (publicKey: Buffer, net: string): string => {
const network = bitcoinNetworkFromString(net)
const bitcoinAddressFromPublicKey = (publicKey: Buffer, type: CoinType, net: string): string => {
const network = bitcoinNetworkFromString(type, net)
if (NON_SEGWIT.includes(type)) {
const addr = Bitcoin.payments.p2pkh({ pubkey: publicKey, network }).address as string
// For BCH, we convert to bitcoincash format
if (type === CoinType.BCH) {
return bchaddr.toCashAddress(addr)
}
return addr
}
return Bitcoin.payments.p2sh({
network,
redeem: Bitcoin.payments.p2wpkh({ pubkey: publicKey, network })
}).address as string
}

const bitcoinNetworkFromString = (net: string | undefined): Bitcoin.Network => {
switch (net) {
case 'MainNet':
return Bitcoin.networks.bitcoin
case 'TestNet':
return Bitcoin.networks.regtest
case 'LocalNet':
return Bitcoin.networks.regtest
const bitcoinNetworkFromString = (type: CoinType, net: string | undefined): Bitcoin.Network => {
switch (type) {
case CoinType.BTC:
switch (net) {
case 'MainNet':
return Bitcoin.networks.bitcoin
case 'TestNet':
return Bitcoin.networks.regtest
case 'LocalNet':
return Bitcoin.networks.regtest
default:
return Bitcoin.networks.bitcoin
}
case CoinType.LTC:
switch (net) {
case 'TestNet':
case 'LocalNet':
return coininfo.litecoin.test.toBitcoinJS()
default:
return coininfo.litecoin.main.toBitcoinJS()
}
case CoinType.BCH:
switch (net) {
case 'TestNet':
case 'LocalNet':
return coininfo.bitcoincash.test.toBitcoinJS()
default:
return coininfo.bitcoincash.main.toBitcoinJS()
}
case CoinType.DOGE:
switch (net) {
case 'TestNet':
case 'LocalNet':
return coininfo.dogecoin.test.toBitcoinJS()
default:
return coininfo.dogecoin.main.toBitcoinJS()
}
default:
return Bitcoin.networks.bitcoin
throw new Error(`Could not get bitcoin network for coin type: ${type}`)
}
}

const dotKeypairFromSeed = (key: Buffer): DotKeyPair => {
const keyring = new Keyring({ type: 'sr25519', ss58Format: 0 })
keyring.addFromSeed(key)
return keyring.getPairs().shift() as DotKeyPair
}

function derivePath(
masterSeed: Buffer,
purpose: number,
Expand Down
5 changes: 5 additions & 0 deletions src/initialize/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import decryptSecretKey from '../decryptSecretKey'
import secretKeyToMnemonic from '../secretKeyToMnemonic'
import mnemonicToMasterSeed from '../mnemonicToMasterSeed'
import { generateNashPayloadSigningKey, generateWallet, coinTypeFromString } from '../generateWallet'
import { cryptoWaitReady } from '@polkadot/util-crypto'

// initialize takes in the init parameters and returns a Config object with all the
// derived keys.
Expand All @@ -12,6 +13,10 @@ export default async function initialize(params: InitParams): Promise<Config> {

const wallets: Record<string, Wallet> = {}
for (const [name, index] of Object.entries(params.walletIndices)) {
// We do not want to initialized polkadotjs unless necessary
if (name.toLowerCase() === 'dot') {
await cryptoWaitReady()
}
wallets[name] = generateWallet(masterSeed, coinTypeFromString(name), index, params.net)
}

Expand Down
Loading