This commit is contained in:
Matias Alejo Garcia 2015-02-17 02:20:04 -03:00
parent d56dcd41de
commit 196ae2a448
3 changed files with 218 additions and 82 deletions

View File

@ -62,35 +62,49 @@ function API(opts) {
}; };
API.prototype._isWalletCorrupt = function(wallet, data) {
var pubKey = Bitcore.PrivateKey.fromString(data.walletPrivKey).toPublicKey().toString();
var fake = [];
if (data.n != wallet.copayers.length)
return true;
var uniq = [];
_.each(wallet.copayers, function(copayer) {
if (uniq[copayer.xPubKey]++)
return true;
if (!SignUtils.verify(copayer.xPubKey, copayer.xPubKeySignature, pubKey)) {
fake.push(copayer);
}
});
return fake.length > 0;
};
API.prototype._tryToComplete = function(data, cb) { API.prototype._tryToComplete = function(data, cb) {
var self = this; var self = this;
var isCorrupted;
var url = '/v1/wallets/'; var url = '/v1/wallets/';
self._doGetRequest(url, data, function(err, wallet) { self._doGetRequest(url, data, function(err, wallet) {
if (err) return cb(err); if (err) return cb(err);
if (wallet.n > 0 && wallet.status === 'complete' && !data.verified) {
var pubKey = Bitcore.PrivateKey.fromString(data.walletPrivKey).toPublicKey().toString(); if (wallet.status === 'complete' && !data.verified) {
var fake = [];
_.each(wallet.copayers, function(copayer) { if (self._isWalletCorrupt(wallet, data)) {
if (!SignUtils.verify(copayer.xPubKey, copayer.xPubKeySignature, pubKey)) {
fake.push(copayer);
}
});
if (fake.length > 0) {
isCorrupted = true;
data.verified = 'corrupt'; data.verified = 'corrupt';
} else { } else {
data.verified = 'ok'; data.verified = 'ok';
} }
self.storage.save(data, function(err) { self.storage.save(data, function(err) {
if (isCorrupted) { if (data.verified == 'corrupt') {
return cb('Some copayers in the wallet could not be verified to have known the wallet secret'); return cb('Some copayers in the wallet could not be verified to have known the wallet secret');
} }
return cb(err, data); return cb(err, data);
}); });
} else {
return cb('Wallet Incomplete');
} }
return cb(null, data);
}); });
}; };
@ -98,12 +112,12 @@ API.prototype._tryToComplete = function(data, cb) {
API.prototype._loadAndCheck = function(cb) { API.prototype._loadAndCheck = function(cb) {
var self = this; var self = this;
this.storage.load(function(err, data) { this.storage.load(function(err, data) {
if (err || !data) { if (err || !data) {
return cb(err || 'Wallet file not found.'); return cb(err || 'Wallet file not found.');
} }
if (data.verified == 'corrupt') { if (data.verified == 'corrupt') {
return cb('The wallet is tagged as corrupt. Some of the copayers cannot be verified to have known the wallet secret.'); return cb('The wallet is tagged as corrupt. Some of the copayers cannot be verified to have known the wallet secret.');
} }
@ -131,10 +145,13 @@ API.prototype._doRequest = function(method, url, args, data, cb) {
body: args, body: args,
json: true, json: true,
}; };
log.verbose('Request Args', util.inspect(args)); log.verbose('Request Args', util.inspect(args, {
depth: 10
}));
this.request(args, function(err, res, body) { this.request(args, function(err, res, body) {
log.verbose('Response:', err, body); log.verbose(util.inspect(body, {
depth: 10
}));
if (err) return cb(err); if (err) return cb(err);
if (res.statusCode != 200) { if (res.statusCode != 200) {
_parseError(body); _parseError(body);
@ -279,34 +296,32 @@ API.prototype.getStatus = function(cb) {
API.prototype.sendTxProposal = function(inArgs, cb) { API.prototype.sendTxProposal = function(inArgs, cb) {
var self = this; var self = this;
this._loadAndCheck( this._loadAndCheck(function(err, data) {
function(err, data) { if (err) return cb(err);
if (err) return cb(err);
var args = _createProposalOpts(inArgs, data.signingPrivKey); var args = _createProposalOpts(inArgs, data.signingPrivKey);
var url = '/v1/txproposals/'; var url = '/v1/txproposals/';
self._doPostRequest(url, args, data, cb); self._doPostRequest(url, args, data, cb);
}); });
}; };
API.prototype.createAddress = function(cb) { API.prototype.createAddress = function(cb) {
var self = this; var self = this;
this._loadAndCheck( this._loadAndCheck(function(err, data) {
function(err, data) { if (err) return cb(err);
var url = '/v1/addresses/';
self._doPostRequest(url, {}, data, function(err, address) {
if (err) return cb(err); if (err) return cb(err);
if (!Verifier.checkAddress(data, address)) {
return cb(new ServerCompromisedError('Server sent fake address'));
}
var url = '/v1/addresses/'; return cb(null, address);
self._doPostRequest(url, {}, data, function(err, address) {
if (err) return cb(err);
if (!Verifier.checkAddress(data, address)) {
return cb(new ServerCompromisedError('Server sent fake address'));
}
return cb(null, address);
});
}); });
});
}; };
API.prototype.history = function(limit, cb) { API.prototype.history = function(limit, cb) {

View File

@ -7,51 +7,95 @@ var should = chai.should();
var Client = require('../../lib/client'); var Client = require('../../lib/client');
var API = Client.API; var API = Client.API;
var Bitcore = require('bitcore'); var Bitcore = require('bitcore');
var TestData = require('./clienttestdata');
var wallet11 = { describe(' client API ', function() {
"m": 1,
"n": 1,
"walletPrivKey": "{\"bn\":\"6b862ffbfc90a37a2fedbbcfea91c6a4e49f49b6aaa322b6e16c46bfdbe71a38\",\"compressed\":true,\"network\":\"livenet\"}",
"network": "testnet",
"xPrivKey": "tprv8ZgxMBicQKsPeisyNJteQXZnb7CnhYc4TVAyxxicXuxMjK1rmaqVq1xnXtbSTPxUKKL9h5xJhUvw1AKfDD3i98A82eJWSYRWYjmPksewFKR",
"copayerId": "a84daa08-17b5-45ad-84cd-e275f3b07123",
"signingPrivKey": "42798f82c4ed9ace4d66335165071edf180e70bc0fc08dacb3e35185a2141d5b",
"publicKeyRing": ["tpubD6NzVbkrYhZ4YBumFxZEowDuA8iirsny2nmmFUkuxBkkZoGdPyf61Waei3tDYvVa1yqW82Xhmmd6oiibeDyM1MS3zTiky7Yg75UEV9oQhFJ"]
};
var incompleteWallet22 = {
"m": 2,
"n": 2,
"walletPrivKey": "L3XSE3KNjQM1XRP1h5yMCSKsN4hs3D6eK7Vwn5M88Bs6jpCnXR3R",
"network": "testnet",
"secret": "d9cf45a1-6793-4df4-94df-c99d2c2e1fe9:bc2488c1b83e455a4b908a0d0aeaf70351efc48fbcaa454bffefdef419a5ee6a:T",
"xPrivKey": "tprv8ZgxMBicQKsPdoC5DGtnXx7fp7YnUtGv8b7fU2oDQfDpHFQh1QCgpKc8GHpdsBN5THaHYMV5LgD5cP5NYaacGVr786p3mVLSZff9berTV8h",
"copayerId": "c3a33ca0-37cf-4e80-b745-71272683835c",
"signingPrivKey": "6e129c4996666e5ecdf78aed626c01977fa19eacce6659738ebe065f86523e9b",
"publicKeyRing": []
};
describe('client API', function() {
var client; var client;
beforeEach(function() { beforeEach(function() {
var fsmock = {};; var fsmock = {};;
fsmock.readFile = sinon.mock().yields(null, JSON.stringify(wallet11)); fsmock.readFile = sinon.mock().yields(null, JSON.stringify(TestData.storage.wallet11));
fsmock.writeFile = sinon.mock().yields(); fsmock.writeFile = sinon.mock().yields();
var storage = new Client.FileStorage({ var storage = new Client.FileStorage({
filename: 'dummy', filename: 'dummy',
fs: fsmock, fs: fsmock,
}); });
client = new Client({ client = new Client({
storage: storage, storage: storage
}); });
}); });
describe('createAddress', function() { describe(' _tryToComplete ', function() {
it('should check address', function(done) { it('should complete a wallet ', function(done) {
var request = sinon.stub();
// Wallet request
request.onCall(0).yields(null, {
statusCode: 200,
}, TestData.serverResponse.completeWallet);
request.onCall(1).yields(null, {
statusCode: 200,
}, "pepe");
client.request = request;
client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22));
client.getBalance(function(err, x) {
should.not.exist(err);
done();
});
})
it('should complain wallet is not complete ', function(done) {
var request = sinon.stub();
// Wallet request
request.onCall(0).yields(null, {
statusCode: 200,
}, TestData.serverResponse.incompleteWallet);
client.request = request;
client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22));
client.createAddress(function(err, x) {
err.should.contain('Incomplete');
done();
});
})
it(' should reject wallets with bad signatures', function(done) {
var request = sinon.stub();
// Wallet request
request.onCall(0).yields(null, {
statusCode: 200,
}, TestData.serverResponse.corruptWallet22);
client.request = request;
client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22));
client.createAddress(function(err, x) {
err.should.contain('verified');
done();
});
})
it(' should reject wallets with missing signatures ', function(done) {
var request = sinon.stub();
// Wallet request
request.onCall(0).yields(null, {
statusCode: 200,
}, TestData.serverResponse.corruptWallet222);
client.request = request;
client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22));
client.createAddress(function(err, x) {
err.should.contain('verified');
done();
});
})
});
describe(' createAddress ', function() {
it(' should check address ', function(done) {
var response = { var response = {
createdOn: 1424105995, createdOn: 1424105995,
@ -71,7 +115,7 @@ describe('client API', function() {
done(); done();
}); });
}) })
it('should detect fake addresses', function(done) { it(' should detect fake addresses ', function(done) {
var response = { var response = {
createdOn: 1424105995, createdOn: 1424105995,
address: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq', address: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq',
@ -88,20 +132,5 @@ describe('client API', function() {
done(); done();
}); });
}) })
})
it('should complain wallet is not complete', function(done) {
var request = sinon.mock().yields(null, {
statusCode: 200
}, {
dummy: true
});
client.request = request;
client.storage.fs.readFile = sinon.mock().yields(null, JSON.stringify(incompleteWallet22));
client.createAddress(function(err, x) {
err.should.contain('Incomplete');
done();
});
})
});
}); });

View File

@ -0,0 +1,92 @@
var storage = {
wallet11: {
"m": 1,
"n": 1,
"walletPrivKey": "{\"bn\":\"6b862ffbfc90a37a2fedbbcfea91c6a4e49f49b6aaa322b6e16c46bfdbe71a38\",\"compressed\":true,\"network\":\"livenet\"}",
"network": "testnet",
"xPrivKey": "tprv8ZgxMBicQKsPeisyNJteQXZnb7CnhYc4TVAyxxicXuxMjK1rmaqVq1xnXtbSTPxUKKL9h5xJhUvw1AKfDD3i98A82eJWSYRWYjmPksewFKR",
"copayerId": "a84daa08-17b5-45ad-84cd-e275f3b07123",
"signingPrivKey": "42798f82c4ed9ace4d66335165071edf180e70bc0fc08dacb3e35185a2141d5b",
"publicKeyRing": ["tpubD6NzVbkrYhZ4YBumFxZEowDuA8iirsny2nmmFUkuxBkkZoGdPyf61Waei3tDYvVa1yqW82Xhmmd6oiibeDyM1MS3zTiky7Yg75UEV9oQhFJ"]
},
incompleteWallet22: {
"m": 2,
"n": 2,
"walletPrivKey":"L2Fu6TM1AqSNBaQcjgjvYjGf3EzS3MVSTwEeTw3bvy52x7ZkffWj",
"network": "testnet",
"secret": "b6f57154-0df8-4845-a61d-47ecd648c2d4:eab5a55d9214845ee8d13ea1033e42ec8d7f780ae6e521d830252a80433e91a5:T",
"xPrivKey": "tprv8ZgxMBicQKsPfFVXegcKyJjy2Y5DSrHNrtGBHG1f9pPX75QQdHwHGjWUtR7cCUXV7QcCCDon4cieHWTYscy8M7oXwF3qd3ssfBiV9M68bPB",
"copayerId": "3fc03e7a-6ebc-409b-a4b7-45b14d5a8199",
"signingPrivKey": "0d3c796fb12e387c4b5a5c566312b2b22fa0553ca041d859e3f0987215ca3a4f",
"publicKeyRing": []
}
};
var serverResponse = {
completeWallet: {
m: 2,
n: 2,
status: 'complete',
publicKeyRing: ['tpubD6NzVbkrYhZ4Y1CGuCZ88eZvhDSTjAqjotZWGXC7e4GEoyXq3SQgZK9iRz4qC2h8MrzqrYBndCMQDiaaLdqpY8ihYmJC9Msvns83jGopb3E',
'tpubD6NzVbkrYhZ4WSuBBLyubi8DHMipbFQcZoLJHjb21gEtznCEJMJhwkvaSshHVLtq8C1uNMKD4GtADVYY6WZt1cyT218JUm3PiNKYVkMATWV'
],
addressIndex: 0,
copayers: [{
xPubKey: 'tpubD6NzVbkrYhZ4Y1CGuCZ88eZvhDSTjAqjotZWGXC7e4GEoyXq3SQgZK9iRz4qC2h8MrzqrYBndCMQDiaaLdqpY8ihYmJC9Msvns83jGopb3E',
xPubKeySignature: '3045022100ef86122060bbb7681db05486f8b1ee1579c5800e8da78182a87384f05542a4cc0220215ce7ef8c484b64178779414efdf2b7033d25ed752eebf4eb3241f9fa8e6b67',
}, {
xPubKey: 'tpubD6NzVbkrYhZ4WSuBBLyubi8DHMipbFQcZoLJHjb21gEtznCEJMJhwkvaSshHVLtq8C1uNMKD4GtADVYY6WZt1cyT218JUm3PiNKYVkMATWV',
xPubKeySignature: '3044022025c93b418ebdbb66a0f2b21af709420e8ae769bf054f29aaa252cb5417c46a2302205e0c8b931324736b7eea4971a48039614e19abe26e13ab0ef1547aef92b55aab',
}],
pubKey: ' { "x": "b2903ab878ed1316f82b859e9807e23bab3d579175563e1068d2ed9c9e37873c", "y": "5f30165915557394223a58329c1527dfa0f34f483d8aed02e0638f9124dbddef", "compressed": true }',
network: 'testnet',
},
incompleteWallet: {
m: 2,
n: 2,
status: 'pending',
publicKeyRing: ['tpubD6NzVbkrYhZ4Y1CGuCZ88eZvhDSTjAqjotZWGXC7e4GEoyXq3SQgZK9iRz4qC2h8MrzqrYBndCMQDiaaLdqpY8ihYmJC9Msvns83jGopb3E',
'tpubD6NzVbkrYhZ4WSuBBLyubi8DHMipbFQcZoLJHjb21gEtznCEJMJhwkvaSshHVLtq8C1uNMKD4GtADVYY6WZt1cyT218JUm3PiNKYVkMATWV'
],
addressIndex: 0,
copayers: [{
xPubKey: 'tpubD6NzVbkrYhZ4Y1CGuCZ88eZvhDSTjAqjotZWGXC7e4GEoyXq3SQgZK9iRz4qC2h8MrzqrYBndCMQDiaaLdqpY8ihYmJC9Msvns83jGopb3E',
xPubKeySignature: '3045022100ef86122060bbb7681db05486f8b1ee1579c5800e8da78182a87384f05542a4cc0220215ce7ef8c484b64178779414efdf2b7033d25ed752eebf4eb3241f9fa8e6b67',
}],
pubKey: ' { "x": "b2903ab878ed1316f82b859e9807e23bab3d579175563e1068d2ed9c9e37873c", "y": "5f30165915557394223a58329c1527dfa0f34f483d8aed02e0638f9124dbddef", "compressed": true }',
network: 'testnet',
},
corruptWallet22: {
m: 2,
n: 2,
status: 'complete',
publicKeyRing: ['tpubD6NzVbkrYhZ4Y1CGuCZ88eZvhDSTjAqjotZWGXC7e4GEoyXq3SQgZK9iRz4qC2h8MrzqrYBndCMQDiaaLdqpY8ihYmJC9Msvns83jGopb3E',
'tpubD6NzVbkrYhZ4WSuBBLyubi8DHMipbFQcZoLJHjb21gEtznCEJMJhwkvaSshHVLtq8C1uNMKD4GtADVYY6WZt1cyT218JUm3PiNKYVkMATWV'
],
copayers: [{
xPubKey: 'tpubD6NzVbkrYhZ4Y1CGuCZ88eZvhDSTjAqjotZWGXC7e4GEoyXq3SQgZK9iRz4qC2h8MrzqrYBndCMQDiaaLdqpY8ihYmJC9Msvns83jGopb3E',
xPubKeySignature: '3045022100ef86122060bbb7681db05486f8b1ee1579c5800e8da78182a87384f05542a4cc0220215ce7ef8c484b64178779414efdf2b7033d25ed752eebf4eb3241f9fa8e6b67',
}, {
xPubKey: 'tpubD6NzVbkrYhZ4Y1CGuCZ88eZvhDSTjAqjotZWGXC7e4GEoyXq3SQgZK9iRz4qC2h8MrzqrYBndCMQDiaaLdqpY8ihYmJC9Msvns83jGopb3E',
xPubKeySignature: 'bababa',
}],
},
corruptWallet222: {
m: 2,
n: 2,
status: 'complete',
publicKeyRing: ['tpubD6NzVbkrYhZ4Y1CGuCZ88eZvhDSTjAqjotZWGXC7e4GEoyXq3SQgZK9iRz4qC2h8MrzqrYBndCMQDiaaLdqpY8ihYmJC9Msvns83jGopb3E',
'tpubD6NzVbkrYhZ4WSuBBLyubi8DHMipbFQcZoLJHjb21gEtznCEJMJhwkvaSshHVLtq8C1uNMKD4GtADVYY6WZt1cyT218JUm3PiNKYVkMATWV'
],
copayers: [{
xPubKey: 'tpubD6NzVbkrYhZ4Y1CGuCZ88eZvhDSTjAqjotZWGXC7e4GEoyXq3SQgZK9iRz4qC2h8MrzqrYBndCMQDiaaLdqpY8ihYmJC9Msvns83jGopb3E',
}, ],
}
};
module.exports.serverResponse = serverResponse;
module.exports.storage = storage;