From 879a352b3e49df89fd53928fc7c19ca90aad16dc Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 23 Feb 2015 00:43:39 -0300 Subject: [PATCH] add public key ring to txprposals --- bit-wallet/bit-txproposals | 11 ++- lib/client/api.js | 80 ++++++++++++++------- lib/client/verifier.js | 2 + test/integration/clientApi.js | 128 ++++++++++++++++++++++------------ 4 files changed, 150 insertions(+), 71 deletions(-) diff --git a/bit-wallet/bit-txproposals b/bit-wallet/bit-txproposals index bb64bc4..7dd298e 100755 --- a/bit-wallet/bit-txproposals +++ b/bit-wallet/bit-txproposals @@ -23,8 +23,15 @@ function end(err, txps, rawtxps) { } utils.renderTxProposals(txps); if (program.output) { - fs.writeFileSync(program.output, JSON.stringify(rawtxps)); - console.log(' * Proposals Saved to: %s\n', program.output); + + client.getEncryptedPublicKeyRing(function (err, pkr) { + var txData = { + pkr: pkr, + txps: txps, + }; + fs.writeFileSync(program.output, JSON.stringify(txData)); + console.log(' * Proposals Saved to: %s\n', program.output); + }); } }; diff --git a/lib/client/api.js b/lib/client/api.js index b30a75b..77c4a17 100644 --- a/lib/client/api.js +++ b/lib/client/api.js @@ -83,8 +83,7 @@ function API(opts) { } }; - -API.prototype._tryToComplete = function(data, cb) { +API.prototype._tryToCompleteFromServer = function(data, cb) { var self = this; var url = '/v1/wallets/'; @@ -112,6 +111,24 @@ API.prototype._tryToComplete = function(data, cb) { +API.prototype._tryToComplete = function(opts, data, cb) { + if (opts.pkr) { + var pkr = _decryptMessage(opts.pkr, data.sharedEncryptingKey); + + if (!pkr) + return cb('Could not complete wallet'); + + data.publicKeyRing = JSON.parse(pkr); + this.storage.save(data, function(err) { + return cb(err, data); + }); + } else { + this._tryToCompleteFromServer(data,cb); + } +}; + + + API.prototype._load = function(cb) { var self = this; @@ -124,7 +141,12 @@ API.prototype._load = function(cb) { }; -API.prototype._loadAndCheck = function(cb) { +/** + * _loadAndCheck + * + * @param opts.pkr + */ +API.prototype._loadAndCheck = function(opts, cb) { var self = this; this._load(function(err, data) { @@ -133,7 +155,7 @@ API.prototype._loadAndCheck = function(cb) { var pkrComplete = data.publicKeyRing && data.m && data.publicKeyRing.length === data.n; if (!pkrComplete) { - return self._tryToComplete(data, cb); + return self._tryToComplete(opts, data, cb); } } return cb(null, data); @@ -270,7 +292,7 @@ API.prototype.createWallet = function(walletName, copayerName, m, n, network, cb API.prototype.reCreateWallet = function(walletName, cb) { var self = this; - this._loadAndCheck(function(err, data) { + this._loadAndCheck({}, function(err, data) { if (err) return cb(err); var walletPrivKey = new Bitcore.PrivateKey(); @@ -351,7 +373,7 @@ API.prototype.sendTxProposal = function(opts, cb) { var self = this; - this._loadAndCheck(function(err, data) { + this._loadAndCheck({}, function(err, data) { if (err) return cb(err); if (!data.rwPrivKey) @@ -374,7 +396,7 @@ API.prototype.sendTxProposal = function(opts, cb) { API.prototype.createAddress = function(cb) { var self = this; - this._loadAndCheck(function(err, data) { + this._loadAndCheck({}, function(err, data) { if (err) return cb(err); var url = '/v1/addresses/'; @@ -396,7 +418,7 @@ API.prototype.createAddress = function(cb) { API.prototype.getMainAddresses = function(opts, cb) { var self = this; - this._loadAndCheck(function(err, data) { + this._loadAndCheck({}, function(err, data) { if (err) return cb(err); var url = '/v1/addresses/'; @@ -422,7 +444,7 @@ API.prototype.history = function(limit, cb) { API.prototype.getBalance = function(cb) { var self = this; - this._loadAndCheck(function(err, data) { + this._loadAndCheck({}, function(err, data) { if (err) return cb(err); var url = '/v1/balance/'; self._doGetRequest(url, data, cb); @@ -440,7 +462,7 @@ API.prototype.export = function(opts, cb) { opts = opts || {}; var access = opts.access || 'full'; - this._loadAndCheck(function(err, data) { + this._loadAndCheck({}, function(err, data) { if (err) return cb(err); var v = []; @@ -517,19 +539,12 @@ API.prototype.import = function(str, cb) { * */ -API.prototype.parseTxProposals = function(txps, cb) { +API.prototype.parseTxProposals = function(txData, cb) { var self = this; - this._load(function(err, data) { - if (err) return cb(err); - if (data.n > 1) { - var pkrComplete = data.publicKeyRing && data.m && data.publicKeyRing.length === data.n; - if (!pkrComplete) { - return cb('Wallet Incomplete'); - } - } - + this._loadAndCheck({pkr: txData.pkr},function(err, data) { + var txps = txData.txps; _processTxps(txps, data.sharedEncryptingKey); var fake = _.any(txps, function(txp) { @@ -555,7 +570,7 @@ API.prototype.parseTxProposals = function(txps, cb) { API.prototype.getTxProposals = function(opts, cb) { var self = this; - this._loadAndCheck(function(err, data) { + this._loadAndCheck({}, function(err, data) { if (err) return cb(err); var url = '/v1/txproposals/'; self._doGetRequest(url, data, function(err, txps) { @@ -619,7 +634,7 @@ API.prototype.getSignatures = function(txp, cb) { $.checkArgument(txp.creatorId); var self = this; - this._loadAndCheck(function(err, data) { + this._loadAndCheck({}, function(err, data) { if (err) return cb(err); if (!Verifier.checkTxProposal(data, txp)) { @@ -630,12 +645,25 @@ API.prototype.getSignatures = function(txp, cb) { }); }; +API.prototype.getEncryptedPublicKeyRing = function(cb) { + var self = this; + + this._loadAndCheck({}, function(err, data) { + if (err) return cb(err); + + var pkr = JSON.stringify(data.publicKeyRing); + return cb(null, _encryptMessage(pkr, data.sharedEncryptingKey)); + }); +}; + + + API.prototype.signTxProposal = function(txp, cb) { $.checkArgument(txp.creatorId); var self = this; - this._loadAndCheck(function(err, data) { + this._loadAndCheck({}, function(err, data) { if (err) return cb(err); if (!Verifier.checkTxProposal(data, txp)) { @@ -658,7 +686,7 @@ API.prototype.rejectTxProposal = function(txp, reason, cb) { var self = this; - this._loadAndCheck( + this._loadAndCheck({}, function(err, data) { if (err) return cb(err); @@ -673,7 +701,7 @@ API.prototype.rejectTxProposal = function(txp, reason, cb) { API.prototype.broadcastTxProposal = function(txp, cb) { var self = this; - this._loadAndCheck( + this._loadAndCheck({}, function(err, data) { if (err) return cb(err); @@ -686,7 +714,7 @@ API.prototype.broadcastTxProposal = function(txp, cb) { API.prototype.removeTxProposal = function(txp, cb) { var self = this; - this._loadAndCheck( + this._loadAndCheck({}, function(err, data) { if (err) return cb(err); var url = '/v1/txproposals/' + txp.id; diff --git a/lib/client/verifier.js b/lib/client/verifier.js index 1b2a324..f1a4897 100644 --- a/lib/client/verifier.js +++ b/lib/client/verifier.js @@ -59,7 +59,9 @@ Verifier.checkTxProposal = function(data, txp) { var creatorXPubKey = _.find(data.publicKeyRing, function(xPubKey) { if (WalletUtils.xPubToCopayerId(xPubKey) === txp.creatorId) return true; }); + if (!creatorXPubKey) return false; + var creatorSigningPubKey = (new Bitcore.HDPublicKey(creatorXPubKey)).derive('m/1/1').publicKey.toString(); var hash = WalletUtils.getProposalHash(txp.toAddress, txp.amount, txp.encryptedMessage || txp.message); diff --git a/test/integration/clientApi.js b/test/integration/clientApi.js index a1df273..63a7838 100644 --- a/test/integration/clientApi.js +++ b/test/integration/clientApi.js @@ -458,7 +458,9 @@ describe('client API ', function() { }, function(err, txs, rawTxps) { should.not.exist(err); - clients[0].parseTxProposals(rawTxps, function(err, txs2) { + clients[0].parseTxProposals({ + txps: rawTxps + }, function(err, txs2) { should.not.exist(err); txs[0].should.deep.equal(txs2[0]); done(); @@ -490,7 +492,9 @@ describe('client API ', function() { //Tamper rawTxps[0].amount++; - clients[0].parseTxProposals(rawTxps, function(err, txs2) { + clients[0].parseTxProposals({ + txps: rawTxps + }, function(err, txs2) { err.code.should.equal('SERVERCOMPROMISED'); done(); }); @@ -500,43 +504,81 @@ describe('client API ', function() { }); }); }); - it('should be able export signatures and sign later from a ro client', - function(done) { - helpers.createAndJoinWallet(clients, 1, 1, function(err, w) { + + it('should complete public key ring from file', function(done) { + helpers.createAndJoinWallet(clients, 1, 2, function(err, w) { should.not.exist(err); - clients[0].createAddress(function(err, x0) { + + clients[1].createAddress(function(err, x0) { should.not.exist(err); blockExplorerMock.setUtxo(x0, 1, 1); - blockExplorerMock.setUtxo(x0, 1, 2); var opts = { - amount: 150000000, + amount: 10000000, toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', message: 'hello 1-1', }; - clients[0].sendTxProposal(opts, function(err, txp) { + clients[1].sendTxProposal(opts, function(err, x) { should.not.exist(err); - clients[0].getSignatures(txp, function(err, signatures) { + clients[1].getTxProposals({ + getRawTxps: true + }, function(err, txs, rawTxps) { should.not.exist(err); - signatures.length.should.equal(txp.inputs.length); - signatures[0].length.should.above(62 * 2); - txp.signatures = signatures; - - // Make client RO - var data = JSON.parse(fsmock._get('client0')); - delete data.xPrivKey; - fsmock._set('client0', JSON.stringify(data)); - - clients[0].signTxProposal(txp, function(err, txp) { + clients[1].getEncryptedPublicKeyRing(function(err, pkr) { should.not.exist(err); - txp.status.should.equal('broadcasted'); - done(); + + // Will trigger _tryToComplete and use pkr + // then, needs pkr to verify the txps + clients[0].parseTxProposals({ + txps: rawTxps, + pkr: pkr, + }, function(err, txs2) { + should.not.exist(err); + done(); + }); }); }); }); }); }); }); + it('should be able export signatures and sign later from a ro client', + function(done) { + helpers.createAndJoinWallet(clients, 1, 1, function(err, w) { + should.not.exist(err); + clients[0].createAddress(function(err, x0) { + should.not.exist(err); + blockExplorerMock.setUtxo(x0, 1, 1); + blockExplorerMock.setUtxo(x0, 1, 2); + var opts = { + amount: 150000000, + toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', + message: 'hello 1-1', + }; + clients[0].sendTxProposal(opts, function(err, txp) { + should.not.exist(err); + clients[0].getSignatures(txp, function(err, signatures) { + should.not.exist(err); + signatures.length.should.equal(txp.inputs.length); + signatures[0].length.should.above(62 * 2); + + txp.signatures = signatures; + + // Make client RO + var data = JSON.parse(fsmock._get('client0')); + delete data.xPrivKey; + fsmock._set('client0', JSON.stringify(data)); + + clients[0].signTxProposal(txp, function(err, txp) { + should.not.exist(err); + txp.status.should.equal('broadcasted'); + done(); + }); + }); + }); + }); + }); + }); }); describe('Address Creation', function() { @@ -977,29 +1019,29 @@ describe('client API ', function() { }; clients[0].sendTxProposal(opts, function(err, x) { should.not.exist(err); - clients[0].getStatus( function(err, st) { - should.not.exist(err); - var x = st.pendingTxps[0]; - x.status.should.equal('pending'); - x.requiredRejections.should.equal(2); - x.requiredSignatures.should.equal(2); - var w = st.wallet; - w.copayers.length.should.equal(3); - w.status.should.equal('complete'); - var b = st.balance; - b.totalAmount.should.equal(1000000000); - b.lockedAmount.should.equal(1000000000); + clients[0].getStatus(function(err, st) { + should.not.exist(err); + var x = st.pendingTxps[0]; + x.status.should.equal('pending'); + x.requiredRejections.should.equal(2); + x.requiredSignatures.should.equal(2); + var w = st.wallet; + w.copayers.length.should.equal(3); + w.status.should.equal('complete'); + var b = st.balance; + b.totalAmount.should.equal(1000000000); + b.lockedAmount.should.equal(1000000000); - clients[0].signTxProposal(x, function(err, tx) { - should.not.exist(err, err); - tx.status.should.equal('pending'); - clients[1].signTxProposal(x, function(err, tx) { - should.not.exist(err); - tx.status.should.equal('broadcasted'); - tx.txid.should.equal((new Bitcore.Transaction(blockExplorerMock.lastBroadcasted)).id); - done(); - }); + clients[0].signTxProposal(x, function(err, tx) { + should.not.exist(err, err); + tx.status.should.equal('pending'); + clients[1].signTxProposal(x, function(err, tx) { + should.not.exist(err); + tx.status.should.equal('broadcasted'); + tx.txid.should.equal((new Bitcore.Transaction(blockExplorerMock.lastBroadcasted)).id); + done(); + }); }); }); });