adds addAccess call and tests

This commit is contained in:
Matias Alejo Garcia 2015-08-05 16:53:06 -03:00
parent edc3bc6713
commit dac564f290
5 changed files with 269 additions and 77 deletions

View File

@ -15,8 +15,10 @@ var errors = {
INSUFFICIENT_FUNDS: 'Insufficient funds',
INSUFFICIENT_FUNDS_FOR_FEE: 'Insufficient funds for fee',
INVALID_ADDRESS: 'Invalid address',
KEY_IN_COPAYER: 'Key already registered',
LOCKED_FUNDS: 'Funds are locked by pending transaction proposals',
NOT_AUTHORIZED: 'Not authorized',
TO_MANY_KEYS: 'To many keys registered',
TX_ALREADY_BROADCASTED: 'The transaction proposal is already broadcasted',
TX_CANNOT_CREATE: 'Cannot create TX proposal during backoff time',
TX_CANNOT_REMOVE: 'Cannot remove this tx proposal during locktime',

View File

@ -70,6 +70,8 @@ TxProposal.create = function(opts) {
x.fee = null;
x.feePerKb = opts.feePerKb;
x.excludeUnconfirmedUtxos = opts.excludeUnconfirmedUtxos;
x.proposalSignaturePubKey = opts.proposalSignaturePubKey;
x.proposalSignaturePubKeySig = opts.proposalSignaturePubKeySig;
if (_.isFunction(TxProposal._create[x.type])) {
TxProposal._create[x.type](x, opts);
@ -114,6 +116,8 @@ TxProposal.fromObj = function(obj) {
x.network = obj.network;
x.feePerKb = obj.feePerKb;
x.excludeUnconfirmedUtxos = obj.excludeUnconfirmedUtxos;
x.proposalSignaturePubKey = obj.proposalSignaturePubKey;
x.proposalSignaturePubKeySig = obj.proposalSignaturePubKeySig;
return x;
};

View File

@ -107,21 +107,18 @@ Wallet.prototype.addCopayer = function(copayer) {
this._updatePublicKeyRing();
};
Wallet.prototype.addCopayerRequestKey = function(copayerId, requestPubKey, signature) {
Wallet.prototype.addCopayerRequestKey = function(copayerId, requestPubKey, signature, restrictions) {
$.checkState(this.copayers.length == this.n);
var c = _.find(this.copayers, {
id: copayerId,
});
$.checkState(c);
var c = this.getCopayer(copayerId);
//new ones go first
c.requestPubKeys.unshift({
key: requestPubKey,
key: requestPubKey.toString(),
signature: signature,
selfSigned: true,
restrictions: restrictions,
});
//this._updatePublicKeyRing();
};
Wallet.prototype.getCopayer = function(copayerId) {

View File

@ -33,7 +33,7 @@ var blockchainExplorer;
var blockchainExplorerOpts;
var messageBroker;
var MAX_KEYS = 100;
/**
* Creates an instance of the Bitcore Wallet Service.
@ -177,7 +177,7 @@ WalletService.getInstanceWithAuth = function(opts, cb) {
if (err) return cb(err);
if (!copayer) return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Copayer not found'));
var isValid = server._verifySignatureAgainstArray(opts.message, opts.signature, copayer.requestPubKeys);
var isValid = !!server._getSigningKey(opts.message, opts.signature, copayer.requestPubKeys);
if (!isValid)
return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Invalid signature'));
@ -289,9 +289,9 @@ WalletService.prototype._verifySignature = function(text, signature, pubkey) {
* @param signature
* @param pubKeys
*/
WalletService.prototype._verifySignatureAgainstArray = function(text, signature, pubKeys) {
WalletService.prototype._getSigningKey = function(text, signature, pubKeys) {
var self = this;
return _.any(pubKeys, function(item) {
return _.find(pubKeys, function(item) {
return self._verifySignature(text, signature, item.key);
});
};
@ -337,6 +337,110 @@ WalletService.prototype._notify = function(type, data, opts, cb) {
};
WalletService.prototype._addCopayerToWallet = function(wallet, opts, cb) {
var self = this;
if (wallet.copayers.length == wallet.n) return cb(Errors.WALLET_FULL);
var copayer = Model.Copayer.create({
name: opts.name,
copayerIndex: wallet.copayers.length,
xPubKey: opts.xPubKey,
requestPubKey: opts.requestPubKey,
signature: opts.copayerSignature,
});
self.storage.fetchCopayerLookup(copayer.id, function(err, res) {
if (err) return cb(err);
if (res) return cb(Errors.COPAYER_REGISTERED);
wallet.addCopayer(copayer);
self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) {
if (err) return cb(err);
async.series([
function(next) {
self._notify('NewCopayer', {
walletId: opts.walletId,
copayerId: copayer.id,
copayerName: copayer.name,
}, next);
},
function(next) {
if (wallet.isComplete() && wallet.isShared()) {
self._notify('WalletComplete', {
walletId: opts.walletId,
}, {
isGlobal: true
}, next);
} else {
next();
}
},
], function() {
return cb(null, {
copayerId: copayer.id,
wallet: wallet
});
});
});
});
};
WalletService.prototype._addKeyToCopayer = function(wallet, copayer, opts, cb) {
var self = this;
wallet.addCopayerRequestKey(copayer.copayerId, opts.requestPubKey, opts.signature, opts.restrictions);
self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) {
if (err) return cb(err);
return cb(null, {
copayerId: copayer.id,
wallet: wallet
});
});
};
/**
* Adds access to a given copayer
*
* @param {Object} opts
* @param {string} opts.copayerId - The copayer id
* @param {string} opts.requestPubKey - Public Key used to check requests from this copayer.
* @param {string} opts.copayerSignature - S(requestPubKey). Used by other copayers to verify the that the copayer is himself (signed with REQUEST_KEY_AUTH)
* @param {string} opts.restrictions
* - cannotProposeTXs
* - cannotXXX TODO
*/
WalletService.prototype.addAccess = function(opts, cb) {
var self = this;
if (!Utils.checkRequired(opts, ['copayerId', 'requestPubKey', 'signature']))
return cb(new ClientError('Required argument missing'));
self.storage.fetchCopayerLookup(opts.copayerId, function(err, copayer) {
if (err) return cb(err);
if (!copayer) return cb(Errors.NOT_AUTHORIZED);
self.storage.fetchWallet(copayer.walletId, function(err, wallet) {
if (err) return cb(err);
if (!wallet) return cb(Errors.NOT_AUTHORIZED);
var xPubKey = _.find(wallet.copayers, {
id: opts.copayerId
}).xPubKey;
if (!WalletUtils.checkRequestPubKey(opts.requestPubKey, opts.signature, xPubKey)) {
return cb(Errors.NOT_AUTHORIZED);
}
if (copayer.requestPubKeys.length > MAX_KEYS)
return cb(Errors.TO_MANY_KEYS);
self._addKeyToCopayer(wallet, copayer, opts, cb);
});
});
};
/**
* Joins a wallet in creation.
* @param {Object} opts
@ -371,52 +475,7 @@ WalletService.prototype.joinWallet = function(opts, cb) {
xPubKey: opts.xPubKey
})) return cb(Errors.COPAYER_IN_WALLET);
if (wallet.copayers.length == wallet.n) return cb(Errors.WALLET_FULL);
var copayer = Model.Copayer.create({
name: opts.name,
copayerIndex: wallet.copayers.length,
xPubKey: opts.xPubKey,
requestPubKey: opts.requestPubKey,
signature: opts.copayerSignature,
});
self.storage.fetchCopayerLookup(copayer.id, function(err, res) {
if (err) return cb(err);
if (res) return cb(Errors.COPAYER_REGISTERED);
wallet.addCopayer(copayer);
self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) {
if (err) return cb(err);
async.series([
function(next) {
self._notify('NewCopayer', {
walletId: opts.walletId,
copayerId: copayer.id,
copayerName: copayer.name,
}, next);
},
function(next) {
if (wallet.isComplete() && wallet.isShared()) {
self._notify('WalletComplete', {
walletId: opts.walletId,
}, {
isGlobal: true
}, next);
} else {
next();
}
},
], function() {
return cb(null, {
copayerId: copayer.id,
wallet: wallet
});
});
});
});
self._addCopayerToWallet(wallet, opts, cb);
});
});
};
@ -559,7 +618,7 @@ WalletService.prototype.verifyMessageSignature = function(opts, cb) {
var copayer = wallet.getCopayer(self.copayerId);
var isValid = self._verifySignatureAgainstArray(opts.message, opts.signature, copayer.requestPubKeys);
var isValid = !!self._getSigningKey(opts.message, opts.signature, copayer.requestPubKeys);
return cb(null, isValid);
});
};
@ -707,7 +766,6 @@ WalletService.prototype.getBalance = function(opts, cb) {
self.getUtxos({}, function(err, utxos) {
if (err) return cb(err);
var balance = self._totalizeUtxos(utxos);
// Compute balance by address
@ -980,7 +1038,8 @@ WalletService.prototype.createTx = function(opts, cb) {
hash = WalletUtils.getProposalHash(header)
}
if (!self._verifySignatureAgainstArray(hash, opts.proposalSignature, copayer.requestPubKeys))
var signingKey = self._getSigningKey(hash, opts.proposalSignature, copayer.requestPubKeys)
if (!signingKey)
return cb(new ClientError('Invalid proposal signature'));
self._canCreateTx(self.copayerId, function(err, canCreate) {
@ -1014,7 +1073,7 @@ WalletService.prototype.createTx = function(opts, cb) {
valid: false
})) return;
var txp = Model.TxProposal.create({
var txOpts = {
type: type,
walletId: self.walletId,
creatorId: self.copayerId,
@ -1030,7 +1089,14 @@ WalletService.prototype.createTx = function(opts, cb) {
requiredRejections: Math.min(wallet.m, wallet.n - wallet.m + 1),
walletN: wallet.n,
excludeUnconfirmedUtxos: !!opts.excludeUnconfirmedUtxos,
});
};
if (signingKey.selfSigned) {
txOpts.proposalSignaturePubKey = signingKey.key;
txOpts.proposalSignaturePubKeySig = signingKey.signature;
}
var txp = Model.TxProposal.create(txOpts);
if (!self.clientVersion || /^bw.-0\.0\./.test(self.clientVersion)) {
txp.version = '1.0.1';

View File

@ -26,6 +26,7 @@ var WalletService = require('../../lib/server');
var EmailService = require('../../lib/emailservice');
var TestData = require('../testdata');
var CLIENT_VERSION = 'bwc-0.1.1';
var helpers = {};
helpers.getAuthServer = function(copayerId, cb) {
@ -1405,31 +1406,154 @@ describe('Wallet service', function() {
});
describe.skip('Multiple request Pub Keys', function() {
describe('Multiple request Pub Keys', function() {
var server, wallet;
beforeEach(function(done) {
helpers.createAndJoinWallet(2, 2, function(s, w) {
server = s;
wallet = w;
done();
var opts, reqPrivKey, ws;
var getAuthServer = function(copayerId, privKey, cb) {
var msg = 'dummy';
var sig = WalletUtils.signMessage(msg, privKey);
WalletService.getInstanceWithAuth({
copayerId: copayerId,
message: msg,
signature: sig,
clientVersion: CLIENT_VERSION,
}, function(err, server) {
return cb(err, server);
});
};
beforeEach(function() {
reqPrivKey = new Bitcore.PrivateKey();
var requestPubKey = reqPrivKey.toPublicKey();
var xPrivKey = TestData.copayers[0].xPrivKey_45H;
var sig = WalletUtils.signRequestPubKey(requestPubKey, xPrivKey);
var copayerId = WalletUtils.xPubToCopayerId(TestData.copayers[0].xPubKey_45H);
opts = {
copayerId: copayerId,
requestPubKey: requestPubKey,
signature: sig,
};
ws = new WalletService();
});
describe('#addAccess 1-1', function() {
beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
helpers.stubUtxos(server, wallet, 1, function() {
done();
});
});
});
it('#addCopayerRequestKey', function(done) {
helpers.stubUtxos(server, wallet, [1, 'u2', 3], function() {
server.getBalance({}, function(err, balance) {
it('should be able to re-gain access from xPrivKey', function(done) {
ws.addAccess(opts, function(err, res) {
should.not.exist(err);
should.exist(balance);
balance.totalAmount.should.equal(helpers.toSatoshi(6));
res.wallet.copayers[0].requestPubKeys.length.should.equal(2);
res.wallet.copayers[0].requestPubKeys[0].selfSigned.should.equal(true);
server.getBalance(res.wallet.walletId, function(err, bal) {
should.not.exist(err);
bal.totalAmount.should.equal(1e8);
getAuthServer(opts.copayerId, reqPrivKey, function(err, server2) {
server2.getBalance(res.wallet.walletId, function(err, bal2) {
should.not.exist(err);
bal2.totalAmount.should.equal(1e8);
done();
});
});
});
});
});
it('should fail to gain access with wrong xPrivKey', function(done) {
opts.signature = 'xx';
ws.addAccess(opts, function(err, res) {
err.code.should.equal('NOT_AUTHORIZED');
done();
});
});
it('should fail to access with wrong privkey after gaining access', function(done) {
ws.addAccess(opts, function(err, res) {
should.not.exist(err);
server.getBalance(res.wallet.walletId, function(err, bal) {
should.not.exist(err);
var privKey = new Bitcore.PrivateKey();
(getAuthServer(opts.copayerId, privKey, function(err, server2) {
err.code.should.equal('NOT_AUTHORIZED');
done();
}));
});
});
});
it('should be able to create TXs after regaining access', function(done) {
ws.addAccess(opts, function(err, res) {
should.not.exist(err);
getAuthServer(opts.copayerId, reqPrivKey, function(err, server2) {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, null, reqPrivKey);
server2.createTx(txOpts, function(err, tx) {
should.not.exist(err);
done();
});
});
});
});
});
describe('#addAccess 2-2', function() {
beforeEach(function(done) {
helpers.createAndJoinWallet(2, 2, function(s, w) {
server = s;
wallet = w;
helpers.stubUtxos(server, wallet, 1, function() {
done();
});
});
});
it('should be able to re-gain access from xPrivKey', function(done) {
ws.addAccess(opts, function(err, res) {
should.not.exist(err);
server.getBalance(res.wallet.walletId, function(err, bal) { should.not.exist(err);
bal.totalAmount.should.equal(1e8);
getAuthServer(opts.copayerId, reqPrivKey, function(err, server2) {
server2.getBalance(res.wallet.walletId, function(err, bal2) {
should.not.exist(err);
bal2.totalAmount.should.equal(1e8);
done();
});
});
});
});
});
it('TX proposals should include info to be verified', function(done) {
ws.addAccess(opts, function(err, res) {
should.not.exist(err);
getAuthServer(opts.copayerId, reqPrivKey, function(err, server2) {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, null, reqPrivKey);
server2.createTx(txOpts, function(err, tx) {
should.not.exist(err);
server2.getPendingTxs({}, function(err, txs) {
should.not.exist(err);
should.exist(txs[0].proposalSignaturePubKey);
should.exist(txs[0].proposalSignaturePubKeySig);
done();
});
});
});
});
});
});
});
describe('#getBalance', function() {
var server, wallet;
beforeEach(function(done) {
@ -4141,7 +4265,7 @@ describe('Wallet service', function() {
});
});
});
describe('Legacy', function() {
describe('Fees', function() {
var server, wallet;
@ -4308,7 +4432,6 @@ describe('Wallet service', function() {
});
});
});
});
});
});