diff --git a/src/crypto/bip44.ts b/src/crypto/bip44.ts index 510bcf2..352cf89 100644 --- a/src/crypto/bip44.ts +++ b/src/crypto/bip44.ts @@ -1,16 +1,18 @@ -const bip44 = { - createDerivePath(network) { - /* - In fact, not every testnet of coins has an index of 1 - Therefore, specify the testnet coin index in the settings - */ +type Path = string - const { coinIndex } = network.settings.bip44 - const addressIndex = 0 +const createDerivePath = ({ coinIndex, addressIndex }: { + coinIndex: number, + addressIndex: number +}): Path => { + /* + In fact, not every testnet of coins has an index of 1 + Therefore, specify the testnet coin index in the settings + */ - const path = `m/44'/${coinIndex}'/0'/0/${addressIndex}` - return path - } + const path = `m/44'/${coinIndex}'/0'/0/${addressIndex}` + return path } -export default bip44 +export default { + createDerivePath +} diff --git a/src/crypto/coins/BTC.ts b/src/crypto/coins/BTC.ts index 56973c7..b53b29d 100644 --- a/src/crypto/coins/BTC.ts +++ b/src/crypto/coins/BTC.ts @@ -3,7 +3,7 @@ import * as bip39 from 'bip39' import bitcore from 'bitcore-lib' import bip44 from '../bip44' -import { ICoin, ENetworkType } from '../index' +import { ICoin, ENetworkType } from '../types' const netNames = { mainnet: 'mainnet', @@ -57,9 +57,8 @@ const BTC: ICoin = { } } }, - profileFromMnemonic({ mnemonic, netName, index }) { - const network = BTC.networks[netName] - const { settings } = network + profileFromMnemonic({ mnemonic, network, addressIndex }) { + const { settings } = BTC.networks[network] // todo: move? const seed = bip39.mnemonicToSeedSync(mnemonic) @@ -73,21 +72,24 @@ const BTC: ICoin = { pubKeyHash: settings.base58prefix.pubKeyHash, scriptHash: settings.base58prefix.scriptHash }) - const derivePath = bip44.createDerivePath(network) + const derivePath = bip44.createDerivePath({ + coinIndex: settings.bip44.coinIndex, + addressIndex + }) const child = root.derivePath(derivePath) let libNetwork - if (netName === netNames.mainnet) { + if (network === netNames.mainnet) { libNetwork = bitcore.Networks.mainnet } - if (netName === netNames.testnet) { + if (network === netNames.testnet) { libNetwork = bitcore.Networks.testnet } if (!libNetwork) { - throw new Error(`Unknown network: ${netName}`) + throw new Error(`Unknown network: ${network}`) } // eslint-disable-next-line new-cap @@ -96,9 +98,9 @@ const BTC: ICoin = { const address = new bitcore.Address(publicKey, libNetwork) const account = { - privateKey, - publicKey, - address + privateKey: child.toWIF(), + publicKey: publicKey.toString(), + address: address.toString() } return account diff --git a/src/crypto/coins/ETH.ts b/src/crypto/coins/ETH.ts index 962ef01..a253931 100644 --- a/src/crypto/coins/ETH.ts +++ b/src/crypto/coins/ETH.ts @@ -2,7 +2,8 @@ import { hdkey } from 'ethereumjs-wallet' import * as bip39 from 'bip39' import web3utils from 'web3-utils' -import { ICoin, ENetworkType } from '../index' +import bip44 from '../bip44' +import { ICoin, ENetworkType } from '../types' const ETH: ICoin = { symbol: 'ETH', @@ -29,11 +30,16 @@ const ETH: ICoin = { } } }, - profileFromMnemonic({ mnemonic, netName, index }) { + profileFromMnemonic({ mnemonic, addressIndex }) { const masterSeed = bip39.mnemonicToSeedSync(mnemonic) const hdwallet = hdkey.fromMasterSeed(masterSeed) - const wallet = hdwallet.derivePath(`m/44'/60'/0'/0/${index}`).getWallet() + const derivePath = bip44.createDerivePath({ + coinIndex: 60, + addressIndex + }) + + const wallet = hdwallet.derivePath(derivePath).getWallet() const privateKey = wallet.getPrivateKeyString() const publicKey = wallet.getPublicKeyString() /* diff --git a/src/crypto/coins/LTC.ts b/src/crypto/coins/LTC.ts index 17abf54..b71858e 100644 --- a/src/crypto/coins/LTC.ts +++ b/src/crypto/coins/LTC.ts @@ -3,7 +3,7 @@ import * as bip39 from 'bip39' import bitcore from 'bitcore-lib' import bip44 from '../bip44' -import { ICoin, ENetworkType } from '../index' +import { ICoin, ENetworkType } from '../types' const netNames = { mainnet: 'mainnet', @@ -60,26 +60,28 @@ const LTC: ICoin = { } } }, - profileFromMnemonic({ mnemonic, netName, index }) { - const network = LTC.networks[netName] - const { settings } = network + profileFromMnemonic({ mnemonic, network, addressIndex }) { + const { settings } = LTC.networks[network] const seed = bip39.mnemonicToSeedSync(mnemonic) const root = bip32.fromSeed(seed /* , network.bip32settings */) - const derivePath = bip44.createDerivePath(network) + const derivePath = bip44.createDerivePath({ + coinIndex: settings.bip44.coinIndex, + addressIndex + }) const child = root.derivePath(derivePath) let libNetworkName - if (netName === netNames.mainnet) { + if (network === netNames.mainnet) { libNetworkName = 'litecoin-mainnet' } - if (netName === netNames.testnet) { + if (network === netNames.testnet) { libNetworkName = 'litecoin-testnet' } if (!libNetworkName) { - throw new Error(`Unknown network: ${netName}`) + throw new Error(`Unknown network: ${network}`) } bitcore.Networks.add({ diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 3f151ee..60829bd 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -1,66 +1,25 @@ -/* -seed + password + HD Path => private key -private key => public key -public key => public address -*/ - -type TMnemonic = string -type TPrivateKey = string -type TPublicKey = string -type TAddress = string - -type TNetworkName = string - -type TProfile = { - privateKey: TPrivateKey - publicKey: TPublicKey - address: TAddress -} - -export enum ENetworkType { - Mainnet = 'Mainnet', - Testnet = 'Testnet' -} - -export enum EPreset { - BIP44 = 'BIP44', - electrum = 'electrum' -} - -export interface IAddProfileParams { - mnemonic?: TMnemonic -} - -export interface ICreateAddressesParams { - coin: 'BTC' | 'LTC' | 'ETH' - preset: EPreset -} - -export interface INetwork { - type: ENetworkType - settings: { - port: number - magic: number - messagePrefix: string - base58prefix: { - pubKeyHash: number - scriptHash: number - privateKeyWIF: number - publicKeyBIP32: number - privateKeyBIP32: number - } - bip44: { - coinIndex: number - } +import coins from './coins' +import { TProfile } from './types' + +/* interface IProfileFromMnemonicParams { + typeof coins.BTC.profileFromMnemonic || + typeof coins.LTC.profileFromMnemonic +} */ + +const profileFromMnemonic: (params) => TProfile = params => { + // todo: improve types - unreliable + if (params.coin === 'BTC') { + return coins.BTC.profileFromMnemonic(params) + } + if (params.coin === 'LTC') { + return coins.LTC.profileFromMnemonic(params) } + if (params.coin === 'ETH') { + return coins.ETH.profileFromMnemonic(params) + } + throw new Error(`Unknown coin "${params.coin}"`) } -export interface ICoin { - symbol: string - name: string - precision: number - networks: { - [key: string]: INetwork - } - profileFromMnemonic: ({ mnemonic: TMnemonic, netName: TNetworkName, index: number }) => TProfile +export default { + profileFromMnemonic } diff --git a/src/crypto/types.ts b/src/crypto/types.ts new file mode 100644 index 0000000..9d237bc --- /dev/null +++ b/src/crypto/types.ts @@ -0,0 +1,66 @@ +/* +seed + password + HD Path => private key +private key => public key +public key => public address +*/ + +export type TMnemonic = string +export type TPrivateKey = string +export type TPublicKey = string +export type TAddress = string + +export type TNetworkName = string + +export type TProfile = { + privateKey: TPrivateKey + publicKey: TPublicKey + address: TAddress +} + +export enum ENetworkType { + Mainnet = 'Mainnet', + Testnet = 'Testnet' +} + +export enum EPreset { + BIP44 = 'BIP44', + electrum = 'electrum' +} + +export interface IAddProfileParams { + mnemonic?: TMnemonic +} + +export interface ICreateAddressesParams { + coin: 'BTC' | 'LTC' | 'ETH' + preset: EPreset +} + +export interface INetwork { + type: ENetworkType + settings: { + port: number + magic: number + messagePrefix: string + base58prefix: { + pubKeyHash: number + scriptHash: number + privateKeyWIF: number + publicKeyBIP32: number + privateKeyBIP32: number + } + bip44: { + coinIndex: number + } + } +} + +export interface ICoin { + symbol: string + name: string + precision: number + networks: { + [key: string]: INetwork + } + profileFromMnemonic: ({ mnemonic: TMnemonic, network: TNetworkName, addressIndex: number }) => TProfile +} diff --git a/tests/unit/api.spec.ts b/tests/unit/api.spec.ts index cd4ec95..d381fc4 100644 --- a/tests/unit/api.spec.ts +++ b/tests/unit/api.spec.ts @@ -1,29 +1,144 @@ -import coins from '@/crypto/coins' +import crypto from '@/crypto' -import { mnemonic, references } from './references' +const mnemonic = 'sudden expire elegant spend they peanut search giggle battle gas sister atom' describe('createProfile', () => { - it('Has references for the test', () => { - expect(references.length).toBe(2) + it('Can create ETH profiles', () => { + const profile0 = crypto.profileFromMnemonic({ + coin: 'ETH', + mnemonic, + addressIndex: 0 + }) + + expect(profile0).toStrictEqual({ + privateKey: '0x630e555b485821671b984fbb481614371fa96c3856c103b8a8155397a3e8a81c', + publicKey: '0x03cf3150838500d4abe82822fe624f654e7f7230420ffabfcadfbbbb60bc2166e6', + address: '0x8707b05F4659b28E8Fe10e2b439625cc34FF0096' + }) + + const profile1 = crypto.profileFromMnemonic({ + coin: 'ETH', + mnemonic, + addressIndex: 1 + }) + + expect(profile1).toStrictEqual({ + privateKey: '0xf3cdfda12a4a70a22edb2cfafcbd26dfa33e0b8cd956aba608bff659fdc01aac', + publicKey: '0x022d9acca03052e670ea6e69ddb787abc60ea251b62894d6433018de8960434409', + address: '0x49Fc2F8A6CF9bEC41d47Fd4017eF54646c97649e' + }) + }) + + it('Can create BTC-testnet profiles', () => { + const profile0 = crypto.profileFromMnemonic({ + coin: 'BTC', + network: 'testnet', + mnemonic, + addressIndex: 0 + }) + + expect(profile0).toStrictEqual({ + privateKey: 'cUA5k263Wd8cA6M4StxyJNdrXs4rKk2h88rxATcHunrxaxtpMMXw', + publicKey: '02baacab92e5563ca1cb785f412ccf69ef6979d5b6e8b01c01a7e5cc7612051eab', + address: 'mnkxYF6DZVSeQrQjusHq7xgECTVPp4w24H' + }) + + const profile1 = crypto.profileFromMnemonic({ + coin: 'BTC', + network: 'testnet', + mnemonic, + addressIndex: 1 + }) + + expect(profile1).toStrictEqual({ + privateKey: 'cVL8jcxeQzGjbb5ctrGwB1eBMXWGU1TZPhP1x65jmLNTPvgbHwTy', + publicKey: '03c52b8ba5981e1b8d706191959c4c1793fac4b8f2bfbb0811ebdbc4d8288a4f80', + address: 'n1diZdNDBJPhd22vuXQvsqAdsfqhaWcy7R' + }) }) - references.forEach(refItem => { - it(`Can create a profile: ${refItem.coin} - ${refItem.network}`, () => { - const coin = coins[refItem.coin] - // const network = coin.networks[refItem.network] - const profile0 = coin.profileFromMnemonic({ - mnemonic, - netName: refItem.network, - index: 0 - }) - const profile1 = coin.profileFromMnemonic({ - mnemonic, - netName: refItem.network, - index: 1 - }) - - expect(profile0).toStrictEqual(refItem.derived[0]) - expect(profile1).toStrictEqual(refItem.derived[1]) + it('Can create BTC-mainnet profiles', () => { + const profile0 = crypto.profileFromMnemonic({ + coin: 'BTC', + network: 'mainnet', + mnemonic, + addressIndex: 0 + }) + + expect(profile0).toStrictEqual({ + privateKey: 'L2opBasCx47tgK9h4dP7r9kVRCjyBu7Z47fByBuamYwHYbzbP42g', + publicKey: '029f7b0a848819c4e19c2282572a821b0a0dc265128f515af292fc84f81c4b1a3f', + address: '1P8HdnAAFkJDbbKnfV3721KS9cVyX59x5j' + }) + + const profile1 = crypto.profileFromMnemonic({ + coin: 'BTC', + network: 'mainnet', + mnemonic, + addressIndex: 1 + }) + + expect(profile1).toStrictEqual({ + privateKey: 'L2ZxxB7sNQjAmwVC7QQxRGypDQjKRNcEBH4JVyGihV6MVpDU754X', + publicKey: '03a54a8e72d16f51149ed1064f2bf23a6a208734db20567167a05de5b9652e26ed', + address: '1PtoSLHfgnrhg1piupG1obmvsdLdxeELyC' + }) + }) + /* + it('Can create LTC-testnet profiles', () => { + const profile0 = crypto.profileFromMnemonic({ + coin: 'LTC', + network: 'testnet', + mnemonic, + addressIndex: 0 + }) + + expect(profile0).toStrictEqual({ + privateKey: 'cUA5k263Wd8cA6M4StxyJNdrXs4rKk2h88rxATcHunrxaxtpMMXw', + publicKey: '02baacab92e5563ca1cb785f412ccf69ef6979d5b6e8b01c01a7e5cc7612051eab', + address: 'mnkxYF6DZVSeQrQjusHq7xgECTVPp4w24H' + }) + + const profile1 = crypto.profileFromMnemonic({ + coin: 'LTC', + network: 'testnet', + mnemonic, + addressIndex: 1 + }) + + expect(profile1).toStrictEqual({ + privateKey: 'cVL8jcxeQzGjbb5ctrGwB1eBMXWGU1TZPhP1x65jmLNTPvgbHwTy', + publicKey: '03c52b8ba5981e1b8d706191959c4c1793fac4b8f2bfbb0811ebdbc4d8288a4f80', + address: 'n1diZdNDBJPhd22vuXQvsqAdsfqhaWcy7R' + }) + }) + + it('Can create LTC-mainnet profiles', () => { + const profile0 = crypto.profileFromMnemonic({ + coin: 'LTC', + network: 'mainnet', + mnemonic, + addressIndex: 0 + }) + + expect(profile0).toStrictEqual({ + privateKey: 'T8LYWa1wvC317PSytdpHBbzviijKuuqyLywT7FCEXupEuAuUwPPo', + publicKey: '026e9e26d004b5fb1da69e7349c32642005cc800002121ef48dad30a212b1dd47b', + address: 'LggM6A4J2NzpZYPzMEGLCBSpRJZrcMML8n' + }) + + const profile1 = crypto.profileFromMnemonic({ + coin: 'LTC', + network: 'mainnet', + mnemonic, + addressIndex: 1 + }) + + expect(profile1).toStrictEqual({ + privateKey: 'T5GdLM1LcrWkbgx4iWJpno1WpgfAGVVJFzCc16ZuZR1VP25LTGB4', + publicKey: '031104acaef56672a73363894f110d5ae12946fb47cc932be85339dfbc16e6cf21', + address: 'LfaPnMj5jYcGMMD6fiXbCq9y1aR6oa5qt1' }) }) + */ }) diff --git a/tests/unit/references.ts b/tests/unit/references.ts deleted file mode 100644 index c502c93..0000000 --- a/tests/unit/references.ts +++ /dev/null @@ -1,101 +0,0 @@ -export const mnemonic = 'sudden expire elegant spend they peanut search giggle battle gas sister atom' - -export const references = [ - /* { - coin: 'BTC', - network: 'mainnet', - derived: [ - { - privateKey: 'L2opBasCx47tgK9h4dP7r9kVRCjyBu7Z47fByBuamYwHYbzbP42g', - publicKey: '029f7b0a848819c4e19c2282572a821b0a0dc265128f515af292fc84f81c4b1a3f', - address: '1P8HdnAAFkJDbbKnfV3721KS9cVyX59x5j' - }, - { - privateKey: 'L2ZxxB7sNQjAmwVC7QQxRGypDQjKRNcEBH4JVyGihV6MVpDU754X', - publicKey: '03a54a8e72d16f51149ed1064f2bf23a6a208734db20567167a05de5b9652e26ed', - address: '1PtoSLHfgnrhg1piupG1obmvsdLdxeELyC' - } - ] - }, - { - coin: 'BTC', - network: 'testnet', - derived: [ - { - privateKey: 'cUA5k263Wd8cA6M4StxyJNdrXs4rKk2h88rxATcHunrxaxtpMMXw', - publicKey: '02baacab92e5563ca1cb785f412ccf69ef6979d5b6e8b01c01a7e5cc7612051eab', - address: 'mnkxYF6DZVSeQrQjusHq7xgECTVPp4w24H' - }, - { - privateKey: 'cVL8jcxeQzGjbb5ctrGwB1eBMXWGU1TZPhP1x65jmLNTPvgbHwTy', - publicKey: '03c52b8ba5981e1b8d706191959c4c1793fac4b8f2bfbb0811ebdbc4d8288a4f80', - address: 'n1diZdNDBJPhd22vuXQvsqAdsfqhaWcy7R' - } - ] - }, - { - coin: 'LTC', - network: 'mainnet', - derived: [ - { - privateKey: 'T8LYWa1wvC317PSytdpHBbzviijKuuqyLywT7FCEXupEuAuUwPPo', - publicKey: '026e9e26d004b5fb1da69e7349c32642005cc800002121ef48dad30a212b1dd47b', - address: 'LggM6A4J2NzpZYPzMEGLCBSpRJZrcMML8n' - }, - { - privateKey: 'T5GdLM1LcrWkbgx4iWJpno1WpgfAGVVJFzCc16ZuZR1VP25LTGB4', - publicKey: '031104acaef56672a73363894f110d5ae12946fb47cc932be85339dfbc16e6cf21', - address: 'LfaPnMj5jYcGMMD6fiXbCq9y1aR6oa5qt1' - } - ] - }, - { - coin: 'LTC', - network: 'testnet', - derived: [ - { - privateKey: 'cUA5k263Wd8cA6M4StxyJNdrXs4rKk2h88rxATcHunrxaxtpMMXw', - publicKey: '02baacab92e5563ca1cb785f412ccf69ef6979d5b6e8b01c01a7e5cc7612051eab', - address: 'mnkxYF6DZVSeQrQjusHq7xgECTVPp4w24H' - }, - { - privateKey: 'cVL8jcxeQzGjbb5ctrGwB1eBMXWGU1TZPhP1x65jmLNTPvgbHwTy', - publicKey: '03c52b8ba5981e1b8d706191959c4c1793fac4b8f2bfbb0811ebdbc4d8288a4f80', - address: 'n1diZdNDBJPhd22vuXQvsqAdsfqhaWcy7R' - } - ] - }, */ - { - coin: 'ETH', - network: 'mainnet', - derived: [ - { - privateKey: '0x630e555b485821671b984fbb481614371fa96c3856c103b8a8155397a3e8a81c', - publicKey: '0x03cf3150838500d4abe82822fe624f654e7f7230420ffabfcadfbbbb60bc2166e6', - address: '0x8707b05F4659b28E8Fe10e2b439625cc34FF0096' - }, - { - privateKey: '0xf3cdfda12a4a70a22edb2cfafcbd26dfa33e0b8cd956aba608bff659fdc01aac', - publicKey: '0x022d9acca03052e670ea6e69ddb787abc60ea251b62894d6433018de8960434409', - address: '0x49Fc2F8A6CF9bEC41d47Fd4017eF54646c97649e' - } - ] - }, - { - coin: 'ETH', - network: 'ropsten', - derived: [ - // the same as on mainnet - { - privateKey: '0x630e555b485821671b984fbb481614371fa96c3856c103b8a8155397a3e8a81c', - publicKey: '0x03cf3150838500d4abe82822fe624f654e7f7230420ffabfcadfbbbb60bc2166e6', - address: '0x8707b05F4659b28E8Fe10e2b439625cc34FF0096' - }, - { - privateKey: '0xf3cdfda12a4a70a22edb2cfafcbd26dfa33e0b8cd956aba608bff659fdc01aac', - publicKey: '0x022d9acca03052e670ea6e69ddb787abc60ea251b62894d6433018de8960434409', - address: '0x49Fc2F8A6CF9bEC41d47Fd4017eF54646c97649e' - } - ] - } -]