diff --git a/lib/api.js b/lib/api.js index 828906af..40b8b0b5 100644 --- a/lib/api.js +++ b/lib/api.js @@ -652,10 +652,16 @@ API.prototype.decryptBIP38PrivateKey = function(encryptedPrivateKeyBase58, passp return cb(null, privateKeyWif); }; -API.prototype.getBalanceFromPrivateKey = function(privateKey, cb) { +API.prototype.getBalanceFromPrivateKey = function(privateKey, coin, cb) { var self = this; - var privateKey = new Bitcore.PrivateKey(privateKey); + if (_.isFunction(coin)) { + cb = coin; + coin = 'btc'; + } + var B = Bitcore_[coin]; + + var privateKey = new B.PrivateKey(privateKey); var address = privateKey.publicKey.toAddress(); self.getUtxos({ addresses: address.toString(), @@ -671,7 +677,8 @@ API.prototype.buildTxFromPrivateKey = function(privateKey, destinationAddress, o opts = opts || {}; var coin = opts.coin || 'btc'; - var privateKey = new Bitcore.PrivateKey(privateKey); + var B = Bitcore_[coin]; + var privateKey = B.PrivateKey(privateKey); var address = privateKey.publicKey.toAddress(); async.waterfall([ @@ -692,9 +699,9 @@ API.prototype.buildTxFromPrivateKey = function(privateKey, destinationAddress, o var tx; try { - var toAddress = Bitcore.Address.fromString(destinationAddress); + var toAddress = B.Address.fromString(destinationAddress); - tx = new Bitcore_[coin].Transaction() + tx = new B.Transaction() .from(utxos) .to(toAddress, amount) .fee(fee) diff --git a/lib/common/utils.js b/lib/common/utils.js index 44369aec..a8d0e216 100644 --- a/lib/common/utils.js +++ b/lib/common/utils.js @@ -10,7 +10,6 @@ var Bitcore_ = { btc: Bitcore, bch: require('bitcore-lib-cash'), }; -var Address = Bitcore.Address; var PrivateKey = Bitcore.PrivateKey; var PublicKey = Bitcore.PublicKey; var crypto = Bitcore.crypto; @@ -101,22 +100,24 @@ Utils.getProposalHash = function(proposalHeader) { return Stringify(proposalHeader); }; -Utils.deriveAddress = function(scriptType, publicKeyRing, path, m, network) { +Utils.deriveAddress = function(scriptType, publicKeyRing, path, m, network, coin) { $.checkArgument(_.includes(_.values(Constants.SCRIPT_TYPES), scriptType)); + coin = coin || 'btc'; + var bitcore = Bitcore_[coin]; var publicKeys = _.map(publicKeyRing, function(item) { - var xpub = new Bitcore.HDPublicKey(item.xPubKey); + var xpub = new bitcore.HDPublicKey(item.xPubKey); return xpub.deriveChild(path).publicKey; }); var bitcoreAddress; switch (scriptType) { case Constants.SCRIPT_TYPES.P2SH: - bitcoreAddress = Address.createMultisig(publicKeys, m, network); + bitcoreAddress = bitcore.Address.createMultisig(publicKeys, m, network); break; case Constants.SCRIPT_TYPES.P2PKH: $.checkState(_.isArray(publicKeys) && publicKeys.length == 1); - bitcoreAddress = Address.fromPublicKey(publicKeys[0], network); + bitcoreAddress = bitcore.Address.fromPublicKey(publicKeys[0], network); break; } diff --git a/lib/index.js b/lib/index.js index 8ef25a37..e717c7eb 100644 --- a/lib/index.js +++ b/lib/index.js @@ -19,3 +19,4 @@ client.sjcl = require('sjcl'); // Expose bitcore client.Bitcore = require('bitcore-lib'); +client.BitcoreCash = require('bitcore-lib-cash'); diff --git a/lib/verifier.js b/lib/verifier.js index fa674ce7..7e4d3edb 100644 --- a/lib/verifier.js +++ b/lib/verifier.js @@ -25,7 +25,7 @@ function Verifier(opts) {}; Verifier.checkAddress = function(credentials, address) { $.checkState(credentials.isComplete()); - var local = Utils.deriveAddress(address.type || credentials.addressType, credentials.publicKeyRing, address.path, credentials.m, credentials.network); + var local = Utils.deriveAddress(address.type || credentials.addressType, credentials.publicKeyRing, address.path, credentials.m, credentials.network, credentials.coin); return (local.address == address.address && _.difference(local.publicKeys, address.publicKeys).length === 0); }; diff --git a/package.json b/package.json index be1501b8..6154fa3f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "bitcore-wallet-client", "description": "Client for bitcore-wallet-service", "author": "BitPay Inc", - "version": "6.1.0", + "version": "6.2.0", "license": "MIT", "keywords": [ "bitcoin", @@ -37,7 +37,7 @@ "superagent": "^3.4.1" }, "devDependencies": { - "bitcore-wallet-service": "2.0.0", + "bitcore-wallet-service": "2.1.0", "browserify": "^13.1.0", "chai": "^1.9.1", "coveralls": "^2.11.2", diff --git a/test/client.js b/test/client.js index 54bb256d..f4f9693c 100644 --- a/test/client.js +++ b/test/client.js @@ -16,6 +16,12 @@ var tingodb = require('tingodb')({ var log = require('../lib/log'); var Bitcore = require('bitcore-lib'); +var Bitcore_ = { + btc: Bitcore, + bch: require('bitcore-lib-cash'), +}; + + var BitcorePayPro = require('bitcore-payment-protocol'); var BWS = require('bitcore-wallet-service'); @@ -199,13 +205,14 @@ blockchainExplorerMock.getUtxos = function(addresses, cb) { }; blockchainExplorerMock.setUtxo = function(address, amount, m, confirmations) { + var B = Bitcore_[address.coin]; var scriptPubKey; switch (address.type) { case Constants.SCRIPT_TYPES.P2SH: - scriptPubKey = address.publicKeys ? Bitcore.Script.buildMultisigOut(address.publicKeys, m).toScriptHashOut() : ''; + scriptPubKey = address.publicKeys ? B.Script.buildMultisigOut(address.publicKeys, m).toScriptHashOut() : ''; break; case Constants.SCRIPT_TYPES.P2PKH: - scriptPubKey = Bitcore.Script.buildPublicKeyHashOut(address.address); + scriptPubKey = B.Script.buildPublicKeyHashOut(address.address); break; } should.exist(scriptPubKey); @@ -1179,6 +1186,29 @@ describe('client API', function() { }); }); + it('should create a BCH address correctly', function(done) { + var xPriv = 'xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu'; + clients[0].seedFromExtendedPrivateKey(xPriv, { + 'coin': 'bch', + }); + clients[0].createWallet('mycashwallet', 'pepe', 1, 1, { + coin: 'bch' + }, function(err, secret) { + should.not.exist(err); + + clients[0].createAddress(function(err, x) { + should.not.exist(err); + should.not.exist(err); + x.coin.should.equal('bch'); + x.network.should.equal('livenet'); + x.address.should.equal('CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz'); + done(); + }) + }); + }); + + + it('should check balance in a 1-1 ', function(done) { helpers.createAndJoinWallet(clients, 1, 1, function() { clients[0].getBalance({}, function(err, balance) { @@ -2660,7 +2690,7 @@ describe('client API', function() { }); it('Should sign proposal', function(done) { - var toAddress = '1PuKMvRFfwbLXyEPXZzkGi111gMUCs6uE3'; + var toAddress = 'CfNCvxmKYzZsS78pDKKfrDd2doZt3w4jUs'; var opts = { outputs: [{ amount: 1e8, @@ -5130,93 +5160,111 @@ describe('client API', function() { }); }); - describe('Sweep paper wallet', function() { - it.skip('should decrypt bip38 encrypted private key', function(done) { - this.timeout(60000); - clients[0].decryptBIP38PrivateKey('6PfRh9ZnWtiHrGoPPSzXe6iafTXc6FSXDhSBuDvvDmGd1kpX2Gvy1CfTcA', 'passphrase', {}, function(err, result) { - should.not.exist(err); - result.should.equal('5KjBgBiadWGhjWmLN1v4kcEZqWSZFqzgv7cSUuZNJg4tD82c4xp'); - done(); - }); - }); - it.skip('should fail to decrypt bip38 encrypted private key with incorrect passphrase', function(done) { - this.timeout(60000); - clients[0].decryptBIP38PrivateKey('6PfRh9ZnWtiHrGoPPSzXe6iafTXc6FSXDhSBuDvvDmGd1kpX2Gvy1CfTcA', 'incorrect passphrase', {}, function(err, result) { - should.exist(err); - err.message.should.contain('passphrase'); - done(); - }); - }); - it('should get balance from single private key', function(done) { - var address = { - address: '1PuKMvRFfwbLXyEPXZzkGi111gMUCs6uE3', - type: 'P2PKH', - }; - helpers.createAndJoinWallet(clients, 1, 1, function() { - blockchainExplorerMock.setUtxo(address, 123, 1); - clients[0].getBalanceFromPrivateKey('5KjBgBiadWGhjWmLN1v4kcEZqWSZFqzgv7cSUuZNJg4tD82c4xp', function(err, balance) { + var addrMap = { + btc: ['1PuKMvRFfwbLXyEPXZzkGi111gMUCs6uE3','1GG3JQikGC7wxstyavUBDoCJ66bWLLENZC'], + bch: ['CfNCvxmKYzZsS78pDKKfrDd2doZt3w4jUs','CXivsT4p9F6Us1oQGfo6oJpKiDovJjRVUE'] + }; + _.each(['bch', 'btc'], function(coin) { + var addr= addrMap[coin]; + + describe('Sweep paper wallet ' + coin, function() { + var B = Bitcore_[coin]; + it.skip('should decrypt bip38 encrypted private key', function(done) { + this.timeout(60000); + clients[0].decryptBIP38PrivateKey('6PfRh9ZnWtiHrGoPPSzXe6iafTXc6FSXDhSBuDvvDmGd1kpX2Gvy1CfTcA', 'passphrase', {}, function(err, result) { should.not.exist(err); - balance.should.equal(123 * 1e8); + result.should.equal('5KjBgBiadWGhjWmLN1v4kcEZqWSZFqzgv7cSUuZNJg4tD82c4xp'); done(); }); }); - }); - it('should build tx for single private key', function(done) { - var address = { - address: '1PuKMvRFfwbLXyEPXZzkGi111gMUCs6uE3', - type: 'P2PKH', - }; - helpers.createAndJoinWallet(clients, 1, 1, function() { - blockchainExplorerMock.setUtxo(address, 123, 1); - clients[0].buildTxFromPrivateKey('5KjBgBiadWGhjWmLN1v4kcEZqWSZFqzgv7cSUuZNJg4tD82c4xp', '1GG3JQikGC7wxstyavUBDoCJ66bWLLENZC', {}, function(err, tx) { - should.not.exist(err); - should.exist(tx); - tx.outputs.length.should.equal(1); - var output = tx.outputs[0]; - output.satoshis.should.equal(123 * 1e8 - 10000); - var script = new Bitcore.Script.buildPublicKeyHashOut(Bitcore.Address.fromString('1GG3JQikGC7wxstyavUBDoCJ66bWLLENZC')); - output.script.toString('hex').should.equal(script.toString('hex')); + it.skip('should fail to decrypt bip38 encrypted private key with incorrect passphrase', function(done) { + this.timeout(60000); + clients[0].decryptBIP38PrivateKey('6PfRh9ZnWtiHrGoPPSzXe6iafTXc6FSXDhSBuDvvDmGd1kpX2Gvy1CfTcA', 'incorrect passphrase', {}, function(err, result) { + should.exist(err); + err.message.should.contain('passphrase'); done(); }); }); - }); + it('should get balance from single private key', function(done) { + var address = { + address: addr[0], + type: 'P2PKH', + coin: coin, + }; + helpers.createAndJoinWallet(clients, 1, 1, function() { + blockchainExplorerMock.setUtxo(address, 123, 1); + clients[0].getBalanceFromPrivateKey('5KjBgBiadWGhjWmLN1v4kcEZqWSZFqzgv7cSUuZNJg4tD82c4xp', coin, function(err, balance) { + should.not.exist(err); + balance.should.equal(123 * 1e8); + done(); + }); + }); + }); + it('should build tx for single private key', function(done) { + var address = { + address: addr[0], + type: 'P2PKH', + coin: coin, + }; + helpers.createAndJoinWallet(clients, 1, 1, function() { + blockchainExplorerMock.setUtxo(address, 123, 1); + clients[0].buildTxFromPrivateKey('5KjBgBiadWGhjWmLN1v4kcEZqWSZFqzgv7cSUuZNJg4tD82c4xp', addr[1], { + coin: coin + }, function(err, tx) { + should.not.exist(err); + should.exist(tx); + tx.outputs.length.should.equal(1); + var output = tx.outputs[0]; + output.satoshis.should.equal(123 * 1e8 - 10000); + var script = B.Script.buildPublicKeyHashOut(B.Address.fromString(addr[1])); + output.script.toString('hex').should.equal(script.toString('hex')); + done(); + }); + }); + }); - it('should handle tx serialization error when building tx', function(done) { - var sandbox = sinon.sandbox.create(); + it('should handle tx serialization error when building tx', function(done) { + var sandbox = sinon.sandbox.create(); - var se = sandbox.stub(Bitcore.Transaction.prototype, 'serialize', function() { - throw new Error('this is an error'); - }); + var se = sandbox.stub(B.Transaction.prototype, 'serialize', function() { + throw new Error('this is an error'); + }); - var address = { - address: '1PuKMvRFfwbLXyEPXZzkGi111gMUCs6uE3', - type: 'P2PKH', - }; - helpers.createAndJoinWallet(clients, 1, 1, function() { - blockchainExplorerMock.setUtxo(address, 123, 1); - clients[0].buildTxFromPrivateKey('5KjBgBiadWGhjWmLN1v4kcEZqWSZFqzgv7cSUuZNJg4tD82c4xp', '1GG3JQikGC7wxstyavUBDoCJ66bWLLENZC', {}, function(err, tx) { - should.exist(err); - should.not.exist(tx); - err.should.be.an.instanceOf(Errors.COULD_NOT_BUILD_TRANSACTION); - sandbox.restore(); - done(); + var address = { + address: addr[0], + type: 'P2PKH', + coin: coin, + }; + helpers.createAndJoinWallet(clients, 1, 1, function() { + blockchainExplorerMock.setUtxo(address, 123, 1); + clients[0].buildTxFromPrivateKey('5KjBgBiadWGhjWmLN1v4kcEZqWSZFqzgv7cSUuZNJg4tD82c4xp', addr[1], { + coin: coin, + }, function(err, tx) { + should.exist(err); + should.not.exist(tx); + err.should.be.an.instanceOf(Errors.COULD_NOT_BUILD_TRANSACTION); + sandbox.restore(); + done(); + }); }); }); - }); - it('should fail to build tx for single private key if insufficient funds', function(done) { - var address = { - address: '1PuKMvRFfwbLXyEPXZzkGi111gMUCs6uE3', - type: 'P2PKH', - }; - helpers.createAndJoinWallet(clients, 1, 1, function() { - blockchainExplorerMock.setUtxo(address, 123 / 1e8, 1); - clients[0].buildTxFromPrivateKey('5KjBgBiadWGhjWmLN1v4kcEZqWSZFqzgv7cSUuZNJg4tD82c4xp', '1GG3JQikGC7wxstyavUBDoCJ66bWLLENZC', { - fee: 500 - }, function(err, tx) { - should.exist(err); - err.should.be.an.instanceOf(Errors.INSUFFICIENT_FUNDS); - done(); + it('should fail to build tx for single private key if insufficient funds', function(done) { + var address = { + address: addr[0], + type: 'P2PKH', + coin: coin, + }; + helpers.createAndJoinWallet(clients, 1, 1, function() { + blockchainExplorerMock.setUtxo(address, 123 / 1e8, 1); + clients[0].buildTxFromPrivateKey('5KjBgBiadWGhjWmLN1v4kcEZqWSZFqzgv7cSUuZNJg4tD82c4xp', addr[1], { + fee: 500, + coin: coin, + }, function(err, tx) { + should.exist(err); + err.should.be.an.instanceOf(Errors.INSUFFICIENT_FUNDS); + done(); + }); }); }); });