adds addAccess call and tests
This commit is contained in:
parent
edc3bc6713
commit
dac564f290
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
176
lib/server.js
176
lib/server.js
|
@ -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';
|
||||
|
|
|
@ -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() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue