Skip to content

Commit

Permalink
Merge pull request bitpay#384 from matiu/feat/bch-support
Browse files Browse the repository at this point in the history
Feat/bch support
  • Loading branch information
matiu committed Sep 15, 2017
2 parents a3882ac + 298c1ac commit 00c27c1
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 90 deletions.
17 changes: 12 additions & 5 deletions lib/api.js
Expand Up @@ -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(),
Expand All @@ -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([
Expand All @@ -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)
Expand Down
11 changes: 6 additions & 5 deletions lib/common/utils.js
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -19,3 +19,4 @@ client.sjcl = require('sjcl');

// Expose bitcore
client.Bitcore = require('bitcore-lib');
client.BitcoreCash = require('bitcore-lib-cash');
2 changes: 1 addition & 1 deletion lib/verifier.js
Expand Up @@ -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);
};
Expand Down
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
202 changes: 125 additions & 77 deletions test/client.js
Expand Up @@ -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');
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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();
});
});
});
});
Expand Down

0 comments on commit 00c27c1

Please sign in to comment.