Skip to content

Commit

Permalink
Merge pull request #30 from nash-io/ts/add-coins
Browse files Browse the repository at this point in the history
Ts/add coins
  • Loading branch information
localhuman authored Mar 4, 2021
2 parents d104e2e + d96a9d3 commit a981806
Show file tree
Hide file tree
Showing 9 changed files with 922 additions and 27 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### [4.0.9](https://github.com/nash-io/nash-protocol/compare/v4.0.7...v4.0.9) (2021-03-04)

### [3.3.21](https://github.com/nash-io/nash-protocol/compare/v4.0.1...v3.3.21) (2020-12-16)

### [4.0.7](https://github.com/nash-io/nash-protocol/compare/v4.0.1...v4.0.7) (2021-01-26)

### [4.0.5](https://github.com/nash-io/nash-protocol/compare/v4.0.3...v4.0.5) (2021-01-21)
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Implementation of Nash cryptographic routines.

## Getting started

This library requires Node v14 or higher. Install on lower versions is not supported.

yarn install
yarn build
yarn test
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@neon-exchange/nash-protocol",
"version": "4.0.7",
"version": "4.0.9",
"description": "TypeScript implementation of Nash crypto routines",
"main": "build/main/index.js",
"typings": "build/main/index.d.ts",
Expand Down 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"
},
"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
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
Loading

0 comments on commit a981806

Please sign in to comment.