Merge pull request #311 from matiu/feat/multiple-req-keys

Feat/multiple req keys
This commit is contained in:
Ivan Socolsky 2015-08-24 18:16:20 -03:00
commit 4f2f9441b9
10 changed files with 359 additions and 357 deletions

View File

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

View File

@ -140,6 +140,15 @@ ExpressApp.prototype.start = function(opts, cb) {
}); });
}); });
router.put('/v1/copayers/:id/', function(req, res) {
req.body.copayerId = req.params['id'];
var server = getServer(req, res);
server.addAccess(req.body, function(err, result) {
if (err) return returnError(err, res, req);
res.json(result);
});
});
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.walletId = req.params['id'];
var server = getServer(req, res); var server = getServer(req, res);
@ -151,16 +160,6 @@ ExpressApp.prototype.start = function(opts, cb) {
}); });
router.put('/v1/copayers', function(req, res) {
getServerWithAuth(req, res, function(server) {
server.replaceTemporaryRequestKey(req.body, function(err, result) {
if (err) return returnError(err, res, req);
res.json(result);
});
});
});
router.get('/v1/wallets/', function(req, res) { router.get('/v1/wallets/', function(req, res) {
getServerWithAuth(req, res, function(server) { getServerWithAuth(req, res, function(server) {
var result = {}; var result = {};

View File

@ -13,13 +13,18 @@ var Bitcore = WalletUtils.Bitcore;
var HDPublicKey = Bitcore.HDPublicKey; var HDPublicKey = Bitcore.HDPublicKey;
function Copayer() { function Copayer() {
this.version = '1.0.0'; this.version = '2';
};
Copayer.getVersion = function() {
return parseInt(this.version);
}; };
Copayer.create = function(opts) { Copayer.create = function(opts) {
opts = opts || {}; opts = opts || {};
$.checkArgument(opts.xPubKey, 'Missing copayer extended public key'); $.checkArgument(opts.xPubKey, 'Missing copayer extended public key')
$.checkArgument(opts.requestPubKey, 'Missing copayer request public key'); .checkArgument(opts.requestPubKey, 'Missing copayer request public key')
.checkArgument(opts.signature, 'Missing copayer request public key signature');
opts.copayerIndex = opts.copayerIndex || 0; opts.copayerIndex = opts.copayerIndex || 0;
@ -30,12 +35,18 @@ Copayer.create = function(opts) {
x.id = WalletUtils.xPubToCopayerId(x.xPubKey); x.id = WalletUtils.xPubToCopayerId(x.xPubKey);
x.name = opts.name; x.name = opts.name;
x.signature = opts.signature; // So third parties can check independently
x.requestPubKey = opts.requestPubKey; x.requestPubKey = opts.requestPubKey;
x.signature = opts.signature;
x.requestPubKeys = [{
key: opts.requestPubKey,
signature: opts.signature,
}];
x.addressManager = AddressManager.create({ x.addressManager = AddressManager.create({
copayerIndex: opts.copayerIndex copayerIndex: opts.copayerIndex
}); });
x.isTemporaryRequestKey = opts.isTemporaryRequestKey || false;
return x; return x;
}; };
@ -47,12 +58,21 @@ Copayer.fromObj = function(obj) {
x.id = obj.id; x.id = obj.id;
x.name = obj.name; x.name = obj.name;
x.xPubKey = obj.xPubKey; x.xPubKey = obj.xPubKey;
x.requestPubKey = obj.requestPubKey; x.requestPubKey = obj.requestPubKey;
x.signature = obj.signature; x.signature = obj.signature;
x.isTemporaryRequestKey = obj.isTemporaryRequestKey;
if (this.getVersion() == 1) {
x.requestPubKeys = [{
key: x.requestPubKey,
signature: x.signature,
}];
x.version = 2;
} else {
x.requestPubKeys = obj.requestPubKeys;
}
x.addressManager = AddressManager.fromObj(obj.addressManager); x.addressManager = AddressManager.fromObj(obj.addressManager);
return x; return x;
}; };

View File

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

View File

@ -94,7 +94,7 @@ Wallet.prototype.isShared = function() {
Wallet.prototype._updatePublicKeyRing = function() { Wallet.prototype._updatePublicKeyRing = function() {
this.publicKeyRing = _.map(this.copayers, function(copayer) { this.publicKeyRing = _.map(this.copayers, function(copayer) {
return _.pick(copayer, ['xPubKey', 'requestPubKey', 'isTemporaryRequestKey']); return _.pick(copayer, ['xPubKey', 'requestPubKey']);
}); });
}; };
@ -107,20 +107,19 @@ Wallet.prototype.addCopayer = function(copayer) {
this._updatePublicKeyRing(); this._updatePublicKeyRing();
}; };
Wallet.prototype.updateCopayerRequestKey = function(copayerId, requestPubKey, signature) { Wallet.prototype.addCopayerRequestKey = function(copayerId, requestPubKey, signature, restrictions, name) {
$.checkState(this.copayers.length == this.n); $.checkState(this.copayers.length == this.n);
var c = _.find(this.copayers, { var c = this.getCopayer(copayerId);
id: copayerId,
//new ones go first
c.requestPubKeys.unshift({
key: requestPubKey.toString(),
signature: signature,
selfSigned: true,
restrictions: restrictions || {},
name: name || null,
}); });
$.checkState(c)
.checkState(c.isTemporaryRequestKey);
c.requestPubKey = requestPubKey;
c.isTemporaryRequestKey = false;
c.signature = signature;
this._updatePublicKeyRing();
}; };
Wallet.prototype.getCopayer = function(copayerId) { Wallet.prototype.getCopayer = function(copayerId) {

View File

@ -33,7 +33,7 @@ var blockchainExplorer;
var blockchainExplorerOpts; var blockchainExplorerOpts;
var messageBroker; var messageBroker;
var MAX_KEYS = 100;
/** /**
* Creates an instance of the Bitcore Wallet Service. * Creates an instance of the Bitcore Wallet Service.
@ -165,7 +165,7 @@ WalletService.getInstance = function(opts) {
* @param {Object} opts * @param {Object} opts
* @param {string} opts.copayerId - The copayer id making the request. * @param {string} opts.copayerId - The copayer id making the request.
* @param {string} opts.message - The contents of the request to be signed. * @param {string} opts.message - The contents of the request to be signed.
* @param {string} opts.signature - Signature of message to be verified using the copayer's requestPubKey * @param {string} opts.signature - Signature of message to be verified using one of the copayer's requestPubKeys
* @param {string} opts.clientVersion - A string that identifies the client issuing the request * @param {string} opts.clientVersion - A string that identifies the client issuing the request
*/ */
WalletService.getInstanceWithAuth = function(opts, cb) { WalletService.getInstanceWithAuth = function(opts, cb) {
@ -177,7 +177,7 @@ WalletService.getInstanceWithAuth = function(opts, cb) {
if (err) return cb(err); if (err) return cb(err);
if (!copayer) return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Copayer not found')); if (!copayer) return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Copayer not found'));
var isValid = server._verifySignature(opts.message, opts.signature, copayer.requestPubKey); var isValid = !!server._getSigningKey(opts.message, opts.signature, copayer.requestPubKeys);
if (!isValid) if (!isValid)
return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Invalid signature')); return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Invalid signature'));
@ -272,78 +272,28 @@ WalletService.prototype.getWallet = function(opts, cb) {
}); });
}; };
/*
/**
* Replace temporary request key
* @param {Object} opts
* @param {string} opts.name - The copayer name.
* @param {string} opts.xPubKey - Extended Public Key for 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.
*/
WalletService.prototype.replaceTemporaryRequestKey = function(opts, cb) {
var self = this;
if (!Utils.checkRequired(opts, ['name', 'xPubKey', 'requestPubKey', 'copayerSignature']))
return cb(new ClientError('Required argument missing'));
if (_.isEmpty(opts.name))
return cb(new ClientError('Invalid copayer name'));
if (opts.isTemporaryRequestKey)
return cb(new ClientError('Bad arguments'));
self._runLocked(cb, function(cb) {
self.storage.fetchWallet(self.walletId, function(err, wallet) {
if (err) return cb(err);
if (!wallet) return cb(Errors.WALLET_NOT_FOUND);
var hash = WalletUtils.getCopayerHash(opts.name, opts.xPubKey, opts.requestPubKey);
if (!self._verifySignature(hash, opts.copayerSignature, wallet.pubKey)) {
return cb(new ClientError());
}
var oldCopayerData = _.find(wallet.copayers, {
id: self.copayerId
});
$.checkState(oldCopayerData);
if (oldCopayerData.xPubKey !== opts.xPubKey || !oldCopayerData.isTemporaryRequestKey)
return cb(Errors.COPAYER_DATA_MISMATCH);
if (wallet.copayers.length != wallet.n)
return cb(Errors.WALLET_NOT_COMPLETE);
wallet.updateCopayerRequestKey(self.copayerId, opts.requestPubKey, opts.copayerSignature);
self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) {
if (err) return cb(err);
self._notify('CopayerUpdated', {
walletId: opts.walletId,
copayerId: self.copayerId,
copayerName: opts.name,
}, function() {
return cb(null, {
copayerId: self.copayerId,
wallet: wallet
});
});
});
});
});
};
/**
* Verifies a signature * Verifies a signature
* @param text * @param text
* @param signature * @param signature
* @param pubKey * @param pubKeys
*/ */
WalletService.prototype._verifySignature = function(text, signature, pubKey) { WalletService.prototype._verifySignature = function(text, signature, pubkey) {
return WalletUtils.verifyMessage(text, signature, pubKey); return WalletUtils.verifyMessage(text, signature, pubkey);
};
/*
* Verifies signature againt a collection of pubkeys
* @param text
* @param signature
* @param pubKeys
*/
WalletService.prototype._getSigningKey = function(text, signature, pubKeys) {
var self = this;
return _.find(pubKeys, function(item) {
return self._verifySignature(text, signature, item.key);
});
}; };
/** /**
@ -387,41 +337,9 @@ WalletService.prototype._notify = function(type, data, opts, cb) {
}; };
/** WalletService.prototype._addCopayerToWallet = function(wallet, opts, cb) {
* Joins a wallet in creation.
* @param {Object} opts
* @param {string} opts.walletId - The wallet id.
* @param {string} opts.name - The copayer name.
* @param {string} opts.xPubKey - Extended Public Key for 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 the that the copayer joining knows the wallet secret.
* @param {string} opts.isTemporaryRequestKey - requestPubKey will be marked as 'temporary' (only used for Copay migration)
*/
WalletService.prototype.joinWallet = function(opts, cb) {
var self = this; var self = this;
if (!Utils.checkRequired(opts, ['walletId', 'name', 'xPubKey', 'requestPubKey', 'copayerSignature']))
return cb(new ClientError('Required argument missing'));
if (_.isEmpty(opts.name))
return cb(new ClientError('Invalid copayer name'));
self.walletId = opts.walletId;
self._runLocked(cb, function(cb) {
self.storage.fetchWallet(opts.walletId, function(err, wallet) {
if (err) return cb(err);
if (!wallet) return cb(Errors.WALLET_NOT_FOUND);
var hash = WalletUtils.getCopayerHash(opts.name, opts.xPubKey, opts.requestPubKey);
if (!self._verifySignature(hash, opts.copayerSignature, wallet.pubKey)) {
return cb(new ClientError());
}
if (_.find(wallet.copayers, {
xPubKey: opts.xPubKey
})) return cb(Errors.COPAYER_IN_WALLET);
if (wallet.copayers.length == wallet.n) return cb(Errors.WALLET_FULL); if (wallet.copayers.length == wallet.n) return cb(Errors.WALLET_FULL);
var copayer = Model.Copayer.create({ var copayer = Model.Copayer.create({
@ -430,7 +348,6 @@ WalletService.prototype.joinWallet = function(opts, cb) {
xPubKey: opts.xPubKey, xPubKey: opts.xPubKey,
requestPubKey: opts.requestPubKey, requestPubKey: opts.requestPubKey,
signature: opts.copayerSignature, signature: opts.copayerSignature,
isTemporaryRequestKey: !!opts.isTemporaryRequestKey,
}); });
self.storage.fetchCopayerLookup(copayer.id, function(err, res) { self.storage.fetchCopayerLookup(copayer.id, function(err, res) {
@ -469,6 +386,98 @@ WalletService.prototype.joinWallet = function(opts, cb) {
}); });
}); });
}); });
};
WalletService.prototype._addKeyToCopayer = function(wallet, copayer, opts, cb) {
var self = this;
wallet.addCopayerRequestKey(copayer.copayerId, opts.requestPubKey, opts.signature, opts.restrictions, opts.name);
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
* @param {string} opts.name (name for the new access)
*/
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.verifyRequestPubKey(opts.requestPubKey, opts.signature, xPubKey)) {
return cb(Errors.NOT_AUTHORIZED);
}
if (copayer.requestPubKeys.length > MAX_KEYS)
return cb(Errors.TOO_MANY_KEYS);
self._addKeyToCopayer(wallet, copayer, opts, cb);
});
});
};
/**
* Joins a wallet in creation.
* @param {Object} opts
* @param {string} opts.walletId - The wallet id.
* @param {string} opts.name - The copayer name.
* @param {string} opts.xPubKey - Extended Public Key for 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 the that the copayer joining knows the wallet secret.
*/
WalletService.prototype.joinWallet = function(opts, cb) {
var self = this;
if (!Utils.checkRequired(opts, ['walletId', 'name', 'xPubKey', 'requestPubKey', 'copayerSignature']))
return cb(new ClientError('Required argument missing'));
if (_.isEmpty(opts.name))
return cb(new ClientError('Invalid copayer name'));
self.walletId = opts.walletId;
self._runLocked(cb, function(cb) {
self.storage.fetchWallet(opts.walletId, function(err, wallet) {
if (err) return cb(err);
if (!wallet) return cb(Errors.WALLET_NOT_FOUND);
var hash = WalletUtils.getCopayerHash(opts.name, opts.xPubKey, opts.requestPubKey);
if (!self._verifySignature(hash, opts.copayerSignature, wallet.pubKey)) {
return cb(new ClientError());
}
if (_.find(wallet.copayers, {
xPubKey: opts.xPubKey
})) return cb(Errors.COPAYER_IN_WALLET);
self._addCopayerToWallet(wallet, opts, cb);
}); });
}); });
}; };
@ -611,7 +620,7 @@ WalletService.prototype.verifyMessageSignature = function(opts, cb) {
var copayer = wallet.getCopayer(self.copayerId); var copayer = wallet.getCopayer(self.copayerId);
var isValid = self._verifySignature(opts.message, opts.signature, copayer.requestPubKey); var isValid = !!self._getSigningKey(opts.message, opts.signature, copayer.requestPubKeys);
return cb(null, isValid); return cb(null, isValid);
}); });
}; };
@ -759,7 +768,6 @@ WalletService.prototype.getBalance = function(opts, cb) {
self.getUtxos({}, function(err, utxos) { self.getUtxos({}, function(err, utxos) {
if (err) return cb(err); if (err) return cb(err);
var balance = self._totalizeUtxos(utxos); var balance = self._totalizeUtxos(utxos);
// Compute balance by address // Compute balance by address
@ -1032,7 +1040,8 @@ WalletService.prototype.createTx = function(opts, cb) {
hash = WalletUtils.getProposalHash(header) hash = WalletUtils.getProposalHash(header)
} }
if (!self._verifySignature(hash, opts.proposalSignature, copayer.requestPubKey)) var signingKey = self._getSigningKey(hash, opts.proposalSignature, copayer.requestPubKeys)
if (!signingKey)
return cb(new ClientError('Invalid proposal signature')); return cb(new ClientError('Invalid proposal signature'));
self._canCreateTx(self.copayerId, function(err, canCreate) { self._canCreateTx(self.copayerId, function(err, canCreate) {
@ -1066,7 +1075,7 @@ WalletService.prototype.createTx = function(opts, cb) {
valid: false valid: false
})) return; })) return;
var txp = Model.TxProposal.create({ var txOpts = {
type: type, type: type,
walletId: self.walletId, walletId: self.walletId,
creatorId: self.copayerId, creatorId: self.copayerId,
@ -1082,7 +1091,14 @@ 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,
}); };
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)) { if (!self.clientVersion || /^bw.-0\.0\./.test(self.clientVersion)) {
txp.version = '1.0.1'; txp.version = '1.0.1';

View File

@ -110,10 +110,11 @@ Storage.prototype.storeWalletAndUpdateCopayersLookup = function(wallet, cb) {
var self = this; var self = this;
var copayerLookups = _.map(wallet.copayers, function(copayer) { var copayerLookups = _.map(wallet.copayers, function(copayer) {
$.checkState(copayer.requestPubKeys);
return { return {
copayerId: copayer.id, copayerId: copayer.id,
walletId: wallet.id, walletId: wallet.id,
requestPubKey: copayer.requestPubKey, requestPubKeys: copayer.requestPubKeys,
}; };
}); });
@ -133,11 +134,20 @@ Storage.prototype.storeWalletAndUpdateCopayersLookup = function(wallet, cb) {
}; };
Storage.prototype.fetchCopayerLookup = function(copayerId, cb) { Storage.prototype.fetchCopayerLookup = function(copayerId, cb) {
this.db.collection(collections.COPAYERS_LOOKUP).findOne({ this.db.collection(collections.COPAYERS_LOOKUP).findOne({
copayerId: copayerId copayerId: copayerId
}, function(err, result) { }, function(err, result) {
if (err) return cb(err); if (err) return cb(err);
if (!result) return cb(); if (!result) return cb();
if (!result.requestPubKeys) {
result.requestPubKeys = [{
key: result.requestPubKey,
signature: result.signature,
}];
}
return cb(null, result); return cb(null, result);
}); });
}; };

View File

@ -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.1.5", "version": "0.1.6",
"keywords": [ "keywords": [
"bitcoin", "bitcoin",
"copay", "copay",

View File

@ -26,6 +26,7 @@ var WalletService = require('../../lib/server');
var EmailService = require('../../lib/emailservice'); var EmailService = require('../../lib/emailservice');
var TestData = require('../testdata'); var TestData = require('../testdata');
var CLIENT_VERSION = 'bwc-0.1.1';
var helpers = {}; var helpers = {};
helpers.getAuthServer = function(copayerId, cb) { helpers.getAuthServer = function(copayerId, cb) {
@ -112,7 +113,6 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
}); });
}, function(err) { }, function(err) {
if (err) return new Error('Could not generate wallet'); if (err) return new Error('Could not generate wallet');
helpers.getAuthServer(copayerIds[0], function(s) { helpers.getAuthServer(copayerIds[0], function(s) {
s.getWallet({}, function(err, w) { s.getWallet({}, function(err, w) {
cb(s, w); cb(s, w);
@ -1404,6 +1404,155 @@ describe('Wallet service', function() {
}); });
}); });
describe('Multiple request Pub Keys', function() {
var server, wallet;
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('should be able to re-gain access from xPrivKey', function(done) {
ws.addAccess(opts, function(err, res) {
should.not.exist(err);
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() { describe('#getBalance', function() {
var server, wallet; var server, wallet;
beforeEach(function(done) { beforeEach(function(done) {
@ -4116,205 +4265,6 @@ describe('Wallet service', function() {
}); });
}); });
describe('#replaceTemporaryRequestKey', function() {
var server, walletId;
beforeEach(function(done) {
server = new WalletService();
var walletOpts = {
name: 'my wallet',
m: 2,
n: 2,
pubKey: TestData.keyPair.pub,
};
server.createWallet(walletOpts, function(err, wId) {
should.not.exist(err);
should.exist.walletId;
walletId = wId;
done();
});
});
it('should join existing wallet with temporaryRequestKey', function(done) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
});
copayerOpts.isTemporaryRequestKey = true;
server.joinWallet(copayerOpts, function(err, result) {
should.not.exist(err);
var copayerId = result.copayerId;
helpers.getAuthServer(copayerId, function(server) {
server.getWallet({}, function(err, wallet) {
wallet.id.should.equal(walletId);
var copayer = wallet.copayers[0];
copayer.isTemporaryRequestKey.should.equal(true);
done();
});
});
});
});
it('should fail to replace a temporaryRequestKey on a not-complete wallet', function(done) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1_0,
});
copayerOpts.isTemporaryRequestKey = true;
server.joinWallet(copayerOpts, function(err, result) {
should.not.exist(err);
var copayerId = result.copayerId;
helpers.getAuthServer(copayerId, function(server) {
server.getWallet({}, function(err, wallet) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
});
copayerOpts.isTemporaryRequestKey = false;
server.replaceTemporaryRequestKey(copayerOpts, function(err, wallet) {
err.code.should.equal('WALLET_NOT_COMPLETE');
done();
});
});
});
});
});
it('should fail to replace a temporaryRequestKey is Copayer is not in wallet', function(done) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1_0,
});
copayerOpts.isTemporaryRequestKey = true;
server.joinWallet(copayerOpts, function(err, result) {
should.not.exist(err);
var copayerId = result.copayerId;
helpers.getAuthServer(copayerId, function(server) {
server.getWallet({}, function(err, wallet) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[1].xPubKey_45H,
requestPubKey: TestData.copayers[1].pubKey_1H_0,
});
copayerOpts.isTemporaryRequestKey = false;
server.replaceTemporaryRequestKey(copayerOpts, function(err, wallet) {
err.code.should.equal('COPAYER_DATA_MISMATCH');
done();
});
});
});
});
});
it('should fail replace a temporaryRequestKey with invalid copayer', function(done) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1_0,
});
copayerOpts.isTemporaryRequestKey = true;
server.joinWallet(copayerOpts, function(err, result) {
should.not.exist(err);
var copayerOpts2 = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[1].xPubKey_45H,
requestPubKey: TestData.copayers[1].pubKey_1H_0,
});
copayerOpts2.isTemporaryRequestKey = false;
server.joinWallet(copayerOpts2, function(err, result) {
should.not.exist(err);
var copayerId = result.copayerId;
helpers.getAuthServer(copayerId, function(server) {
server.getWallet({}, function(err, wallet) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[1].xPubKey_45H,
requestPubKey: TestData.copayers[1].pubKey_1H_0,
});
copayerOpts.isTemporaryRequestKey = false;
server.replaceTemporaryRequestKey(copayerOpts, function(err, wallet) {
err.code.should.equal('COPAYER_DATA_MISMATCH');
done();
});
});
});
});
});
});
it('should replace a temporaryRequestKey', function(done) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1_0,
});
copayerOpts.isTemporaryRequestKey = true;
server.joinWallet(copayerOpts, function(err, result) {
should.not.exist(err);
var copayerId = result.copayerId;
var copayerOpts2 = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[1].xPubKey_45H,
requestPubKey: TestData.copayers[1].pubKey_1H_0,
});
copayerOpts2.isTemporaryRequestKey = false;
server.joinWallet(copayerOpts2, function(err, result) {
should.not.exist(err);
var copayerId2 = result.copayerId;
helpers.getAuthServer(copayerId, function(server) {
server.getWallet({}, function(err, wallet) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
});
copayerOpts.isTemporaryRequestKey = false;
server.replaceTemporaryRequestKey(copayerOpts, function(err, wallet) {
should.not.exist(err);
server.getWallet({}, function(err, wallet) {
wallet.copayers[0].isTemporaryRequestKey.should.equal(false);
wallet.copayers[1].isTemporaryRequestKey.should.equal(false);
done();
});
});
});
});
});
});
});
});
describe('Legacy', function() { describe('Legacy', function() {
describe('Fees', function() { describe('Fees', function() {
var server, wallet; var server, wallet;
@ -4481,7 +4431,6 @@ describe('Wallet service', function() {
}); });
}); });
}); });
}); });
}); });
}); });

View File

@ -90,6 +90,7 @@ describe('Storage', function() {
name: 'copayer ' + i, name: 'copayer ' + i,
xPubKey: 'xPubKey ' + i, xPubKey: 'xPubKey ' + i,
requestPubKey: 'requestPubKey ' + i, requestPubKey: 'requestPubKey ' + i,
signature: 'xxx',
}); });
wallet.addCopayer(copayer); wallet.addCopayer(copayer);
}); });
@ -101,7 +102,8 @@ describe('Storage', function() {
should.not.exist(err); should.not.exist(err);
should.exist(lookup); should.exist(lookup);
lookup.walletId.should.equal('123'); lookup.walletId.should.equal('123');
lookup.requestPubKey.should.equal('requestPubKey 1'); lookup.requestPubKeys[0].key.should.equal('requestPubKey 1');
lookup.requestPubKeys[0].signature.should.equal('xxx');
done(); done();
}) })
}); });
@ -130,6 +132,7 @@ describe('Storage', function() {
name: 'copayer ' + i, name: 'copayer ' + i,
xPubKey: 'xPubKey ' + i, xPubKey: 'xPubKey ' + i,
requestPubKey: 'requestPubKey ' + i, requestPubKey: 'requestPubKey ' + i,
signature: 'signarture ' + i,
}); });
wallet.addCopayer(copayer); wallet.addCopayer(copayer);
}); });