diff --git a/packages/bitcore-wallet-client/package.json b/packages/bitcore-wallet-client/package.json index b13e9ffc81f..65e44e05f25 100644 --- a/packages/bitcore-wallet-client/package.json +++ b/packages/bitcore-wallet-client/package.json @@ -32,6 +32,7 @@ "bitcore-lib": "^8.6.0", "bitcore-lib-cash": "^8.6.0", "bitcore-mnemonic": "^8.6.0", + "crypto-wallet-core": "^8.6.0", "json-stable-stringify": "^1.0.1", "lodash": "^4.17.15", "preconditions": "^2.2.3", diff --git a/packages/bitcore-wallet-client/src/lib/api.ts b/packages/bitcore-wallet-client/src/lib/api.ts index be05be23f0a..243e5e50409 100644 --- a/packages/bitcore-wallet-client/src/lib/api.ts +++ b/packages/bitcore-wallet-client/src/lib/api.ts @@ -2,6 +2,7 @@ import { EventEmitter } from 'events'; import _ from 'lodash'; +import * as CWC from 'crypto-wallet-core'; import sjcl from 'sjcl'; import { Constants, Utils } from './common'; import { Credentials } from './credentials'; @@ -17,7 +18,8 @@ var events = require('events'); var Bitcore = require('bitcore-lib'); var Bitcore_ = { btc: Bitcore, - bch: require('bitcore-lib-cash') + bch: require('bitcore-lib-cash'), + eth: Bitcore }; var Mnemonic = require('bitcore-mnemonic'); var url = require('url'); @@ -51,6 +53,7 @@ export class API extends EventEmitter { static PayPro = PayPro; static Key = Key; static Verifier = Verifier; + static Core = CWC; static Utils = Utils; static sjcl = sjcl; static errors = Errors; @@ -2328,6 +2331,8 @@ export class API extends EventEmitter { // coin, network, multisig ['btc', 'livenet'], ['bch', 'livenet'], + ['eth', 'livenet'], + ['eth', 'testnet'], ['btc', 'livenet', true], ['bch', 'livenet', true] ]; diff --git a/packages/bitcore-wallet-client/src/lib/common/constants.ts b/packages/bitcore-wallet-client/src/lib/common/constants.ts index b3af657b750..3dc22e69fe1 100644 --- a/packages/bitcore-wallet-client/src/lib/common/constants.ts +++ b/packages/bitcore-wallet-client/src/lib/common/constants.ts @@ -12,6 +12,7 @@ export const Constants = { BIP48: 'BIP48', }, PATHS: { + SINGLE_ADDRESS: "m/0/0", REQUEST_KEY: "m/1'/0", // TXPROPOSAL_KEY: "m/1'/1", REQUEST_KEY_AUTH: 'm/2', // relative to BASE @@ -29,6 +30,28 @@ export const Constants = { minDecimals: 2, } }, + bch: { + toSatoshis: 100000000, + full: { + maxDecimals: 8, + minDecimals: 8, + }, + short: { + maxDecimals: 6, + minDecimals: 2, + } + }, + eth: { + toSatoshis: 1e18, + full: { + maxDecimals: 8, + minDecimals: 8, + }, + short: { + maxDecimals: 6, + minDecimals: 2, + } + }, bit: { toSatoshis: 100, full: { @@ -41,5 +64,6 @@ export const Constants = { } }, }, - COINS: ['btc', 'bch'] + COINS: ['btc', 'bch', 'eth'], + UTXO_COINS: ['btc', 'bch'] }; diff --git a/packages/bitcore-wallet-client/src/lib/common/utils.ts b/packages/bitcore-wallet-client/src/lib/common/utils.ts index 9c4a37fa6b2..3cdc83c573d 100644 --- a/packages/bitcore-wallet-client/src/lib/common/utils.ts +++ b/packages/bitcore-wallet-client/src/lib/common/utils.ts @@ -2,6 +2,7 @@ import * as _ from 'lodash'; import { Constants } from './constants'; +import { Deriver, Transactions } from 'crypto-wallet-core'; import { Defaults } from './defaults'; var $ = require('preconditions').singleton(); @@ -11,7 +12,8 @@ var Stringify = require('json-stable-stringify'); var Bitcore = require('bitcore-lib'); var Bitcore_ = { btc: Bitcore, - bch: require('bitcore-lib-cash') + bch: require('bitcore-lib-cash'), + eth: Bitcore }; var PrivateKey = Bitcore.PrivateKey; var PublicKey = Bitcore.PublicKey; @@ -135,6 +137,13 @@ export class Utils { return [toAddress, amount, message || '', payProUrl || ''].join('|'); } + static parseDerivationPath = function(path: string) { + const pathIndex = /m\/([0-9]*)\/([0-9]*)/; + const [_input, changeIndex, addressIndex] = path.match(pathIndex); + const isChange = Number.parseInt(changeIndex) > 0; + return { _input, addressIndex, isChange }; + } + static deriveAddress(scriptType, publicKeyRing, path, m, network, coin) { $.checkArgument(_.includes(_.values(Constants.SCRIPT_TYPES), scriptType)); @@ -152,7 +161,19 @@ export class Utils { break; case Constants.SCRIPT_TYPES.P2PKH: $.checkState(_.isArray(publicKeys) && publicKeys.length == 1); - bitcoreAddress = bitcore.Address.fromPublicKey(publicKeys[0], network); + if (Constants.UTXO_COINS.includes(coin)) { + bitcoreAddress = bitcore.Address.fromPublicKey(publicKeys[0], network); + } else { + const { addressIndex, isChange } = this.parseDerivationPath(path); + const [{ xPubKey }] = publicKeyRing; + bitcoreAddress = Deriver.deriveAddress( + coin.toUpperCase(), + network, + xPubKey, + addressIndex, + isChange + ); + } break; } @@ -231,79 +252,81 @@ export class Utils { static buildTx(txp) { var coin = txp.coin || 'btc'; - var bitcore = Bitcore_[coin]; + if (Constants.UTXO_COINS.includes(coin)) { - var t = new bitcore.Transaction(); + var bitcore = Bitcore_[coin]; - $.checkState(_.includes(_.values(Constants.SCRIPT_TYPES), txp.addressType)); + var t = new bitcore.Transaction(); - switch (txp.addressType) { - case Constants.SCRIPT_TYPES.P2SH: - _.each(txp.inputs, i => { - t.from(i, i.publicKeys, txp.requiredSignatures); - }); - break; - case Constants.SCRIPT_TYPES.P2PKH: - t.from(txp.inputs); - break; - } + $.checkState(_.includes(_.values(Constants.SCRIPT_TYPES), txp.addressType)); - if (txp.toAddress && txp.amount && !txp.outputs) { - t.to(txp.toAddress, txp.amount); - } else if (txp.outputs) { - _.each(txp.outputs, o => { - $.checkState( - o.script || o.toAddress, - 'Output should have either toAddress or script specified' - ); - if (o.script) { - t.addOutput( - new bitcore.Transaction.Output({ + switch (txp.addressType) { + case Constants.SCRIPT_TYPES.P2SH: + _.each(txp.inputs, (i) => { + t.from(i, i.publicKeys, txp.requiredSignatures); + }); + break; + case Constants.SCRIPT_TYPES.P2PKH: + t.from(txp.inputs); + break; + } + + if (txp.toAddress && txp.amount && !txp.outputs) { + t.to(txp.toAddress, txp.amount); + } else if (txp.outputs) { + _.each(txp.outputs, (o) => { + $.checkState(o.script || o.toAddress, 'Output should have either toAddress or script specified'); + if (o.script) { + t.addOutput(new bitcore.Transaction.Output({ script: o.script, satoshis: o.amount - }) - ); - } else { - t.to(o.toAddress, o.amount); - } - }); - } + })); + } else { + t.to(o.toAddress, o.amount); + } + }); + } - t.fee(txp.fee); - t.change(txp.changeAddress.address); + t.fee(txp.fee); + t.change(txp.changeAddress.address); - // Shuffle outputs for improved privacy - if (t.outputs.length > 1) { - var outputOrder = _.reject(txp.outputOrder, order => { - return order >= t.outputs.length; - }); - $.checkState(t.outputs.length == outputOrder.length); - t.sortOutputs(outputs => { - return _.map(outputOrder, i => { - return outputs[i]; + // Shuffle outputs for improved privacy + if (t.outputs.length > 1) { + var outputOrder = _.reject(txp.outputOrder, (order) => { + return order >= t.outputs.length; }); - }); - } + $.checkState(t.outputs.length == outputOrder.length); + t.sortOutputs((outputs) => { + return _.map(outputOrder, (i) => { + return outputs[i]; + }); + }); + } - // Validate inputs vs outputs independently of Bitcore - var totalInputs = _.reduce( - txp.inputs, - (memo, i) => { + // Validate inputs vs outputs independently of Bitcore + var totalInputs = _.reduce(txp.inputs, (memo, i) => { return +i.satoshis + memo; - }, - 0 - ); - var totalOutputs = _.reduce( - t.outputs, - (memo, o) => { + }, 0); + var totalOutputs = _.reduce(t.outputs, (memo, o) => { return +o.satoshis + memo; - }, - 0 - ); - - $.checkState(totalInputs - totalOutputs >= 0); - $.checkState(totalInputs - totalOutputs <= Defaults.MAX_TX_FEE); - - return t; + }, 0); + + $.checkState(totalInputs - totalOutputs >= 0); + $.checkState(totalInputs - totalOutputs <= Defaults.MAX_TX_FEE); + + return t; + } else { + const { outputs, amount, from, nonce, gasPrice, data, gasLimit } = txp; + const rawTx = Transactions.create({ + chain: coin.toUpperCase(), + recipients: [{ address: outputs[0].toAddress, amount }], + from, + nonce, + fee: gasPrice, + data, + gasLimit + }); + return { uncheckedSerialize: () => rawTx }; + } } } diff --git a/packages/bitcore-wallet-client/src/lib/credentials.ts b/packages/bitcore-wallet-client/src/lib/credentials.ts index b0a05b8f03e..9bdaf2fe60d 100644 --- a/packages/bitcore-wallet-client/src/lib/credentials.ts +++ b/packages/bitcore-wallet-client/src/lib/credentials.ts @@ -148,7 +148,7 @@ export class Credentials { } var coin = '0'; - if (this.network != 'livenet') { + if (this.network != 'livenet' && this.coin !== 'eth') { coin = '1'; } else if (this.coin == 'bch') { if (this.use145forBCH) { @@ -158,6 +158,8 @@ export class Credentials { } } else if (this.coin == 'btc') { coin = '0'; + } else if (this.coin == 'eth') { + coin = '60'; } else { throw new Error('unknown coin: ' + this.coin); } diff --git a/packages/bitcore-wallet-client/src/lib/key.ts b/packages/bitcore-wallet-client/src/lib/key.ts index da925480423..73ba83cb015 100644 --- a/packages/bitcore-wallet-client/src/lib/key.ts +++ b/packages/bitcore-wallet-client/src/lib/key.ts @@ -5,6 +5,8 @@ import * as _ from 'lodash'; import { Constants, Utils } from './common'; import { Credentials } from './credentials'; +import { Transactions } from 'crypto-wallet-core'; + var Bitcore = require('bitcore-lib'); var Mnemonic = require('bitcore-mnemonic'); var sjcl = require('sjcl'); @@ -278,7 +280,7 @@ export class Key { let purpose = opts.n == 1 || this.use44forMultisig ? '44' : '48'; var coinCode = '0'; - if (opts.network == 'testnet') { + if (opts.network == 'testnet' && opts.coin !== 'eth') { coinCode = '1'; } else if (opts.coin == 'bch') { if (this.use0forBCH) { @@ -383,28 +385,38 @@ export class Key { var derived = this.derive(password, rootPath); var xpriv = new Bitcore.HDPrivateKey(derived); - _.each(txp.inputs, function (i) { - $.checkState( - i.path, - 'Input derivation path not available (signing transaction)' - ); - if (!derived[i.path]) { - derived[i.path] = xpriv.deriveChild(i.path).privateKey; - privs.push(derived[i.path]); - } - }); - var t = Utils.buildTx(txp); - var signatures = _.map(privs, function (priv, i) { - return t.getSignatures(priv); - }); - signatures = _.map(_.sortBy(_.flatten(signatures), 'inputIndex'), function ( - s - ) { - return s.signature.toDER().toString('hex'); - }); + if (Constants.UTXO_COINS.includes(txp.coin)) { + _.each(txp.inputs, function (i) { + $.checkState(i.path, 'Input derivation path not available (signing transaction)'); + if (!derived[i.path]) { + derived[i.path] = xpriv.deriveChild(i.path).privateKey; + privs.push(derived[i.path]); + } + }); + + var signatures = _.map(privs, function (priv, i) { + return t.getSignatures(priv); + }); - return signatures; + signatures = _.map(_.sortBy(_.flatten(signatures), 'inputIndex'), function (s) { + return s.signature.toDER().toString('hex'); + }); + + return signatures; + } else { + const addressPath = Constants.PATHS.SINGLE_ADDRESS; + const privKey = xpriv.deriveChild(addressPath).privateKey; + const tx = t.uncheckedSerialize(); + const signedRawTx = Transactions.sign({ + chain: txp.coin.toUpperCase(), + tx, + key: { privKey: privKey.toString('hex') }, + from: txp.from + }); + + return Object.assign(txp, { rawTx: signedRawTx, status: 'accepted' }); + } }; } diff --git a/packages/bitcore-wallet-client/test/api.test.js b/packages/bitcore-wallet-client/test/api.test.js index 9558edf0fc2..77d777589aa 100644 --- a/packages/bitcore-wallet-client/test/api.test.js +++ b/packages/bitcore-wallet-client/test/api.test.js @@ -941,6 +941,7 @@ describe('client API', () => { var utxos = helpers.generateUtxos('P2SH', publicKeyRing, 'm/2147483647/0/0', 1, [1000, 2000]); var txp = { inputs: utxos, + coin: 'btc', toAddress: toAddress, amount: 1200, changeAddress: { @@ -971,6 +972,7 @@ describe('client API', () => { var txp = { inputs: utxos, toAddress: toAddress, + coin: 'btc', amount: 1200, changeAddress: { address: changeAddress @@ -999,6 +1001,7 @@ describe('client API', () => { var utxos = helpers.generateUtxos('P2PKH', publicKeyRing, 'm/1/0', 1, [1000, 2000]); var txp = { inputs: utxos, + coin: 'btc', outputs: [{ toAddress: toAddress, amount: 800, @@ -1034,6 +1037,7 @@ describe('client API', () => { var txp = { inputs: utxos, type: 'external', + coin: 'btc', outputs: [{ "amount": 700, "script": "512103ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff210314a96cd6f5a20826070173fe5b7e9797f21fc8ca4a55bcb2d2bde99f55dd352352ae" @@ -1069,6 +1073,7 @@ describe('client API', () => { var utxos = helpers.generateUtxos('P2PKH', publicKeyRing, 'm/1/0', 1, [1000, 2000]); var txp = { version: 3, + coin: 'btc', inputs: utxos, outputs: [{ toAddress: toAddress,