Browse Source

Remove ledger-test-library. Prepare sync.

master
meriadec 7 years ago
parent
commit
01ab8b599d
No known key found for this signature in database GPG Key ID: 1D2FC2305E2CB399
  1. 2
      package.json
  2. 241
      src/helpers/btc.js
  3. 31
      src/internals/accounts/sync.js
  4. 151
      src/internals/usb/wallet/accounts.js
  5. 33
      src/internals/usb/wallet/index.js
  6. 33
      yarn.lock

2
package.json

@ -44,7 +44,6 @@
"babel-runtime": "^6.26.0",
"bcryptjs": "^2.4.3",
"bitcoinjs-lib": "^3.3.2",
"bs58check": "^2.1.1",
"color": "^3.0.0",
"cross-env": "^5.1.4",
"d3": "^5.1.0",
@ -57,7 +56,6 @@
"i18next": "^11.2.2",
"i18next-node-fs-backend": "^1.0.0",
"invariant": "^2.2.4",
"ledger-test-library": "KhalilBellakrid/ledger-test-library-nodejs#7d37482",
"lodash": "^4.17.5",
"moment": "^2.22.1",
"object-path": "^0.11.4",

241
src/helpers/btc.js

@ -1,241 +0,0 @@
// @flow
import { coinTypeForId } from 'internals/usb/wallet/accounts'
import ledger from 'ledger-test-library'
import bitcoin from 'bitcoinjs-lib'
import axios from 'axios'
import type { OperationRaw } from '@ledgerhq/live-common/lib/types'
import groupBy from 'lodash/groupBy'
import noop from 'lodash/noop'
import uniqBy from 'lodash/uniqBy'
const GAP_LIMIT_ADDRESSES = 20
export const networks = {
bitcoin: {
...bitcoin.networks.bitcoin,
family: 1,
},
bitcoin_testnet: {
...bitcoin.networks.testnet,
family: 1,
},
}
export function computeOperation(addresses: Array<string>, accountId: string) {
return (t: Object) => {
const outputVal = t.outputs
.filter(o => addresses.includes(o.address))
.reduce((acc, cur) => acc + cur.value, 0)
const inputVal = t.inputs
.filter(i => addresses.includes(i.address))
.reduce((acc, cur) => acc + cur.value, 0)
const amount = outputVal - inputVal
return {
id: t.hash,
hash: t.hash,
address: t.amount > 0 ? t.inputs[0].address : t.outputs[0].address,
from: t.inputs.map(t => t.address),
to: t.outputs.map(t => t.address),
amount,
confirmations: t.confirmations,
date: t.received_at,
accountId,
blockHeight: t.block.height,
}
}
}
export function getBalanceByDay(operations: OperationRaw[]) {
const txsByDate = groupBy(operations, tx => {
const [date] = new Date(tx.date).toISOString().split('T')
return date
})
let balance = 0
return Object.keys(txsByDate)
.sort()
.reduce((result, k) => {
const txs = txsByDate[k]
balance += txs.reduce((r, v) => {
r += v.amount
return r
}, 0)
result[k] = balance
return result
}, {})
}
export async function getAccount({
rootPath,
allAddresses = [],
allTxsHash = [],
currentIndex = 0,
hdnode,
segwit,
network,
currencyId,
accountId,
asyncDelay = 250,
onProgress = noop,
}: {
rootPath: string,
allAddresses?: Array<string>,
allTxsHash?: Array<string>,
currentIndex?: number,
hdnode: Object,
segwit: boolean,
currencyId: string,
accountId: string,
network: Object,
asyncDelay?: number,
onProgress?: Function,
}): Promise<any> {
const script = segwit ? parseInt(network.scriptHash, 10) : parseInt(network.pubKeyHash, 10)
let balance = 0
let operations = []
let lastAddress = null
const pubKeyToSegwitAddress = (pubKey, scriptVersion) => {
const script = [0x00, 0x14].concat(Array.from(bitcoin.crypto.hash160(pubKey)))
const hash160 = bitcoin.crypto.hash160(new Uint8Array(script))
return bitcoin.address.toBase58Check(hash160, scriptVersion)
}
const getPublicAddress = ({ hdnode, path, script, segwit }) => {
hdnode = hdnode.derivePath(path)
if (!segwit) {
return hdnode.getAddress().toString()
}
return pubKeyToSegwitAddress(hdnode.getPublicKeyBuffer(), script)
}
const getPath = (type, index) => `${type === 'external' ? 0 : 1}/${index}`
const getAddress = ({ type, index }) => {
const p = getPath(type, index)
return {
type,
index,
path: `${rootPath}/${p}`,
address: getPublicAddress({ hdnode, path: p, script, segwit }),
}
}
const getAsyncAddress = params =>
new Promise(resolve => setTimeout(() => resolve(getAddress(params)), asyncDelay))
const getLastAddress = (addresses, txs) => {
const txsAddresses = [...txs.inputs.map(t => t.address), ...txs.outputs.map(t => t.address)]
return addresses.find(a => txsAddresses.includes(a.address)) || null
}
const nextPath = (index = 0) =>
Array.from(new Array(GAP_LIMIT_ADDRESSES).keys())
.reduce(
(promise, v) =>
promise.then(async results => {
const result = await Promise.all([
getAsyncAddress({ type: 'external', index: v + index }),
getAsyncAddress({ type: 'internal', index: v + index }),
])
return [...results, result]
}),
Promise.resolve([]),
)
.then(async results => {
const addresses = results.reduce((result, v) => [...result, ...v], [])
const listAddresses = addresses.map(a => a.address)
allAddresses = [...new Set([...allAddresses, ...listAddresses])]
let txs = []
const operationsOpts = { coin_type: coinTypeForId(currencyId) }
try {
txs = await ledger.getTransactions(listAddresses, operationsOpts)
txs = txs.filter(t => !allTxsHash.includes(t.hash)).reverse()
} catch (e) {
console.log('getOperations', e) // eslint-disable-line no-console
}
const hasOperations = txs.length > 0
if (hasOperations) {
const newOperations = txs.map(computeOperation(allAddresses, accountId))
const txHashs = operations.map(t => t.id)
balance = newOperations
.filter(t => !txHashs.includes(t.id))
.reduce((result, v) => result + v.amount, balance)
lastAddress = getLastAddress(addresses, txs[0])
operations = uniqBy([...operations, ...newOperations], t => t.id)
onProgress({
balance,
operations,
balanceByDay: getBalanceByDay(operations),
})
return nextPath(index + (GAP_LIMIT_ADDRESSES - 1))
}
const { type, ...nextAddress } =
lastAddress !== null
? getAddress({
type: 'external',
index: lastAddress.index + 1,
})
: getAddress({ type: 'external', index: 0 })
// TODO: in the future, it should be done with the libc call
const {
data: { height: blockHeight },
} = await axios.get('https://api.ledgerwallet.com/blockchain/v2/btc_testnet/blocks/current')
const account = {
...nextAddress,
currencyId,
addresses: operations.length > 0 ? allAddresses : [],
balance,
balanceByDay: getBalanceByDay(operations),
rootPath,
operations,
lastSyncDate: new Date(),
blockHeight,
}
onProgress({
...account,
finish: true,
})
return account
})
if (allAddresses.length === 0 && currentIndex > 0) {
for (let i = currentIndex; i--; ) {
allAddresses = [
...allAddresses,
getAddress({ type: 'internal', index: i }).address,
getAddress({ type: 'external', index: i }).address,
]
}
}
return nextPath(currentIndex)
}
export function getHDNode({ xpub58, network }: { xpub58: string, network: Object }) {
return bitcoin.HDNode.fromBase58(xpub58, network)
}

31
src/internals/accounts/sync.js

@ -1,34 +1,7 @@
// @flow
import { getAccount, getHDNode, networks } from 'helpers/btc'
const network = networks[1]
function syncAccount({ id, operations, ...currentAccount }) {
const hdnode = getHDNode({ xpub58: id, network })
const allTxsHash = operations.map(t => t.id)
return getAccount({ hdnode, network, allTxsHash, segwit: true, ...currentAccount }).then(
account => ({
id,
...account,
}),
)
}
export default (send: Function) => ({
all: async ({ accounts }: { accounts: Array<Object> }) => {
send('accounts.sync.progress', null, { kill: false })
try {
await Promise.all(
accounts.map(a =>
syncAccount(a).then(account => send('account.sync.success', account, { kill: false })),
),
)
send('accounts.sync.success')
} catch (err) {
send('accounts.sync.fail', err.stack || err)
}
all: () => {
setTimeout(() => send('accounts.sync.success'), 5e3)
},
})

151
src/internals/usb/wallet/accounts.js

@ -2,58 +2,8 @@
/* eslint-disable no-bitwise */
import bitcoin from 'bitcoinjs-lib'
import bs58check from 'bs58check'
import Btc from '@ledgerhq/hw-app-btc'
import { getAccount, getHDNode, networks } from 'helpers/btc'
import { serializeAccounts } from 'reducers/accounts'
async function sleep(delay, callback) {
if (delay !== 0) {
await new Promise(resolve => setTimeout(resolve, delay))
}
return callback()
}
function getCompressPublicKey(publicKey) {
let compressedKeyIndex
if (parseInt(publicKey.substring(128, 130), 16) % 2 !== 0) {
compressedKeyIndex = '03'
} else {
compressedKeyIndex = '02'
}
const result = compressedKeyIndex + publicKey.substring(2, 66)
return result
}
function parseHexString(str: any) {
const result = []
while (str.length >= 2) {
result.push(parseInt(str.substring(0, 2), 16))
str = str.substring(2, str.length)
}
return result
}
function createXpub({ depth, fingerprint, childnum, chainCode, publicKey, network }) {
return [
network.toString(16).padStart(8, '0'),
depth.toString(16).padStart(2, '0'),
fingerprint.toString(16).padStart(8, '0'),
childnum.toString(16).padStart(8, '0'),
chainCode,
publicKey,
].join('')
}
function encodeBase58Check(vchIn) {
vchIn = parseHexString(vchIn)
return bs58check.encode(Buffer.from(vchIn))
}
export function coinTypeForId(id: string) {
if (id === 'bitcoin_testnet') return 1
if (id === 'bitcoin') return 0
@ -87,104 +37,3 @@ export function verifyAddress({
return btc.getWalletPublicKey(path, true, segwit)
}
export default async ({
transport,
currentAccounts,
onProgress,
currencyId = 'bitcoin_testnet',
segwit = true,
nextAccountDelay = 1e3,
}: {
transport: Object,
currentAccounts: Array<*>,
onProgress: Function,
currencyId?: string,
segwit?: boolean,
nextAccountDelay?: number,
}) => {
const btc = new Btc(transport)
const network = networks[currencyId]
const [p2pkh, p2sh, fam] = [network.pubKeyHash, network.scriptHash, network.family].map(v =>
v.toString(16).padStart(4, 0),
)
await transport.exchange(Buffer.from(`e014000005${p2pkh}${p2sh}${fam.substr(-2)}`), [0x9000])
const getPublicKey = path => btc.getWalletPublicKey(path)
let result = bitcoin.crypto.sha256(
await getPublicKey(getPath({ segwit, currencyId })).then(
({ publicKey }) => new Uint8Array(parseHexString(getCompressPublicKey(publicKey))),
),
)
result = bitcoin.crypto.ripemd160(result)
onProgress(null)
const fingerprint = ((result[0] << 24) | (result[1] << 16) | (result[2] << 8) | result[3]) >>> 0
const getXpub58ByPath = async ({ path, account, network }) => {
const { publicKey, chainCode } = await getPublicKey(path)
const compressPublicKey = getCompressPublicKey(publicKey)
const childnum = (0x80000000 | account) >>> 0
const xpub = createXpub({
depth: 3,
fingerprint,
childnum,
chainCode,
publicKey: compressPublicKey,
network: network.bip32.public,
})
return encodeBase58Check(xpub)
}
const getAllAccounts = async (currentAccount = 0, accounts = []) => {
const path = getPath({ segwit, currencyId, account: currentAccount })
const xpub58 = await getXpub58ByPath({ path, account: currentAccount, network })
if (currentAccounts.includes(xpub58)) {
return getAllAccounts(currentAccount + 1, accounts) // Skip existing account
}
const hdnode = getHDNode({ xpub58, network })
const account = await getAccount({
asyncDelay: 0,
currencyId,
accountId: xpub58,
hdnode,
network,
rootPath: path,
segwit,
onProgress: ({ operations, ...progress }) =>
operations.length > 0 &&
onProgress({ id: xpub58, accountIndex: currentAccount, operations, ...progress }),
})
const hasOperations = account.operations.length > 0
accounts.push({
id: xpub58,
...account,
})
if (hasOperations) {
const nextAccount = await sleep(nextAccountDelay, () =>
getAllAccounts(currentAccount + 1, accounts),
)
return nextAccount
}
const result = await sleep(nextAccountDelay, () => accounts)
return serializeAccounts(result)
}
return getAllAccounts()
}

33
src/internals/usb/wallet/index.js

@ -3,17 +3,7 @@
import CommNodeHid from '@ledgerhq/hw-transport-node-hid'
import Btc from '@ledgerhq/hw-app-btc'
import getAllAccounts, { getPath, verifyAddress } from './accounts'
async function getAllAccountsByCurrencyId({ pathDevice, currencyId, currentAccounts, onProgress }) {
const transport = await CommNodeHid.open(pathDevice)
if (currencyId === 'bitcoin_testnet') {
return getAllAccounts({ currencyId, transport, currentAccounts, onProgress })
}
throw new Error('Invalid coinType')
}
import { getPath, verifyAddress } from './accounts'
export default (sendEvent: Function) => ({
getAccounts: async ({
@ -25,26 +15,9 @@ export default (sendEvent: Function) => ({
currencyId: string,
currentAccounts: Array<string>,
}) => {
sendEvent(
'wallet.getAccounts.start',
{
pid: process.pid,
},
{ kill: false },
console.warn(
`NOT IMPLEMENTED: getting account for ${pathDevice} ${currencyId} ${currentAccounts.length}`,
)
try {
const data = await getAllAccountsByCurrencyId({
pathDevice,
currencyId,
currentAccounts,
onProgress: progress => sendEvent('wallet.getAccounts.progress', progress, { kill: false }),
})
sendEvent('wallet.getAccounts.success', data)
} catch (err) {
sendEvent('wallet.getAccounts.fail', err.stack || err)
}
},
verifyAddress: async ({ pathDevice, path }: { pathDevice: string, path: string }) => {
const transport = await CommNodeHid.open(pathDevice)

33
yarn.lock

@ -2148,13 +2148,6 @@ aws4@^1.6.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289"
axios@^0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.17.1.tgz#2d8e3e5d0bdbd7327f91bc814f5c57660f81824d"
dependencies:
follow-redirects "^1.2.5"
is-buffer "^1.1.5"
axios@^0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102"
@ -3510,7 +3503,7 @@ bs58@^4.0.0:
dependencies:
base-x "^3.0.2"
bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.1.1:
bs58check@<3.0.0, bs58check@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.1.tgz#8a5d0e587af97b784bf9cbf1b29f454d82bc0222"
dependencies:
@ -5263,7 +5256,7 @@ electron-builder-lib@~20.9.0:
semver "^5.5.0"
temp-file "^3.1.1"
electron-builder@^20.0.4, electron-builder@^20.9.0:
electron-builder@^20.9.0:
version "20.11.1"
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-20.11.1.tgz#a84feea424ac3abfa0dd1e4818dde8338250e543"
dependencies:
@ -5447,14 +5440,6 @@ electron@1.8.4:
electron-download "^3.0.1"
extract-zip "^1.0.3"
electron@^1.8.2:
version "1.8.6"
resolved "https://registry.yarnpkg.com/electron/-/electron-1.8.6.tgz#6d45e377b321a35b78a794b64e40b2cf8face6ae"
dependencies:
"@types/node" "^8.0.24"
electron-download "^3.0.1"
extract-zip "^1.0.3"
elegant-spinner@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
@ -6280,7 +6265,7 @@ flush-write-stream@^1.0.0:
inherits "^2.0.1"
readable-stream "^2.0.4"
follow-redirects@^1.0.0, follow-redirects@^1.2.5, follow-redirects@^1.3.0:
follow-redirects@^1.0.0, follow-redirects@^1.3.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.4.1.tgz#d8120f4518190f55aac65bb6fc7b85fcd666d6aa"
dependencies:
@ -8266,18 +8251,6 @@ lcid@^1.0.0:
dependencies:
invert-kv "^1.0.0"
ledger-test-library@KhalilBellakrid/ledger-test-library-nodejs#7d37482:
version "1.0.0"
resolved "https://codeload.github.com/KhalilBellakrid/ledger-test-library-nodejs/tar.gz/7d374820ae44f1b7d2177dfe52deb4702a36cb65"
dependencies:
axios "^0.17.1"
bindings "^1.3.0"
electron "^1.8.2"
electron-builder "^20.0.4"
electron-rebuild "^1.7.3"
nan "^2.6.2"
prebuild-install "^2.2.2"
left-pad@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"

Loading…
Cancel
Save