id param to wallet create

This commit is contained in:
Matias Alejo Garcia 2015-03-31 17:28:01 -03:00
parent d7964a8239
commit 039396dfaa
6 changed files with 353 additions and 15 deletions

View File

@ -18,8 +18,8 @@ function Copayer() {
Copayer.create = function(opts) {
opts = opts || {};
$.checkArgument(opts.xPubKey, 'Missing extended public key');
$.checkArgument(opts.requestPubKey, 'Missing request public key');
$.checkArgument(opts.xPubKey, 'Missing copayer extended public key');
$.checkArgument(opts.requestPubKey, 'Missing copayer request public key');
opts.copayerIndex = opts.copayerIndex || 0;
@ -35,6 +35,7 @@ Copayer.create = function(opts) {
x.addressManager = AddressManager.create({
copayerIndex: opts.copayerIndex
});
x.isTemporaryRequestKey = opts.isTemporaryRequestKey || false;
return x;
};
@ -48,6 +49,8 @@ Copayer.fromObj = function(obj) {
x.xPubKey = obj.xPubKey;
x.requestPubKey = obj.requestPubKey;
x.signature = obj.signature;
x.isTemporaryRequestKey = obj.isTemporaryRequestKey || false;
x.addressManager = AddressManager.fromObj(obj.addressManager);
return x;

View File

@ -20,7 +20,7 @@ Wallet.create = function(opts) {
var x = new Wallet();
x.createdOn = Math.floor(Date.now() / 1000);
x.id = Uuid.v4();
x.id = opts.id || Uuid.v4();
x.name = opts.name;
x.m = opts.m;
x.n = opts.n;
@ -100,6 +100,23 @@ Wallet.prototype.addCopayer = function(copayer) {
});
};
Wallet.prototype.updateCopayerRequestKey = function(copayerId, requestPubKey) {
$.checkState(this.copayers.length == this.n);
var c = _.find(this.copayers, {
id: copayerId,
});
$.checkState(c)
.checkState(c.isTemporaryRequestKey);
c.requestPubKey = requestPubKey;
c.isTemporaryRequestKey = false;
this.publicKeyRing = _.map(this.copayers, function(copayer) {
return _.pick(copayer, ['xPubKey', 'requestPubKey']);
});
};
Wallet.prototype.getCopayer = function(copayerId) {
return _.find(this.copayers, {
id: copayerId

View File

@ -121,17 +121,35 @@ WalletService.prototype.createWallet = function(opts, cb) {
return cb(new ClientError('Invalid public key'));
};
var wallet = Wallet.create({
name: opts.name,
m: opts.m,
n: opts.n,
network: network,
pubKey: pubKey.toString(),
});
var newWallet;
async.series([
self.storage.storeWallet(wallet, function(err) {
log.debug('Wallet created', wallet.id, network);
return cb(err, wallet.id);
function(acb) {
if (!opts.id)
return acb();
self.storage.fetchWallet(opts.id, function(err, wallet) {
if (wallet) return acb(new ClientError('Wallet already exists'));
return acb(err);
});
},
function(acb) {
var wallet = Wallet.create({
name: opts.name,
m: opts.m,
n: opts.n,
network: network,
pubKey: pubKey.toString(),
id: opts.id,
});
self.storage.storeWallet(wallet, function(err) {
log.debug('Wallet created', wallet.id, network);
newWallet = wallet;
return acb(err);
});
}
], function(err) {
return cb(err, newWallet ? newWallet.id : null);
});
};
@ -151,6 +169,63 @@ 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 the 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'));
Utils.runLocked(self.walletId, cb, function(cb) {
self.storage.fetchWallet(self.walletId, function(err, wallet) {
if (err) return cb(err);
if (!wallet) return cb(new ClientError('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.name !== opts.name || !oldCopayerData.isTemporaryRequestKey)
return cb(new ClientError('CDATAMISMATCH', 'Copayer data mismatch'));
if (wallet.copayers.length != wallet.n)
return cb(new ClientError('WNOTFULL', 'Replace only works on full wallets'));
wallet.updateCopayerRequestKey(self.copayerId, opts.requestPubKey);
self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) {
if (err) return cb(err);
return cb(null, {
copayerId: self.copayerId,
wallet: wallet
});
});
});
});
};
/**
* Verifies a signature
* @param text
@ -206,6 +281,7 @@ WalletService.prototype._notify = function(type, data) {
* @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;
@ -218,6 +294,7 @@ WalletService.prototype.joinWallet = function(opts, cb) {
Utils.runLocked(opts.walletId, cb, function(cb) {
self.storage.fetchWallet(opts.walletId, function(err, wallet) {
if (err) return cb(err);
if (!wallet) return cb(new ClientError('Wallet not found'));
@ -239,6 +316,7 @@ WalletService.prototype.joinWallet = function(opts, cb) {
xPubKey: opts.xPubKey,
requestPubKey: opts.requestPubKey,
signature: opts.copayerSignature,
isTemporaryRequestKey: !!opts.isTemporaryRequestKey,
});
self.storage.fetchCopayerLookup(copayer.id, function(err, res) {

View File

@ -2,7 +2,7 @@
"name": "bitcore-wallet-service",
"description": "A service for Mutisig HD Bitcoin Wallets",
"author": "BitPay Inc",
"version": "0.0.17",
"version": "0.0.18",
"keywords": [
"bitcoin",
"copay",

View File

@ -299,6 +299,43 @@ describe('Wallet service', function() {
});
});
it('should create wallet with given id', function(done) {
var opts = {
name: 'my wallet',
m: 2,
n: 3,
pubKey: TestData.keyPair.pub,
id: '1234',
};
server.createWallet(opts, function(err, walletId) {
should.not.exist(err);
server.storage.fetchWallet('1234', function(err, wallet) {
should.not.exist(err);
wallet.id.should.equal(walletId);
wallet.name.should.equal('my wallet');
done();
});
});
});
it('should fail to create wallets with same id', function(done) {
var opts = {
name: 'my wallet',
m: 2,
n: 3,
pubKey: TestData.keyPair.pub,
id: '1234',
};
server.createWallet(opts, function(err, walletId) {
server.createWallet(opts, function(err, walletId) {
err.message.should.contain('Wallet already exists');
done();
});
});
});
it('should fail to create wallet with no name', function(done) {
var opts = {
name: '',
@ -2595,6 +2632,207 @@ describe('Wallet service', function() {
});
});
it.skip('should abort scan if there is an error checking address activity', function(done) {});
});
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('WNOTFULL');
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('CDATAMISMATCH');
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('CDATAMISMATCH');
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();
});
});
});
});
});
});
});
});
});

View File

@ -17,7 +17,9 @@ var copayers = [{
xPrivKey_1H: 'xprv9uZiJ2ZYFNQ1WeKrKfTxrbUnXfNnCtpbdHVuUMSVGB57nhbmjqWbJBQbgGNa2jSbrgQX9NFHXtAoQ3y1hsEgwjKHxUU52ZYTC6m5kjXoNpJ',
xPubKey_1H: 'xpub68Z4hY6S5jxJj8QKRgzyDjRX5hDGcMYSzWRWGjr6pWc6fVvvHNpqqyj5XXbMcfW737beYZcd7EQau5HS74Ws6Ctx9XwFn2wHQjSUKLbfdFk',
privKey_1H_0: 'e334a0ce3d573bd99fc4cd7e2065e39dc7851cb61da7f381431c253c3e230828',
pubKey_1H_0: '02db35c363e2c904bba3cd0eadb6b8d68fc1d8e6160d632b8fb91a471b80e40c86'
pubKey_1H_0: '02db35c363e2c904bba3cd0eadb6b8d68fc1d8e6160d632b8fb91a471b80e40c86',
privKey_1_0: '89833f39998ff14f2cbb461365ccba30583abfd2a7845e0bb61849275802005d',
pubKey_1_0: '022ef899f914c9b90a5544923a3aa6de5ddcd6c9d0b603b63ab9fd0c2cdc5d3772',
}, {
id: 'cf652642ebf997460d642231f9929c39257bcd4c42e9ecb312c226961e21c882',
xPrivKey: 'xprv9s21ZrQH143K2XtR13LpXVAPaGTFsPY5dJfNvNTShfhdVW16vdDaYGjLwHmyKLCtv3F6h9NpKsAGusmKCZNQvKcYqYf9MjCLwJwsoEyAuge',