Merge pull request #344 from isocolsky/bip44

Restrict support for BIP44 to 1-of-1 wallets
This commit is contained in:
Matias Alejo Garcia 2015-09-05 01:15:15 -03:00
commit cf59b73023
4 changed files with 226 additions and 255 deletions

View File

@ -132,6 +132,18 @@ ExpressApp.prototype.start = function(opts, cb) {
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;
server.createWallet(req.body, function(err, walletId) {
if (err) return returnError(err, res, req);
res.json({
walletId: walletId,
});
});
});
router.post('/v2/wallets/', function(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({

View File

@ -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.derivationStrategy = 'BIP45'] - Strategy used for address derivation (BIP44, BIP45). * @param {string} [opts.supportBIP44 = false] - Client supports BIP44 paths for 1-of-1 wallets.
*/ */
WalletService.prototype.createWallet = function(opts, cb) { WalletService.prototype.createWallet = function(opts, cb) {
var self = this, var self = this,
@ -220,9 +220,8 @@ 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'));
opts.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45; var derivationStrategy = (opts.n == 1 && opts.supportBIP44) ?
if (!_.contains(_.values(WalletUtils.DERIVATION_STRATEGIES), opts.derivationStrategy)) WalletUtils.DERIVATION_STRATEGIES.BIP44 : WalletUtils.DERIVATION_STRATEGIES.BIP45;
return cb(new ClientError('Invalid address derivation strategy'));
try { try {
pubKey = new PublicKey.fromString(opts.pubKey); pubKey = new PublicKey.fromString(opts.pubKey);
@ -250,7 +249,7 @@ WalletService.prototype.createWallet = function(opts, cb) {
n: opts.n, n: opts.n,
network: opts.network, network: opts.network,
pubKey: pubKey.toString(), pubKey: pubKey.toString(),
derivationStrategy: opts.derivationStrategy, derivationStrategy: derivationStrategy,
}); });
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);
@ -417,7 +416,7 @@ WalletService.prototype._addCopayerToWallet = function(wallet, opts, cb) {
requestPubKey: opts.requestPubKey, requestPubKey: opts.requestPubKey,
signature: opts.copayerSignature, signature: opts.copayerSignature,
customData: opts.customData, customData: opts.customData,
derivationStrategy: opts.derivationStrategy, derivationStrategy: wallet.derivationStrategy,
}); });
self.storage.fetchCopayerLookup(copayer.id, function(err, res) { self.storage.fetchCopayerLookup(copayer.id, function(err, res) {
if (err) return cb(err); if (err) return cb(err);
@ -563,7 +562,6 @@ 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.derivationStrategy = 'BIP45'] - Strategy used for address derivation (BIP44, BIP45).
*/ */
WalletService.prototype.joinWallet = function(opts, cb) { WalletService.prototype.joinWallet = function(opts, cb) {
var self = this; var self = this;
@ -574,21 +572,11 @@ 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.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45;
if (!_.contains(_.values(WalletUtils.DERIVATION_STRATEGIES), opts.derivationStrategy))
return cb(new ClientError('Invalid address derivation strategy'));
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 (wallet.addressManager.derivationStrategy != opts.derivationStrategy) {
if (!self._clientSupportsBIP44()) {
return cb(new ClientError(Errors.codes.UPGRADE_NEEDED, 'To join this wallet, you need to upgrade your client app.'));
}
return cb(new ClientError('Incompatible address derivation strategy'));
}
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)) {
@ -1222,7 +1210,7 @@ WalletService.prototype.createTx = function(opts, cb) {
requiredRejections: Math.min(wallet.m, wallet.n - wallet.m + 1), requiredRejections: Math.min(wallet.m, wallet.n - wallet.m + 1),
walletN: wallet.n, walletN: wallet.n,
excludeUnconfirmedUtxos: !!opts.excludeUnconfirmedUtxos, excludeUnconfirmedUtxos: !!opts.excludeUnconfirmedUtxos,
derivationStrategy: wallet.addressManager.derivationStrategy, derivationStrategy: wallet.derivationStrategy,
customData: opts.customData customData: opts.customData
}; };

View File

@ -163,7 +163,7 @@ Storage.prototype._completeTxData = function(walletId, txs, cb) {
action.copayerName = wallet.getCopayer(action.copayerId).name; action.copayerName = wallet.getCopayer(action.copayerId).name;
}); });
if (tx.status=='accepted') if (tx.status == 'accepted')
tx.raw = tx.getRawTx(); tx.raw = tx.getRawTx();
}); });

View File

@ -96,27 +96,27 @@ 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,
derivationStrategy: opts.derivationStrategy || 'BIP44', supportBIP44: supportBIP44,
}; };
server.createWallet(walletOpts, function(err, walletId) { server.createWallet(walletOpts, function(err, walletId) {
if (err) return cb(err); if (err) return cb(err);
async.each(_.range(n), function(i, cb) { async.each(_.range(n), function(i, cb) {
var copayerData = TestData.copayers[i + offset]; var copayerData = TestData.copayers[i + offset];
var bip = opts.derivationStrategy || 'BIP44';
var copayerOpts = helpers.getSignedCopayerOpts({ var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId, walletId: walletId,
name: 'copayer ' + (i + 1), name: 'copayer ' + (i + 1),
xPubKey: bip == 'BIP44' ? copayerData.xPubKey_44H_0H_0H : copayerData.xPubKey_45H, xPubKey: (n == 1 && supportBIP44) ? copayerData.xPubKey_44H_0H_0H : copayerData.xPubKey_45H,
requestPubKey: copayerData.pubKey_1H_0, requestPubKey: copayerData.pubKey_1H_0,
customData: 'custom data ' + (i + 1), customData: 'custom data ' + (i + 1),
derivationStrategy: bip,
}); });
server.joinWallet(copayerOpts, function(err, result) { server.joinWallet(copayerOpts, function(err, result) {
should.not.exist(err); should.not.exist(err);
copayerIds.push(result.copayerId); copayerIds.push(result.copayerId);
@ -495,7 +495,7 @@ describe('Wallet service', function() {
txpId = txp.id; txpId = txp.id;
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.id44, function(server) { helpers.getAuthServer(copayer.id45, 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,
@ -553,7 +553,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.id44, function(server) { helpers.getAuthServer(copayer.id45, function(server) {
server.rejectTx({ server.rejectTx({
txProposalId: txp.id, txProposalId: txp.id,
}, next); }, next);
@ -1012,9 +1012,8 @@ describe('Wallet service', function() {
var copayerOpts = helpers.getSignedCopayerOpts({ var copayerOpts = helpers.getSignedCopayerOpts({
walletId: wallet.id, walletId: wallet.id,
name: 'me', name: 'me',
xPubKey: TestData.copayers[1].xPubKey_45H, xPubKey: TestData.copayers[1].xPubKey_44H_0H_0H,
requestPubKey: TestData.copayers[1].pubKey_1H_0, requestPubKey: TestData.copayers[1].pubKey_1H_0,
derivationStrategy: 'BIP44',
}); });
server.joinWallet(copayerOpts, function(err) { server.joinWallet(copayerOpts, function(err) {
should.exist(err); should.exist(err);
@ -1155,84 +1154,53 @@ describe('Wallet service', function() {
describe('Address derivation strategy', function() { describe('Address derivation strategy', function() {
var server; var server;
beforeEach(function() { beforeEach(function() {
server = WalletService.getInstance({ server = WalletService.getInstance();
clientVersion: 'bwc-0.2.0',
});
}); });
it('should fail to join BIP45 wallet with BIP44 copayer opts', function(done) { it('should use BIP44 for 1-of-1 wallet if supported', function(done) {
var walletOpts = { var walletOpts = {
name: 'my wallet', name: 'my wallet',
m: 2, m: 1,
n: 3, n: 1,
pubKey: TestData.keyPair.pub, pubKey: TestData.keyPair.pub,
derivationStrategy: 'BIP45', supportBIP44: true,
}; };
server.createWallet(walletOpts, function(err, wid) { server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err); should.not.exist(err);
var copayerOpts = helpers.getSignedCopayerOpts({ server.storage.fetchWallet(wid, function(err, wallet) {
walletId: wid, should.not.exist(err);
name: 'me', wallet.derivationStrategy.should.equal('BIP44');
xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
customData: 'dummy custom data',
derivationStrategy: 'BIP44',
});
server.joinWallet(copayerOpts, function(err, result) {
should.exist(err);
err.message.toLowerCase().should.contain('address derivation strategy');
done(); done();
}); });
}); });
}); });
it('should fail to join BIP44 wallet with BIP45 copayer opts', function(done) { it('should use BIP45 for 1-of-1 wallet if BIP44 not supported', function(done) {
var walletOpts = { var walletOpts = {
name: 'my wallet', name: 'my wallet',
m: 2, m: 1,
n: 3, n: 1,
pubKey: TestData.keyPair.pub, pubKey: TestData.keyPair.pub,
derivationStrategy: 'BIP44',
}; };
server.createWallet(walletOpts, function(err, wid) { server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err); should.not.exist(err);
var copayerOpts = helpers.getSignedCopayerOpts({ server.storage.fetchWallet(wid, function(err, wallet) {
walletId: wid, should.not.exist(err);
name: 'me', wallet.derivationStrategy.should.equal('BIP45');
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
customData: 'dummy custom data',
derivationStrategy: 'BIP45',
});
server.joinWallet(copayerOpts, function(err, result) {
should.exist(err);
err.message.toLowerCase().should.contain('address derivation strategy');
done(); done();
}); });
}); });
}); });
it('should require upgrade when joining BIP44 wallet with BIP45 copayer opts from old client app', function(done) { it('should always use BIP45 for shared wallets', 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,
derivationStrategy: 'BIP44',
}; };
server.createWallet(walletOpts, function(err, wid) { server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err); should.not.exist(err);
var copayerOpts = helpers.getSignedCopayerOpts({ server.storage.fetchWallet(wid, function(err, wallet) {
walletId: wid, should.not.exist(err);
name: 'me', wallet.derivationStrategy.should.equal('BIP45');
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
customData: 'dummy custom data',
derivationStrategy: 'BIP45',
});
server = WalletService.getInstance({
clientVersion: 'bwc-0.1.4',
});
server.joinWallet(copayerOpts, function(err, result) {
should.exist(err);
err.code.should.equal('UPGRADE_NEEDED');
done(); done();
}); });
}); });
@ -1242,9 +1210,7 @@ describe('Wallet service', function() {
describe('#getStatus', function() { describe('#getStatus', function() {
var server, wallet; var server, wallet;
beforeEach(function(done) { beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, { helpers.createAndJoinWallet(1, 2, function(s, w) {
derivationStrategy: 'BIP45'
}, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
done(); done();
@ -1258,7 +1224,7 @@ describe('Wallet service', function() {
should.exist(status.wallet); should.exist(status.wallet);
status.wallet.name.should.equal(wallet.name); status.wallet.name.should.equal(wallet.name);
should.exist(status.wallet.copayers); should.exist(status.wallet.copayers);
status.wallet.copayers.length.should.equal(1); status.wallet.copayers.length.should.equal(2);
should.exist(status.balance); should.exist(status.balance);
status.balance.totalAmount.should.equal(0); status.balance.totalAmount.should.equal(0);
should.exist(status.preferences); should.exist(status.preferences);
@ -1364,74 +1330,10 @@ describe('Wallet service', function() {
describe('#createAddress', function() { describe('#createAddress', function() {
var server, wallet; var server, wallet;
beforeEach(function(done) {
helpers.createAndJoinWallet(2, 2, function(s, w) {
server = s;
wallet = w;
done();
});
});
it('should create address', function(done) { describe('shared wallets (BIP45)', function() {
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');
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notif = _.find(notifications, {
type: 'NewAddress'
});
should.exist(notif);
notif.data.address.should.equal('36q2G5FMGvJbPgAVEaiyAsFGmpkhPKwk2r');
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) {
sinon.stub(server.storage, 'storeAddressAndWallet').yields('dummy error');
server.createAddress({}, function(err, address) {
should.exist(err);
should.not.exist(address);
server.getMainAddresses({}, function(err, addresses) {
addresses.length.should.equal(0);
server.storage.storeAddressAndWallet.restore();
server.createAddress({}, function(err, address) {
should.not.exist(err);
should.exist(address);
done();
});
});
});
});
describe('BIP45', function() {
beforeEach(function(done) { beforeEach(function(done) {
helpers.createAndJoinWallet(2, 2, { helpers.createAndJoinWallet(2, 2, function(s, w) {
derivationStrategy: 'BIP45'
}, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
done(); done();
@ -1453,7 +1355,7 @@ describe('Wallet service', function() {
type: 'NewAddress' type: 'NewAddress'
}); });
should.exist(notif); should.exist(notif);
notif.data.address.should.equal('3BVJZ4CYzeTtawDtgwHvWV5jbvnXtYe97i'); notif.data.address.should.equal(address.address);
done(); done();
}); });
}); });
@ -1473,6 +1375,72 @@ describe('Wallet service', function() {
done(); done();
}); });
}); });
it('should not create address if unable to store it', function(done) {
sinon.stub(server.storage, 'storeAddressAndWallet').yields('dummy error');
server.createAddress({}, function(err, address) {
should.exist(err);
should.not.exist(address);
server.getMainAddresses({}, function(err, addresses) {
addresses.length.should.equal(0);
server.storage.storeAddressAndWallet.restore();
server.createAddress({}, function(err, address) {
should.not.exist(err);
should.exist(address);
done();
});
});
});
});
});
describe('1-of-1 (BIP44)', function() {
beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
w.copayers[0].id.should.equal(TestData.copayers[0].id44);
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('3J4J9nkFpzQjUGDh5hLKMKztFSPWMKejKE');
address.isChange.should.be.false;
address.path.should.equal('m/0/0');
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();
});
});
}); });
}); });
@ -1679,10 +1647,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_44H_0H_0H; var xPrivKey = TestData.copayers[0].xPrivKey_45H;
var sig = WalletUtils.signRequestPubKey(requestPubKey, xPrivKey); var sig = WalletUtils.signRequestPubKey(requestPubKey, xPrivKey);
var copayerId = WalletUtils.xPubToCopayerId(TestData.copayers[0].xPubKey_44H_0H_0H); var copayerId = WalletUtils.xPubToCopayerId(TestData.copayers[0].xPubKey_45H);
opts = { opts = {
copayerId: copayerId, copayerId: copayerId,
requestPubKey: requestPubKey, requestPubKey: requestPubKey,
@ -1693,7 +1661,9 @@ describe('Wallet service', function() {
describe('#addAccess 1-1', function() { describe('#addAccess 1-1', function() {
beforeEach(function(done) { beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, function(s, w) { helpers.createAndJoinWallet(1, 1, {
supportBIP44: false
}, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
@ -1703,7 +1673,7 @@ describe('Wallet service', function() {
}); });
}); });
it('should be able to re-gain access from xPrivKey', function(done) { it('should be able to re-gain access from xPrivKey', function(done) {
ws.addAccess(opts, function(err, res) { ws.addAccess(opts, function(err, res) {
should.not.exist(err); should.not.exist(err);
res.wallet.copayers[0].requestPubKeys.length.should.equal(2); res.wallet.copayers[0].requestPubKeys.length.should.equal(2);
@ -4387,124 +4357,125 @@ describe('Wallet service', function() {
describe('#scan', function() { describe('#scan', function() {
var server, wallet; var server, wallet;
var scanConfigOld = WalletService.SCAN_CONFIG; var scanConfigOld = WalletService.SCAN_CONFIG;
beforeEach(function(done) {
this.timeout(5000);
WalletService.SCAN_CONFIG.scanWindow = 2;
WalletService.SCAN_CONFIG.derivationDelay = 0;
helpers.createAndJoinWallet(1, 2, function(s, w) { describe('1-of-1 wallet (BIP44)', function() {
server = s; beforeEach(function(done) {
wallet = w; this.timeout(5000);
done(); WalletService.SCAN_CONFIG.scanWindow = 2;
WalletService.SCAN_CONFIG.derivationDelay = 0;
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
done();
});
});
afterEach(function() {
WalletService.SCAN_CONFIG = scanConfigOld;
}); });
});
afterEach(function() {
WalletService.SCAN_CONFIG = scanConfigOld;
});
it('should scan main addresses', function(done) { it('should scan main addresses', function(done) {
helpers.stubAddressActivity( helpers.stubAddressActivity(
['3Jc2hcN13vGBCdWR4SMNRoL9EUiBHuf4LT', // m/0/0 ['3J4J9nkFpzQjUGDh5hLKMKztFSPWMKejKE', // m/0/0
'3Pk2Cn3TW6JLhGEnHg8iM2qtmx2T1uHyZM', // m/0/2 '384JHSf9kVBs3yXsPwCzEScRs395u8hwxj', // m/0/2
'3GqrwYwyLt1czKMvvqFwYj2ZeooiHtbgbc', // m/1/0 '3NgXBiMQvwcRU8khVoPFJ6gsbGg9ZYrRzH', // m/1/0
]); ]);
var expectedPaths = [ var expectedPaths = [
'm/0/0', 'm/0/0',
'm/0/1', 'm/0/1',
'm/0/2', 'm/0/2',
'm/0/3', 'm/0/3',
'm/1/0', 'm/1/0',
'm/1/1', 'm/1/1',
]; ];
server.scan({}, function(err) { server.scan({}, function(err) {
should.not.exist(err);
server.getWallet({}, function(err, wallet) {
should.not.exist(err); should.not.exist(err);
wallet.scanStatus.should.equal('success'); server.getWallet({}, function(err, wallet) {
server.storage.fetchAddresses(wallet.id, function(err, addresses) { should.not.exist(err);
should.exist(addresses); wallet.scanStatus.should.equal('success');
addresses.length.should.equal(expectedPaths.length); server.storage.fetchAddresses(wallet.id, function(err, addresses) {
var paths = _.pluck(addresses, 'path'); should.exist(addresses);
_.difference(paths, expectedPaths).length.should.equal(0); addresses.length.should.equal(expectedPaths.length);
server.createAddress({}, function(err, address) { var paths = _.pluck(addresses, 'path');
_.difference(paths, expectedPaths).length.should.equal(0);
server.createAddress({}, function(err, address) {
should.not.exist(err);
address.path.should.equal('m/0/4');
done();
});
});
});
});
});
it('should restore wallet balance', function(done) {
async.waterfall([
function(next) {
helpers.stubUtxos(server, wallet, [1, 2, 3], function(utxos) {
should.exist(utxos);
helpers.stubAddressActivity(_.pluck(utxos, 'address'));
server.getBalance({}, function(err, balance) {
balance.totalAmount.should.equal(helpers.toSatoshi(6));
next(null, server, wallet);
});
});
},
function(server, wallet, next) {
server.removeWallet({}, function(err) {
next(err);
});
},
function(next) {
// NOTE: this works because it creates the exact same wallet!
helpers.createAndJoinWallet(1, 1, function(server, wallet) {
server.getBalance({}, function(err, balance) {
balance.totalAmount.should.equal(0);
next(null, server, wallet);
});
});
},
function(server, wallet, next) {
server.scan({}, function(err) {
should.not.exist(err); should.not.exist(err);
address.path.should.equal('m/0/4'); server.getBalance(wallet.id, function(err, balance) {
balance.totalAmount.should.equal(helpers.toSatoshi(6));
next();
})
});
},
], function(err) {
should.not.exist(err);
done();
});
});
it('should abort scan if there is an error checking address activity', function(done) {
blockchainExplorer.getAddressActivity = sinon.stub().callsArgWith(1, 'dummy error');
server.scan({}, function(err) {
should.exist(err);
err.toString().should.equal('dummy error');
server.getWallet({}, function(err, wallet) {
should.not.exist(err);
wallet.scanStatus.should.equal('error');
wallet.addressManager.receiveAddressIndex.should.equal(0);
wallet.addressManager.changeAddressIndex.should.equal(0);
server.storage.fetchAddresses(wallet.id, function(err, addresses) {
should.not.exist(err);
addresses.should.be.empty;
done(); done();
}); });
}); });
}); });
}); });
}); });
it('should restore wallet balance', function(done) {
async.waterfall([
function(next) { describe('shared wallet (BIP45)', function() {
helpers.stubUtxos(server, wallet, [1, 2, 3], function(utxos) {
should.exist(utxos);
helpers.stubAddressActivity(_.pluck(utxos, 'address'));
server.getBalance({}, function(err, balance) {
balance.totalAmount.should.equal(helpers.toSatoshi(6));
next(null, server, wallet);
});
});
},
function(server, wallet, next) {
server.removeWallet({}, function(err) {
next(err);
});
},
function(next) {
// NOTE: this works because it creates the exact same wallet!
helpers.createAndJoinWallet(1, 2, function(server, wallet) {
server.getBalance({}, function(err, balance) {
balance.totalAmount.should.equal(0);
next(null, server, wallet);
});
});
},
function(server, wallet, next) {
server.scan({}, function(err) {
should.not.exist(err);
server.getBalance(wallet.id, function(err, balance) {
balance.totalAmount.should.equal(helpers.toSatoshi(6));
next();
})
});
},
], function(err) {
should.not.exist(err);
done();
});
});
it('should abort scan if there is an error checking address activity', function(done) {
blockchainExplorer.getAddressActivity = sinon.stub().callsArgWith(1, 'dummy error');
server.scan({}, function(err) {
should.exist(err);
err.toString().should.equal('dummy error');
server.getWallet({}, function(err, wallet) {
should.not.exist(err);
wallet.scanStatus.should.equal('error');
wallet.addressManager.receiveAddressIndex.should.equal(0);
wallet.addressManager.changeAddressIndex.should.equal(0);
server.storage.fetchAddresses(wallet.id, function(err, addresses) {
should.not.exist(err);
addresses.should.be.empty;
done();
});
});
});
});
describe('BIP45', function() {
beforeEach(function(done) { beforeEach(function(done) {
this.timeout(5000); this.timeout(5000);
WalletService.SCAN_CONFIG.scanWindow = 2; WalletService.SCAN_CONFIG.scanWindow = 2;
WalletService.SCAN_CONFIG.derivationDelay = 0; WalletService.SCAN_CONFIG.derivationDelay = 0;
helpers.createAndJoinWallet(1, 2, { helpers.createAndJoinWallet(1, 2, function(s, w) {
derivationStrategy: 'BIP45'
}, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
done(); done();
@ -4591,7 +4562,7 @@ 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, 1, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
done(); done();
@ -4604,9 +4575,9 @@ describe('Wallet service', function() {
it('should start an asynchronous scan', function(done) { it('should start an asynchronous scan', function(done) {
helpers.stubAddressActivity( helpers.stubAddressActivity(
['3Jc2hcN13vGBCdWR4SMNRoL9EUiBHuf4LT', // m/0/0 ['3J4J9nkFpzQjUGDh5hLKMKztFSPWMKejKE', // m/0/0
'3Pk2Cn3TW6JLhGEnHg8iM2qtmx2T1uHyZM', // m/0/2 '384JHSf9kVBs3yXsPwCzEScRs395u8hwxj', // m/0/2
'3GqrwYwyLt1czKMvvqFwYj2ZeooiHtbgbc', // m/1/0 '3NgXBiMQvwcRU8khVoPFJ6gsbGg9ZYrRzH', // m/1/0
]); ]);
var expectedPaths = [ var expectedPaths = [
'm/0/0', 'm/0/0',