|
|
@ -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'; |
|
|
|