Merge pull request #311 from matiu/feat/multiple-req-keys
Feat/multiple req keys
This commit is contained in:
commit
4f2f9441b9
|
@ -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',
|
||||||
|
|
|
@ -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 = {};
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
236
lib/server.js
236
lib/server.js
|
@ -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';
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue