Browse Source

Merge pull request #357 from isocolsky/ref/bip44

Ref/bip44
activeAddress
Matias Alejo Garcia 9 years ago
parent
commit
0a09dac821
  1. 16
      lib/expressapp.js
  2. 16
      lib/model/address.js
  3. 3
      lib/model/copayer.js
  4. 2
      lib/model/txproposal.js
  5. 10
      lib/model/wallet.js
  6. 39
      lib/server.js
  7. 4
      package.json
  8. 266
      test/integration/server.js

16
lib/expressapp.js

@ -131,9 +131,10 @@ ExpressApp.prototype.start = function(opts, cb) {
}); });
}; };
// DEPRECATED
router.post('/v1/wallets/', function(req, res) { router.post('/v1/wallets/', function(req, res) {
var server = getServer(req, res); var server = getServer(req, res);
req.body.supportBIP44 = false; req.body.supportBIP44AndP2PKH = false;
server.createWallet(req.body, function(err, walletId) { server.createWallet(req.body, function(err, walletId) {
if (err) return returnError(err, res, req); if (err) return returnError(err, res, req);
res.json({ res.json({
@ -144,7 +145,6 @@ ExpressApp.prototype.start = function(opts, cb) {
router.post('/v2/wallets/', function(req, res) { router.post('/v2/wallets/', function(req, res) {
var server = getServer(req, res); var server = getServer(req, res);
req.body.supportBIP44 = true;
server.createWallet(req.body, function(err, walletId) { server.createWallet(req.body, function(err, walletId) {
if (err) return returnError(err, res, req); if (err) return returnError(err, res, req);
res.json({ res.json({
@ -162,7 +162,19 @@ ExpressApp.prototype.start = function(opts, cb) {
}); });
}); });
// DEPRECATED
router.post('/v1/wallets/:id/copayers/', function(req, res) { router.post('/v1/wallets/:id/copayers/', function(req, res) {
req.body.walletId = req.params['id'];
req.body.supportBIP44AndP2PKH = false;
var server = getServer(req, res);
server.joinWallet(req.body, function(err, result) {
if (err) return returnError(err, res, req);
res.json(result);
});
});
router.post('/v2/wallets/:id/copayers/', function(req, res) {
req.body.walletId = req.params['id']; req.body.walletId = req.params['id'];
var server = getServer(req, res); var server = getServer(req, res);
server.joinWallet(req.body, function(err, result) { server.joinWallet(req.body, function(err, result) {

16
lib/model/address.js

@ -1,6 +1,7 @@
'use strict'; 'use strict';
var Bitcore = require('bitcore-wallet-utils').Bitcore; var WalletUtils = require('bitcore-wallet-utils');
var Bitcore = WalletUtils.Bitcore;
function Address() {}; function Address() {};
@ -17,6 +18,7 @@ Address.create = function(opts) {
x.path = opts.path; x.path = opts.path;
x.publicKeys = opts.publicKeys; x.publicKeys = opts.publicKeys;
x.network = Bitcore.Address(x.address).toObject().network; x.network = Bitcore.Address(x.address).toObject().network;
x.type = opts.type || WalletUtils.SCRIPT_TYPES.P2SH;
return x; return x;
}; };
@ -31,18 +33,8 @@ Address.fromObj = function(obj) {
x.isChange = obj.isChange; x.isChange = obj.isChange;
x.path = obj.path; x.path = obj.path;
x.publicKeys = obj.publicKeys; x.publicKeys = obj.publicKeys;
x.type = obj.type || WalletUtils.SCRIPT_TYPES.P2SH;
return x; return x;
}; };
/**
* getScriptPubKey
*
* @param {number} threshold - amount of required signatures to spend the output
* @return {Script}
*/
Address.prototype.getScriptPubKey = function(threshold) {
return Bitcore.Script.buildMultisigOut(this.publicKeys, threshold).toScriptHashOut();
};
module.exports = Address; module.exports = Address;

3
lib/model/copayer.js

@ -82,9 +82,10 @@ Copayer.prototype.createAddress = function(wallet, isChange) {
$.checkState(wallet.isComplete()); $.checkState(wallet.isComplete());
var path = this.addressManager.getNewAddressPath(isChange); var path = this.addressManager.getNewAddressPath(isChange);
var raw = Address.create(WalletUtils.deriveAddress(wallet.publicKeyRing, path, wallet.m, wallet.network)); var raw = Address.create(WalletUtils.deriveAddress(wallet.addressType, wallet.publicKeyRing, path, wallet.m, wallet.network));
var address = Address.create(_.extend(raw, { var address = Address.create(_.extend(raw, {
walletId: wallet.id, walletId: wallet.id,
type: wallet.addressType,
})); }));
address.isChange = isChange; address.isChange = isChange;

2
lib/model/txproposal.js

@ -73,6 +73,7 @@ TxProposal.create = function(opts) {
x.proposalSignaturePubKey = opts.proposalSignaturePubKey; x.proposalSignaturePubKey = opts.proposalSignaturePubKey;
x.proposalSignaturePubKeySig = opts.proposalSignaturePubKeySig; x.proposalSignaturePubKeySig = opts.proposalSignaturePubKeySig;
x.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45; x.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45;
x.addressType = opts.addressType || WalletUtils.SCRIPT_TYPES.P2SH;
x.customData = opts.customData; x.customData = opts.customData;
if (_.isFunction(TxProposal._create[x.type])) { if (_.isFunction(TxProposal._create[x.type])) {
@ -121,6 +122,7 @@ TxProposal.fromObj = function(obj) {
x.proposalSignaturePubKey = obj.proposalSignaturePubKey; x.proposalSignaturePubKey = obj.proposalSignaturePubKey;
x.proposalSignaturePubKeySig = obj.proposalSignaturePubKeySig; x.proposalSignaturePubKeySig = obj.proposalSignaturePubKeySig;
x.derivationStrategy = obj.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45; x.derivationStrategy = obj.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45;
x.addressType = obj.addressType || WalletUtils.SCRIPT_TYPES.P2SH;
x.customData = obj.customData; x.customData = obj.customData;
return x; return x;

10
lib/model/wallet.js

@ -30,6 +30,8 @@ Wallet.create = function(opts) {
x.pubKey = opts.pubKey; x.pubKey = opts.pubKey;
x.network = opts.network; x.network = opts.network;
x.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45; x.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45;
x.addressType = opts.addressType || WalletUtils.SCRIPT_TYPES.P2SH;
x.addressManager = AddressManager.create({ x.addressManager = AddressManager.create({
derivationStrategy: x.derivationStrategy, derivationStrategy: x.derivationStrategy,
}); });
@ -55,6 +57,7 @@ Wallet.fromObj = function(obj) {
x.pubKey = obj.pubKey; x.pubKey = obj.pubKey;
x.network = obj.network; x.network = obj.network;
x.derivationStrategy = obj.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45; x.derivationStrategy = obj.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45;
x.addressType = obj.addressType || WalletUtils.SCRIPT_TYPES.P2SH;
x.addressManager = AddressManager.fromObj(obj.addressManager); x.addressManager = AddressManager.fromObj(obj.addressManager);
x.scanStatus = obj.scanStatus; x.scanStatus = obj.scanStatus;
@ -147,10 +150,13 @@ Wallet.prototype.isScanning = function() {
Wallet.prototype.createAddress = function(isChange) { Wallet.prototype.createAddress = function(isChange) {
$.checkState(this.isComplete()); $.checkState(this.isComplete());
var self = this;
var path = this.addressManager.getNewAddressPath(isChange); var path = this.addressManager.getNewAddressPath(isChange);
var raw = WalletUtils.deriveAddress(this.publicKeyRing, path, this.m, this.network); var raw = WalletUtils.deriveAddress(this.addressType, this.publicKeyRing, path, this.m, this.network);
var address = Address.create(_.extend(raw, { var address = Address.create(_.extend(raw, {
walletId: this.id, walletId: self.id,
type: self.addressType,
})); }));
address.isChange = isChange; address.isChange = isChange;
return address; return address;

39
lib/server.js

@ -203,7 +203,7 @@ WalletService.prototype._runLocked = function(cb, task) {
* @param {number} opts.n - Total copayers. * @param {number} opts.n - Total copayers.
* @param {string} opts.pubKey - Public key to verify copayers joining have access to the wallet secret. * @param {string} opts.pubKey - Public key to verify copayers joining have access to the wallet secret.
* @param {string} [opts.network = 'livenet'] - The Bitcoin network for this wallet. * @param {string} [opts.network = 'livenet'] - The Bitcoin network for this wallet.
* @param {string} [opts.supportBIP44 = false] - Client supports BIP44 paths for 1-of-1 wallets. * @param {string} [opts.supportBIP44AndP2PKH = true] - Client supports BIP44 & P2PKH for new wallets.
*/ */
WalletService.prototype.createWallet = function(opts, cb) { WalletService.prototype.createWallet = function(opts, cb) {
var self = this, var self = this,
@ -220,8 +220,10 @@ WalletService.prototype.createWallet = function(opts, cb) {
if (!_.contains(['livenet', 'testnet'], opts.network)) if (!_.contains(['livenet', 'testnet'], opts.network))
return cb(new ClientError('Invalid network')); return cb(new ClientError('Invalid network'));
var derivationStrategy = (opts.n == 1 && opts.supportBIP44) ? opts.supportBIP44AndP2PKH = _.isBoolean(opts.supportBIP44AndP2PKH) ? opts.supportBIP44AndP2PKH : true;
WalletUtils.DERIVATION_STRATEGIES.BIP44 : WalletUtils.DERIVATION_STRATEGIES.BIP45;
var derivationStrategy = opts.supportBIP44AndP2PKH ? WalletUtils.DERIVATION_STRATEGIES.BIP44 : WalletUtils.DERIVATION_STRATEGIES.BIP45;
var addressType = (opts.n == 1 && opts.supportBIP44AndP2PKH) ? WalletUtils.SCRIPT_TYPES.P2PKH : WalletUtils.SCRIPT_TYPES.P2SH;
try { try {
pubKey = new PublicKey.fromString(opts.pubKey); pubKey = new PublicKey.fromString(opts.pubKey);
@ -250,6 +252,7 @@ WalletService.prototype.createWallet = function(opts, cb) {
network: opts.network, network: opts.network,
pubKey: pubKey.toString(), pubKey: pubKey.toString(),
derivationStrategy: derivationStrategy, derivationStrategy: derivationStrategy,
addressType: addressType,
}); });
self.storage.storeWallet(wallet, function(err) { self.storage.storeWallet(wallet, function(err) {
log.debug('Wallet created', wallet.id, opts.network); log.debug('Wallet created', wallet.id, opts.network);
@ -407,8 +410,6 @@ WalletService.prototype._notify = function(type, data, opts, cb) {
WalletService.prototype._addCopayerToWallet = function(wallet, opts, cb) { WalletService.prototype._addCopayerToWallet = function(wallet, opts, cb) {
var self = this; var self = this;
if (wallet.copayers.length == wallet.n) return cb(Errors.WALLET_FULL);
var copayer = Model.Copayer.create({ var copayer = Model.Copayer.create({
name: opts.name, name: opts.name,
copayerIndex: wallet.copayers.length, copayerIndex: wallet.copayers.length,
@ -537,14 +538,6 @@ WalletService.prototype._parseClientVersion = function() {
return this.parsedClientVersion; return this.parsedClientVersion;
}; };
WalletService.prototype._clientSupportsBIP44 = function() {
var version = this._parseClientVersion();
if (!version) return false;
if (version.agent != 'bwc') return true; // Asume 3rd party clients are up-to-date
if (version.major == 0 && version.minor <= 1) return false;
return true;
};
WalletService.prototype._clientSupportsTXPv2 = function() { WalletService.prototype._clientSupportsTXPv2 = function() {
var version = this._parseClientVersion(); var version = this._parseClientVersion();
if (!version) return false; if (!version) return false;
@ -562,6 +555,7 @@ WalletService.prototype._clientSupportsTXPv2 = function() {
* @param {string} opts.requestPubKey - Public Key used to check requests from this copayer. * @param {string} opts.requestPubKey - Public Key used to check requests from this copayer.
* @param {string} opts.copayerSignature - S(name|xPubKey|requestPubKey). Used by other copayers to verify that the copayer joining knows the wallet secret. * @param {string} opts.copayerSignature - S(name|xPubKey|requestPubKey). Used by other copayers to verify that the copayer joining knows the wallet secret.
* @param {string} opts.customData - (optional) Custom data for this copayer. * @param {string} opts.customData - (optional) Custom data for this copayer.
* @param {string} [opts.supportBIP44AndP2PKH = true] - Client supports BIP44 & P2PKH for joining wallets.
*/ */
WalletService.prototype.joinWallet = function(opts, cb) { WalletService.prototype.joinWallet = function(opts, cb) {
var self = this; var self = this;
@ -572,12 +566,26 @@ WalletService.prototype.joinWallet = function(opts, cb) {
if (_.isEmpty(opts.name)) if (_.isEmpty(opts.name))
return cb(new ClientError('Invalid copayer name')); return cb(new ClientError('Invalid copayer name'));
opts.supportBIP44AndP2PKH = _.isBoolean(opts.supportBIP44AndP2PKH) ? opts.supportBIP44AndP2PKH : true;
self.walletId = opts.walletId; self.walletId = opts.walletId;
self._runLocked(cb, function(cb) { self._runLocked(cb, function(cb) {
self.storage.fetchWallet(opts.walletId, function(err, wallet) { self.storage.fetchWallet(opts.walletId, function(err, wallet) {
if (err) return cb(err); if (err) return cb(err);
if (!wallet) return cb(Errors.WALLET_NOT_FOUND); if (!wallet) return cb(Errors.WALLET_NOT_FOUND);
if (opts.supportBIP44AndP2PKH) {
// New client trying to join legacy wallet
if (wallet.derivationStrategy == WalletUtils.DERIVATION_STRATEGIES.BIP45) {
return cb(new ClientError('The wallet you are trying to join was created with an older version of the client app.'));
}
} else {
// Legacy client trying to join new wallet
if (wallet.derivationStrategy == WalletUtils.DERIVATION_STRATEGIES.BIP44) {
return cb(new ClientError(Errors.codes.UPGRADE_NEEDED, 'To join this wallet you need to upgrade your client app.'));
}
}
var hash = WalletUtils.getCopayerHash(opts.name, opts.xPubKey, opts.requestPubKey); var hash = WalletUtils.getCopayerHash(opts.name, opts.xPubKey, opts.requestPubKey);
if (!self._verifySignature(hash, opts.copayerSignature, wallet.pubKey)) { if (!self._verifySignature(hash, opts.copayerSignature, wallet.pubKey)) {
return cb(new ClientError()); return cb(new ClientError());
@ -587,6 +595,8 @@ WalletService.prototype.joinWallet = function(opts, cb) {
xPubKey: opts.xPubKey xPubKey: opts.xPubKey
})) return cb(Errors.COPAYER_IN_WALLET); })) return cb(Errors.COPAYER_IN_WALLET);
if (wallet.copayers.length == wallet.n) return cb(Errors.WALLET_FULL);
self._addCopayerToWallet(wallet, opts, cb); self._addCopayerToWallet(wallet, opts, cb);
}); });
}); });
@ -1211,7 +1221,8 @@ WalletService.prototype.createTx = function(opts, cb) {
walletN: wallet.n, walletN: wallet.n,
excludeUnconfirmedUtxos: !!opts.excludeUnconfirmedUtxos, excludeUnconfirmedUtxos: !!opts.excludeUnconfirmedUtxos,
derivationStrategy: wallet.derivationStrategy, derivationStrategy: wallet.derivationStrategy,
customData: opts.customData addressType: wallet.addressType,
customData: opts.customData,
}; };
if (signingKey.selfSigned) { if (signingKey.selfSigned) {

4
package.json

@ -2,7 +2,7 @@
"name": "bitcore-wallet-service", "name": "bitcore-wallet-service",
"description": "A service for Mutisig HD Bitcoin Wallets", "description": "A service for Mutisig HD Bitcoin Wallets",
"author": "BitPay Inc", "author": "BitPay Inc",
"version": "0.2.0", "version": "0.2.2",
"keywords": [ "keywords": [
"bitcoin", "bitcoin",
"copay", "copay",
@ -20,7 +20,7 @@
"dependencies": { "dependencies": {
"async": "^0.9.0", "async": "^0.9.0",
"bitcore": "0.13.0", "bitcore": "0.13.0",
"bitcore-wallet-utils": "0.2.0", "bitcore-wallet-utils": "0.2.2",
"body-parser": "^1.11.0", "body-parser": "^1.11.0",
"coveralls": "^2.11.2", "coveralls": "^2.11.2",
"email-validator": "^1.0.1", "email-validator": "^1.0.1",

266
test/integration/server.js

@ -96,14 +96,15 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
var copayerIds = []; var copayerIds = [];
var offset = opts.offset || 0; var offset = opts.offset || 0;
var supportBIP44 = _.isBoolean(opts.supportBIP44) ? opts.supportBIP44 : true
var walletOpts = { var walletOpts = {
name: 'a wallet', name: 'a wallet',
m: m, m: m,
n: n, n: n,
pubKey: TestData.keyPair.pub, pubKey: TestData.keyPair.pub,
supportBIP44: supportBIP44,
}; };
if (_.isBoolean(opts.supportBIP44AndP2PKH))
walletOpts.supportBIP44AndP2PKH = opts.supportBIP44AndP2PKH;
server.createWallet(walletOpts, function(err, walletId) { server.createWallet(walletOpts, function(err, walletId) {
if (err) return cb(err); if (err) return cb(err);
@ -112,10 +113,12 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
var copayerOpts = helpers.getSignedCopayerOpts({ var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId, walletId: walletId,
name: 'copayer ' + (i + 1), name: 'copayer ' + (i + 1),
xPubKey: (n == 1 && supportBIP44) ? copayerData.xPubKey_44H_0H_0H : copayerData.xPubKey_45H, xPubKey: (_.isBoolean(opts.supportBIP44AndP2PKH) && !opts.supportBIP44AndP2PKH) ? copayerData.xPubKey_45H : copayerData.xPubKey_44H_0H_0H,
requestPubKey: copayerData.pubKey_1H_0, requestPubKey: copayerData.pubKey_1H_0,
customData: 'custom data ' + (i + 1), customData: 'custom data ' + (i + 1),
}); });
if (_.isBoolean(opts.supportBIP44AndP2PKH))
copayerOpts.supportBIP44AndP2PKH = opts.supportBIP44AndP2PKH;
server.joinWallet(copayerOpts, function(err, result) { server.joinWallet(copayerOpts, function(err, result) {
should.not.exist(err); should.not.exist(err);
@ -133,6 +136,7 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
}); });
}; };
helpers.randomTXID = function() { helpers.randomTXID = function() {
return Bitcore.crypto.Hash.sha256(new Buffer(Math.random() * 100000)).toString('hex');; return Bitcore.crypto.Hash.sha256(new Buffer(Math.random() * 100000)).toString('hex');;
}; };
@ -161,11 +165,23 @@ helpers.stubUtxos = function(server, wallet, amounts, cb) {
} else { } else {
confirmations = Math.floor(Math.random() * 100 + 1); confirmations = Math.floor(Math.random() * 100 + 1);
} }
var scriptPubKey;
switch (wallet.addressType) {
case WalletUtils.SCRIPT_TYPES.P2SH:
scriptPubKey = Bitcore.Script.buildMultisigOut(address.publicKeys, wallet.m).toScriptHashOut();
break;
case WalletUtils.SCRIPT_TYPES.P2PKH:
scriptPubKey = Bitcore.Script.buildPublicKeyHashOut(address.address);
break;
}
should.exist(scriptPubKey);
return { return {
txid: helpers.randomTXID(), txid: helpers.randomTXID(),
vout: Math.floor(Math.random() * 10 + 1), vout: Math.floor(Math.random() * 10 + 1),
satoshis: helpers.toSatoshi(amount).toString(), satoshis: helpers.toSatoshi(amount).toString(),
scriptPubKey: address.getScriptPubKey(wallet.m).toBuffer().toString('hex'), scriptPubKey: scriptPubKey.toBuffer().toString('hex'),
address: address.address, address: address.address,
confirmations: confirmations, confirmations: confirmations,
}; };
@ -496,7 +512,7 @@ describe('Wallet service', function() {
txp = t; txp = t;
async.eachSeries(_.range(2), function(i, next) { async.eachSeries(_.range(2), function(i, next) {
var copayer = TestData.copayers[i]; var copayer = TestData.copayers[i];
helpers.getAuthServer(copayer.id45, function(server) { helpers.getAuthServer(copayer.id44, function(server) {
var signatures = helpers.clientSign(txp, copayer.xPrivKey); var signatures = helpers.clientSign(txp, copayer.xPrivKey);
server.signTx({ server.signTx({
txProposalId: txp.id, txProposalId: txp.id,
@ -557,7 +573,7 @@ describe('Wallet service', function() {
txpId = txp.id; txpId = txp.id;
async.eachSeries(_.range(1, 3), function(i, next) { async.eachSeries(_.range(1, 3), function(i, next) {
var copayer = TestData.copayers[i]; var copayer = TestData.copayers[i];
helpers.getAuthServer(copayer.id45, function(server) { helpers.getAuthServer(copayer.id44, function(server) {
server.rejectTx({ server.rejectTx({
txProposalId: txp.id, txProposalId: txp.id,
}, next); }, next);
@ -930,14 +946,14 @@ describe('Wallet service', function() {
server = new WalletService(); server = new WalletService();
var walletOpts = { var walletOpts = {
name: 'my wallet', name: 'my wallet',
m: 2, m: 1,
n: 3, n: 2,
pubKey: TestData.keyPair.pub, pubKey: TestData.keyPair.pub,
}; };
server.createWallet(walletOpts, function(err, wId) { server.createWallet(walletOpts, function(err, wId) {
should.not.exist(err); should.not.exist(err);
should.exist.walletId;
walletId = wId; walletId = wId;
should.exist(walletId);
done(); done();
}); });
}); });
@ -946,7 +962,7 @@ describe('Wallet service', function() {
var copayerOpts = helpers.getSignedCopayerOpts({ var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId, walletId: walletId,
name: 'me', name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H, xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
requestPubKey: TestData.copayers[0].pubKey_1H_0, requestPubKey: TestData.copayers[0].pubKey_1H_0,
customData: 'dummy custom data', customData: 'dummy custom data',
}); });
@ -986,7 +1002,7 @@ describe('Wallet service', function() {
var copayerOpts = helpers.getSignedCopayerOpts({ var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId, walletId: walletId,
name: '', name: '',
xPubKey: TestData.copayers[0].xPubKey_45H, xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
requestPubKey: TestData.copayers[0].pubKey_1H_0, requestPubKey: TestData.copayers[0].pubKey_1H_0,
}); });
server.joinWallet(copayerOpts, function(err, result) { server.joinWallet(copayerOpts, function(err, result) {
@ -1028,11 +1044,27 @@ describe('Wallet service', function() {
}); });
}); });
it('should return copayer in wallet error before full wallet', function(done) {
helpers.createAndJoinWallet(1, 1, function(s, wallet) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: wallet.id,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
});
server.joinWallet(copayerOpts, function(err) {
should.exist(err);
err.code.should.equal('COPAYER_IN_WALLET');
done();
});
});
});
it('should fail to re-join wallet', function(done) { it('should fail to re-join wallet', function(done) {
var copayerOpts = helpers.getSignedCopayerOpts({ var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId, walletId: walletId,
name: 'me', name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H, xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
requestPubKey: TestData.copayers[0].pubKey_1H_0, requestPubKey: TestData.copayers[0].pubKey_1H_0,
}); });
server.joinWallet(copayerOpts, function(err) { server.joinWallet(copayerOpts, function(err) {
@ -1050,7 +1082,7 @@ describe('Wallet service', function() {
var copayerOpts = helpers.getSignedCopayerOpts({ var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId, walletId: walletId,
name: 'me', name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H, xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
requestPubKey: TestData.copayers[0].pubKey_1H_0, requestPubKey: TestData.copayers[0].pubKey_1H_0,
}); });
server.joinWallet(copayerOpts, function(err) { server.joinWallet(copayerOpts, function(err) {
@ -1067,7 +1099,7 @@ describe('Wallet service', function() {
copayerOpts = helpers.getSignedCopayerOpts({ copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId, walletId: walletId,
name: 'me', name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H, xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
requestPubKey: TestData.copayers[0].pubKey_1H_0, requestPubKey: TestData.copayers[0].pubKey_1H_0,
}); });
server.joinWallet(copayerOpts, function(err) { server.joinWallet(copayerOpts, function(err) {
@ -1084,7 +1116,7 @@ describe('Wallet service', function() {
var copayerOpts = { var copayerOpts = {
walletId: walletId, walletId: walletId,
name: 'me', name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H, xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
requestPubKey: TestData.copayers[0].pubKey_1H_0, requestPubKey: TestData.copayers[0].pubKey_1H_0,
copayerSignature: 'bad sign', copayerSignature: 'bad sign',
}; };
@ -1098,7 +1130,7 @@ describe('Wallet service', function() {
var copayerOpts = { var copayerOpts = {
walletId: walletId, walletId: walletId,
name: 'me', name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H, xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
requestPubKey: TestData.copayers[0].pubKey_1H_0, requestPubKey: TestData.copayers[0].pubKey_1H_0,
}; };
server.joinWallet(copayerOpts, function(err) { server.joinWallet(copayerOpts, function(err) {
@ -1112,7 +1144,7 @@ describe('Wallet service', function() {
var copayerOpts = helpers.getSignedCopayerOpts({ var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId, walletId: walletId,
name: 'me', name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H, xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
requestPubKey: TestData.copayers[0].pubKey_1H_0, requestPubKey: TestData.copayers[0].pubKey_1H_0,
}); });
copayerOpts.name = 'me2'; copayerOpts.name = 'me2';
@ -1155,56 +1187,133 @@ describe('Wallet service', function() {
}); });
}); });
describe('#joinWallet new/legacy clients', function() {
var server;
beforeEach(function() {
server = new WalletService();
});
it('should fail to join legacy wallet from new client', function(done) {
var walletOpts = {
name: 'my wallet',
m: 1,
n: 2,
pubKey: TestData.keyPair.pub,
supportBIP44AndP2PKH: false,
};
server.createWallet(walletOpts, function(err, walletId) {
should.not.exist(err);
should.exist(walletId);
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
});
server.joinWallet(copayerOpts, function(err, result) {
should.exist(err);
err.message.should.contain('The wallet you are trying to join was created with an older version of the client app');
done();
});
});
});
it('should fail to join new wallet from legacy client', function(done) {
var walletOpts = {
name: 'my wallet',
m: 1,
n: 2,
pubKey: TestData.keyPair.pub,
};
server.createWallet(walletOpts, function(err, walletId) {
should.not.exist(err);
should.exist(walletId);
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
supportBIP44AndP2PKH: false,
});
server.joinWallet(copayerOpts, function(err, result) {
should.exist(err);
err.code.should.equal('UPGRADE_NEEDED');
done();
});
});
});
});
describe('Address derivation strategy', function() { describe('Address derivation strategy', function() {
var server; var server;
beforeEach(function() { beforeEach(function() {
server = WalletService.getInstance(); server = WalletService.getInstance();
}); });
it('should use BIP44 for 1-of-1 wallet if supported', function(done) { it('should use BIP44 & P2PKH for 1-of-1 wallet if supported', function(done) {
var walletOpts = { var walletOpts = {
name: 'my wallet', name: 'my wallet',
m: 1, m: 1,
n: 1, n: 1,
pubKey: TestData.keyPair.pub, pubKey: TestData.keyPair.pub,
supportBIP44: true,
}; };
server.createWallet(walletOpts, function(err, wid) { server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err); should.not.exist(err);
server.storage.fetchWallet(wid, function(err, wallet) { server.storage.fetchWallet(wid, function(err, wallet) {
should.not.exist(err); should.not.exist(err);
wallet.derivationStrategy.should.equal('BIP44'); wallet.derivationStrategy.should.equal('BIP44');
wallet.addressType.should.equal('P2PKH');
done(); done();
}); });
}); });
}); });
it('should use BIP45 for 1-of-1 wallet if BIP44 not supported', function(done) { it('should use BIP45 & P2SH for 1-of-1 wallet if not supported', function(done) {
var walletOpts = { var walletOpts = {
name: 'my wallet', name: 'my wallet',
m: 1, m: 1,
n: 1, n: 1,
pubKey: TestData.keyPair.pub, pubKey: TestData.keyPair.pub,
supportBIP44AndP2PKH: false,
}; };
server.createWallet(walletOpts, function(err, wid) { server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err); should.not.exist(err);
server.storage.fetchWallet(wid, function(err, wallet) { server.storage.fetchWallet(wid, function(err, wallet) {
should.not.exist(err); should.not.exist(err);
wallet.derivationStrategy.should.equal('BIP45'); wallet.derivationStrategy.should.equal('BIP45');
wallet.addressType.should.equal('P2SH');
done();
});
});
});
it('should use BIP44 & P2SH for shared wallet if supported', function(done) {
var walletOpts = {
name: 'my wallet',
m: 2,
n: 3,
pubKey: TestData.keyPair.pub,
};
server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err);
server.storage.fetchWallet(wid, function(err, wallet) {
should.not.exist(err);
wallet.derivationStrategy.should.equal('BIP44');
wallet.addressType.should.equal('P2SH');
done(); done();
}); });
}); });
}); });
it('should always use BIP45 for shared wallets', function(done) { it('should use BIP45 & P2SH for shared wallet if supported', function(done) {
var walletOpts = { var walletOpts = {
name: 'my wallet', name: 'my wallet',
m: 2, m: 2,
n: 3, n: 3,
pubKey: TestData.keyPair.pub, pubKey: TestData.keyPair.pub,
supportBIP44AndP2PKH: false,
}; };
server.createWallet(walletOpts, function(err, wid) { server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err); should.not.exist(err);
server.storage.fetchWallet(wid, function(err, wallet) { server.storage.fetchWallet(wid, function(err, wallet) {
should.not.exist(err); should.not.exist(err);
wallet.derivationStrategy.should.equal('BIP45'); wallet.derivationStrategy.should.equal('BIP45');
wallet.addressType.should.equal('P2SH');
done(); done();
}); });
}); });
@ -1262,7 +1371,6 @@ describe('Wallet service', function() {
should.exist(status.wallet.copayers[0].requestPubKey); should.exist(status.wallet.copayers[0].requestPubKey);
should.exist(status.wallet.copayers[0].signature); should.exist(status.wallet.copayers[0].signature);
should.exist(status.wallet.copayers[0].requestPubKey); should.exist(status.wallet.copayers[0].requestPubKey);
should.exist(status.wallet.copayers[0].addressManager);
should.exist(status.wallet.copayers[0].customData); should.exist(status.wallet.copayers[0].customData);
// Do not return other copayer's custom data // Do not return other copayer's custom data
_.each(_.rest(status.wallet.copayers), function(copayer) { _.each(_.rest(status.wallet.copayers), function(copayer) {
@ -1337,7 +1445,9 @@ describe('Wallet service', function() {
describe('shared wallets (BIP45)', function() { describe('shared wallets (BIP45)', function() {
beforeEach(function(done) { beforeEach(function(done) {
helpers.createAndJoinWallet(2, 2, function(s, w) { helpers.createAndJoinWallet(2, 2, {
supportBIP44AndP2PKH: false
}, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
done(); done();
@ -1353,6 +1463,7 @@ describe('Wallet service', function() {
address.address.should.equal('3BVJZ4CYzeTtawDtgwHvWV5jbvnXtYe97i'); address.address.should.equal('3BVJZ4CYzeTtawDtgwHvWV5jbvnXtYe97i');
address.isChange.should.be.false; address.isChange.should.be.false;
address.path.should.equal('m/2147483647/0/0'); address.path.should.equal('m/2147483647/0/0');
address.type.should.equal('P2SH');
server.getNotifications({}, function(err, notifications) { server.getNotifications({}, function(err, notifications) {
should.not.exist(err); should.not.exist(err);
var notif = _.find(notifications, { var notif = _.find(notifications, {
@ -1379,6 +1490,53 @@ describe('Wallet service', function() {
done(); done();
}); });
}); });
});
describe('shared wallets (BIP44)', function() {
beforeEach(function(done) {
helpers.createAndJoinWallet(2, 2, function(s, w) {
server = s;
wallet = w;
done();
});
});
it('should create address', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
should.exist(address);
address.walletId.should.equal(wallet.id);
address.network.should.equal('livenet');
address.address.should.equal('36q2G5FMGvJbPgAVEaiyAsFGmpkhPKwk2r');
address.isChange.should.be.false;
address.path.should.equal('m/0/0');
address.type.should.equal('P2SH');
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notif = _.find(notifications, {
type: 'NewAddress'
});
should.exist(notif);
notif.data.address.should.equal(address.address);
done();
});
});
});
it('should create many addresses on simultaneous requests', function(done) {
var N = 5;
async.map(_.range(N), function(i, cb) {
server.createAddress({}, cb);
}, function(err, addresses) {
addresses.length.should.equal(N);
_.each(_.range(N), function(i) {
addresses[i].path.should.equal('m/0/' + i);
});
// No two identical addresses
_.uniq(_.pluck(addresses, 'address')).length.should.equal(N);
done();
});
});
it('should not create address if unable to store it', function(done) { it('should not create address if unable to store it', function(done) {
sinon.stub(server.storage, 'storeAddressAndWallet').yields('dummy error'); sinon.stub(server.storage, 'storeAddressAndWallet').yields('dummy error');
@ -1400,7 +1558,7 @@ describe('Wallet service', function() {
}); });
}); });
describe('1-of-1 (BIP44)', function() { describe('1-of-1 (BIP44 & P2PKH)', function() {
beforeEach(function(done) { beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, function(s, w) { helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s; server = s;
@ -1416,9 +1574,10 @@ describe('Wallet service', function() {
should.exist(address); should.exist(address);
address.walletId.should.equal(wallet.id); address.walletId.should.equal(wallet.id);
address.network.should.equal('livenet'); address.network.should.equal('livenet');
address.address.should.equal('3J4J9nkFpzQjUGDh5hLKMKztFSPWMKejKE'); address.address.should.equal('1L3z9LPd861FWQhf3vDn89Fnc9dkdBo2CG');
address.isChange.should.be.false; address.isChange.should.be.false;
address.path.should.equal('m/0/0'); address.path.should.equal('m/0/0');
address.type.should.equal('P2PKH');
server.getNotifications({}, function(err, notifications) { server.getNotifications({}, function(err, notifications) {
should.not.exist(err); should.not.exist(err);
var notif = _.find(notifications, { var notif = _.find(notifications, {
@ -1651,10 +1810,10 @@ describe('Wallet service', function() {
reqPrivKey = new Bitcore.PrivateKey(); reqPrivKey = new Bitcore.PrivateKey();
var requestPubKey = reqPrivKey.toPublicKey(); var requestPubKey = reqPrivKey.toPublicKey();
var xPrivKey = TestData.copayers[0].xPrivKey_45H; var xPrivKey = TestData.copayers[0].xPrivKey_44H_0H_0H;
var sig = WalletUtils.signRequestPubKey(requestPubKey, xPrivKey); var sig = WalletUtils.signRequestPubKey(requestPubKey, xPrivKey);
var copayerId = WalletUtils.xPubToCopayerId(TestData.copayers[0].xPubKey_45H); var copayerId = WalletUtils.xPubToCopayerId(TestData.copayers[0].xPubKey_44H_0H_0H);
opts = { opts = {
copayerId: copayerId, copayerId: copayerId,
requestPubKey: requestPubKey, requestPubKey: requestPubKey,
@ -1665,9 +1824,7 @@ describe('Wallet service', function() {
describe('#addAccess 1-1', function() { describe('#addAccess 1-1', function() {
beforeEach(function(done) { beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, { helpers.createAndJoinWallet(1, 1, function(s, w) {
supportBIP44: false
}, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
@ -2801,18 +2958,20 @@ describe('Wallet service', function() {
}); });
describe('#signTx', function() { describe('#signTx', function() {
describe('1-1', function() { describe('1-of-1 (BIP44 & P2PKH)', function() {
var server, wallet, txid; var server, wallet, txid;
beforeEach(function(done) { beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, function(s, w) { helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
helpers.stubUtxos(server, wallet, _.range(1, 9), function() { helpers.stubUtxos(server, wallet, [1, 2], function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 20, TestData.copayers[0].privKey_1H_0); var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 2.5, TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) { server.createTx(txOpts, function(err, tx) {
should.not.exist(err); should.not.exist(err);
should.exist(tx); should.exist(tx);
tx.derivationStrategy.should.equal('BIP44');
tx.addressType.should.equal('P2PKH');
txid = tx.id; txid = tx.id;
done(); done();
}); });
@ -2847,10 +3006,9 @@ describe('Wallet service', function() {
}); });
}); });
}); });
}); });
describe('Multisign 2-3', function() {
describe('Multisig', function() {
var server, wallet, txid; var server, wallet, txid;
beforeEach(function(done) { beforeEach(function(done) {
@ -4439,7 +4597,7 @@ describe('Wallet service', function() {
var server, wallet; var server, wallet;
var scanConfigOld = WalletService.SCAN_CONFIG; var scanConfigOld = WalletService.SCAN_CONFIG;
describe('1-of-1 wallet (BIP44)', function() { describe('1-of-1 wallet (BIP44 & P2PKH)', function() {
beforeEach(function(done) { beforeEach(function(done) {
this.timeout(5000); this.timeout(5000);
WalletService.SCAN_CONFIG.scanWindow = 2; WalletService.SCAN_CONFIG.scanWindow = 2;
@ -4457,9 +4615,9 @@ describe('Wallet service', function() {
it('should scan main addresses', function(done) { it('should scan main addresses', function(done) {
helpers.stubAddressActivity( helpers.stubAddressActivity(
['3J4J9nkFpzQjUGDh5hLKMKztFSPWMKejKE', // m/0/0 ['1L3z9LPd861FWQhf3vDn89Fnc9dkdBo2CG', // m/0/0
'384JHSf9kVBs3yXsPwCzEScRs395u8hwxj', // m/0/2 '1GdXraZ1gtoVAvBh49D4hK9xLm6SKgesoE', // m/0/2
'3NgXBiMQvwcRU8khVoPFJ6gsbGg9ZYrRzH', // m/1/0 '1FUzgKcyPJsYwDLUEVJYeE2N3KVaoxTjGS', // m/1/0
]); ]);
var expectedPaths = [ var expectedPaths = [
'm/0/0', 'm/0/0',
@ -4556,7 +4714,9 @@ describe('Wallet service', function() {
WalletService.SCAN_CONFIG.scanWindow = 2; WalletService.SCAN_CONFIG.scanWindow = 2;
WalletService.SCAN_CONFIG.derivationDelay = 0; WalletService.SCAN_CONFIG.derivationDelay = 0;
helpers.createAndJoinWallet(1, 2, function(s, w) { helpers.createAndJoinWallet(1, 2, {
supportBIP44AndP2PKH: false
}, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
done(); done();
@ -4643,7 +4803,9 @@ describe('Wallet service', function() {
WalletService.SCAN_CONFIG.scanWindow = 2; WalletService.SCAN_CONFIG.scanWindow = 2;
WalletService.SCAN_CONFIG.derivationDelay = 0; WalletService.SCAN_CONFIG.derivationDelay = 0;
helpers.createAndJoinWallet(1, 1, function(s, w) { helpers.createAndJoinWallet(1, 1, {
supportBIP44AndP2PKH: false
}, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
done(); done();
@ -4656,17 +4818,17 @@ describe('Wallet service', function() {
it('should start an asynchronous scan', function(done) { it('should start an asynchronous scan', function(done) {
helpers.stubAddressActivity( helpers.stubAddressActivity(
['3J4J9nkFpzQjUGDh5hLKMKztFSPWMKejKE', // m/0/0 ['3GvvHimEMk2GBZnPxTF89GHZL6QhZjUZVs', // m/2147483647/0/0
'384JHSf9kVBs3yXsPwCzEScRs395u8hwxj', // m/0/2 '37pd1jjTUiGBh8JL2hKLDgsyrhBoiz5vsi', // m/2147483647/0/2
'3NgXBiMQvwcRU8khVoPFJ6gsbGg9ZYrRzH', // m/1/0 '3C3tBn8Sr1wHTp2brMgYsj9ncB7R7paYuB', // m/2147483647/1/0
]); ]);
var expectedPaths = [ var expectedPaths = [
'm/0/0', 'm/2147483647/0/0',
'm/0/1', 'm/2147483647/0/1',
'm/0/2', 'm/2147483647/0/2',
'm/0/3', 'm/2147483647/0/3',
'm/1/0', 'm/2147483647/1/0',
'm/1/1', 'm/2147483647/1/1',
]; ];
server.messageBroker.onMessage(function(n) { server.messageBroker.onMessage(function(n) {
if (n.type == 'ScanFinished') { if (n.type == 'ScanFinished') {
@ -4681,7 +4843,7 @@ describe('Wallet service', function() {
_.difference(paths, expectedPaths).length.should.equal(0); _.difference(paths, expectedPaths).length.should.equal(0);
server.createAddress({}, function(err, address) { server.createAddress({}, function(err, address) {
should.not.exist(err); should.not.exist(err);
address.path.should.equal('m/0/4'); address.path.should.equal('m/2147483647/0/4');
done(); done();
}); });
}) })

Loading…
Cancel
Save