From 7f0e9cd03dadf4a1011813219d9a1785bbf72de2 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 20 Aug 2014 14:45:59 -0400 Subject: [PATCH] implements skipping fields in backup imports --- js/controllers/backup.js | 1 - js/controllers/import.js | 6 +- js/models/core/PublicKeyRing.js | 2 +- js/models/core/Wallet.js | 230 ++++++++++++++++++-------------- js/models/core/WalletFactory.js | 22 ++- test/test.WalletFactory.js | 13 ++ views/import.html | 8 ++ 7 files changed, 169 insertions(+), 113 deletions(-) diff --git a/js/controllers/backup.js b/js/controllers/backup.js index 8582b3bfe..9b0e23656 100644 --- a/js/controllers/backup.js +++ b/js/controllers/backup.js @@ -21,5 +21,4 @@ angular.module('copayApp.controllers').controller('BackupController', controllerUtils.logout(); }); }; - }); diff --git a/js/controllers/import.js b/js/controllers/import.js index f978884cd..c64bea1c6 100644 --- a/js/controllers/import.js +++ b/js/controllers/import.js @@ -7,6 +7,10 @@ angular.module('copayApp.controllers').controller('ImportController', $scope.title = 'Import a backup'; $scope.importStatus = 'Importing wallet - Reading backup...'; + var s = ($location.search()).skip; + if (s) { + $scope.skipFields = s.split(','); + } var reader = new FileReader(); @@ -22,7 +26,7 @@ angular.module('copayApp.controllers').controller('ImportController', // try to import encrypted wallet with passphrase try { - w = walletFactory.import(encryptedObj, passphrase); + w = walletFactory.import(encryptedObj, passphrase, $scope.skipFields); } catch (e) { errMsg = e.message; } diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index 898739283..3a71dd0e6 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -239,7 +239,7 @@ PublicKeyRing.prototype.getCosigner = function(pubKey) { }); var index = sorted.indexOf(pubKey); - if (index == -1) throw new Error('no public key in ring'); + if (index == -1) throw new Error('public key is not on the ring'); return index; } diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index c08687879..85e3ac820 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -39,7 +39,7 @@ function Wallet(opts) { }); if (copayConfig.forceNetwork && this.getNetworkName() !== copayConfig.networkName) throw new Error('Network forced to ' + copayConfig.networkName + - ' and tried to create a Wallet with network ' + this.getNetworkName()); + ' and tried to create a Wallet with network ' + this.getNetworkName()); this.log('creating ' + opts.requiredCopayers + ' of ' + opts.totalCopayers + ' wallet'); @@ -321,30 +321,30 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) { // This handler is repeaded on WalletFactory (#join). TODO case 'walletId': this.sendWalletReady(senderId); - break; + break; case 'walletReady': this.sendPublicKeyRing(senderId); - this.sendAddressBook(senderId); - this.sendAllTxProposals(senderId); // send old txps - break; + this.sendAddressBook(senderId); + this.sendAllTxProposals(senderId); // send old txps + break; case 'publicKeyRing': this._handlePublicKeyRing(senderId, data, isInbound); - break; + break; case 'reject': this._handleReject(senderId, data, isInbound); - break; + break; case 'seen': this._handleSeen(senderId, data, isInbound); - break; + break; case 'txProposal': this._handleTxProposal(senderId, data, isInbound); - break; + break; case 'indexes': this._handleIndexes(senderId, data, isInbound); - break; + break; case 'addressbook': this._handleAddressBook(senderId, data, isInbound); - break; + break; } }; @@ -495,11 +495,11 @@ Wallet.prototype.getRegisteredPeerIds = function() { }; Wallet.prototype.keepAlive = function() { - try{ + try { this.lock.keepAlive(); - } catch(e){ + } catch (e) { this.log(e); - this.emit('locked',null,'Wallet appears to be openned on other browser instance. Closing this one.' ); + this.emit('locked', null, 'Wallet appears to be openned on other browser instance. Closing this one.'); } }; @@ -535,9 +535,33 @@ Wallet.fromObj = function(o, storage, network, blockchain) { var opts = JSON.parse(JSON.stringify(o.opts)); opts.addressBook = o.addressBook; - opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing); - opts.txProposals = TxProposals.fromObj(o.txProposals, Wallet.builderOpts); - opts.privateKey = PrivateKey.fromObj(o.privateKey); + if (o.privateKey) + opts.privateKey = PrivateKey.fromObj(o.privateKey); + else + opts.privateKey = new PrivateKey({ + networkName: this.networkName + }); + + if (o.publicKeyRing) + opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing); + else { + opts.publicKeyRing = new PublicKeyRing({ + networkName: opts.networkName, + requiredCopayers: opts.requiredCopayers, + totalCopayers: opts.totalCopayers, + }); + opts.publicKeyRing.addCopayer( + opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(), + opts.nickname + ); + } + + if (o.txProposals) + opts.txProposals = TxProposals.fromObj(o.txProposals, Wallet.builderOpts); + else + opts.txProposals = new TxProposals({ + networkName: this.networkName, + }); opts.storage = storage; opts.network = network; @@ -788,22 +812,22 @@ Wallet.prototype.createPaymentTx = function(options, cb) { } return Wallet.request({ - method: 'GET', - url: options.uri, - headers: { - 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE - }, - responseType: 'arraybuffer' - }) - .success(function(data, status, headers, config) { - data = PayPro.PaymentRequest.decode(data); - var pr = new PayPro(); - pr = pr.makePaymentRequest(data); - return self.receivePaymentRequest(options, pr, cb); - }) - .error(function(data, status, headers, config) { - return cb(new Error('Status: ' + JSON.stringify(status))); - }); + method: 'GET', + url: options.uri, + headers: { + 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + }, + responseType: 'arraybuffer' + }) + .success(function(data, status, headers, config) { + data = PayPro.PaymentRequest.decode(data); + var pr = new PayPro(); + pr = pr.makePaymentRequest(data); + return self.receivePaymentRequest(options, pr, cb); + }) + .error(function(data, status, headers, config) { + return cb(new Error('Status: ' + JSON.stringify(status))); + }); }; Wallet.prototype.fetchPaymentTx = function(options, cb) { @@ -991,23 +1015,23 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { rpo.set('amount', +total.toString(10)); rpo.set('script', - Buffer.concat([ - new Buffer([ - 118, // OP_DUP - 169, // OP_HASH160 - 76, // OP_PUSHDATA1 - 20, // number of bytes - ]), - // needs to be ripesha'd - bitcore.util.sha256ripe160(options.refund_to), - new Buffer([ - 136, // OP_EQUALVERIFY - 172 // OP_CHECKSIG - ]) - ]) - ); + Buffer.concat([ + new Buffer([ + 118, // OP_DUP + 169, // OP_HASH160 + 76, // OP_PUSHDATA1 + 20, // number of bytes + ]), + // needs to be ripesha'd + bitcore.util.sha256ripe160(options.refund_to), + new Buffer([ + 136, // OP_EQUALVERIFY + 172 // OP_CHECKSIG + ]) + ]) + ); - refund_outputs.push(rpo.message); + refund_outputs.push(rpo.message); } // We send this to the serve after receiving a PaymentRequest @@ -1035,30 +1059,30 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { } return Wallet.request({ - method: 'POST', - url: txp.merchant.pr.pd.payment_url, - headers: { - // BIP-71 - 'Accept': PayPro.PAYMENT_ACK_CONTENT_TYPE, - 'Content-Type': PayPro.PAYMENT_CONTENT_TYPE - // XHR does not allow these: - // 'Content-Length': (pay.byteLength || pay.length) + '', - // 'Content-Transfer-Encoding': 'binary' - }, - // Technically how this should be done via XHR (used to - // be the ArrayBuffer, now you send the View instead). - data: view, - responseType: 'arraybuffer' - }) - .success(function(data, status, headers, config) { - data = PayPro.PaymentACK.decode(data); - var ack = new PayPro(); - ack = ack.makePaymentACK(data); - return self.receivePaymentRequestACK(ntxid, tx, txp, ack, cb); - }) - .error(function(data, status, headers, config) { - return cb(new Error('Status: ' + JSON.stringify(status))); - }); + method: 'POST', + url: txp.merchant.pr.pd.payment_url, + headers: { + // BIP-71 + 'Accept': PayPro.PAYMENT_ACK_CONTENT_TYPE, + 'Content-Type': PayPro.PAYMENT_CONTENT_TYPE + // XHR does not allow these: + // 'Content-Length': (pay.byteLength || pay.length) + '', + // 'Content-Transfer-Encoding': 'binary' + }, + // Technically how this should be done via XHR (used to + // be the ArrayBuffer, now you send the View instead). + data: view, + responseType: 'arraybuffer' + }) + .success(function(data, status, headers, config) { + data = PayPro.PaymentACK.decode(data); + var ack = new PayPro(); + ack = ack.makePaymentACK(data); + return self.receivePaymentRequestACK(ntxid, tx, txp, ack, cb); + }) + .error(function(data, status, headers, config) { + return cb(new Error('Status: ' + JSON.stringify(status))); + }); }; Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) { @@ -1175,8 +1199,8 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) merchantData.total = merchantData.total.toString(10); var b = new Builder(opts) - .setUnspent(unspent) - .setOutputs(outs); + .setUnspent(unspent) + .setOutputs(outs); merchantData.pr.pd.outputs.forEach(function(output, i) { var script = { @@ -1368,7 +1392,7 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { // Actual script var as = new Buffer(ro.script.buffer, 'hex') - .slice(ro.script.offset, ro.script.limit); + .slice(ro.script.offset, ro.script.limit); if (av.toString('hex') !== ev.toString('hex') || as.toString('hex') !== es.toString('hex')) { return false; @@ -1548,11 +1572,11 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos } var b = new Builder(opts) - .setUnspent(utxos) - .setOutputs([{ - address: toAddress, - amountSatStr: amountSatStr, - }]); + .setUnspent(utxos) + .setOutputs([{ + address: toAddress, + amountSatStr: amountSatStr, + }]); var selectedUtxos = b.getSelectedUnspent(); var inputChainPaths = selectedUtxos.map(function(utxo) { @@ -1649,29 +1673,29 @@ Wallet.prototype.indexDiscovery = function(start, change, copayerIndex, gap, cb) var self = this; async.doWhilst( function _do(next) { - // Optimize window to minimize the derivations. - var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1; - var addresses = self.deriveAddresses(scanIndex, scanWindow, change, copayerIndex); - self.blockchain.checkActivity(addresses, function(err, actives) { - if (err) throw err; + // Optimize window to minimize the derivations. + var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1; + var addresses = self.deriveAddresses(scanIndex, scanWindow, change, copayerIndex); + self.blockchain.checkActivity(addresses, function(err, actives) { + if (err) throw err; - // Check for new activities in the newlly scanned addresses - var recentActive = actives.reduce(function(r, e, i) { - return e ? scanIndex + i : r; - }, lastActive); - hasActivity = lastActive != recentActive; - lastActive = recentActive; - scanIndex += scanWindow; - next(); - }); - }, - function _while() { - return hasActivity; - }, - function _finnaly(err) { - if (err) return cb(err); - cb(null, lastActive); - } + // Check for new activities in the newlly scanned addresses + var recentActive = actives.reduce(function(r, e, i) { + return e ? scanIndex + i : r; + }, lastActive); + hasActivity = lastActive != recentActive; + lastActive = recentActive; + scanIndex += scanWindow; + next(); + }); + }, + function _while() { + return hasActivity; + }, + function _finnaly(err) { + if (err) return cb(err); + cb(null, lastActive); + } ); } diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js index 3173fcc31..cee830506 100644 --- a/js/models/core/WalletFactory.js +++ b/js/models/core/WalletFactory.js @@ -50,11 +50,19 @@ WalletFactory.prototype._checkRead = function(walletId) { return !!ret; }; -WalletFactory.prototype.fromObj = function(obj) { +WalletFactory.prototype.fromObj = function(obj, skipFields) { // not stored options obj.opts.reconnectDelay = this.walletDefaults.reconnectDelay; + skipFields = skipFields || []; + skipFields.forEach(function(k){ + if (obj[k]) + delete obj[k]; + else + throw new Error('unknown field:' + k); + }); + var w = Wallet.fromObj(obj, this.storage, this.network, this.blockchain); if (!w) return false; w.verbose = this.verbose; @@ -63,23 +71,23 @@ WalletFactory.prototype.fromObj = function(obj) { return w; }; -WalletFactory.prototype.fromEncryptedObj = function(base64, password) { +WalletFactory.prototype.fromEncryptedObj = function(base64, password, skipFields) { this.storage._setPassphrase(password); var walletObj = this.storage.import(base64); if (!walletObj) return false; - var w = this.fromObj(walletObj); + var w = this.fromObj(walletObj, skipFields); return w; }; -WalletFactory.prototype.import = function(base64, password) { +WalletFactory.prototype.import = function(base64, password, skipFields) { var self = this; - var w = self.fromEncryptedObj(base64, password); + var w = self.fromEncryptedObj(base64, password, skipFields); if (!w) throw new Error('Wrong password'); return w; } -WalletFactory.prototype.read = function(walletId) { +WalletFactory.prototype.read = function(walletId, skipFields) { if (!this._checkRead(walletId)) return false; @@ -94,7 +102,7 @@ WalletFactory.prototype.read = function(walletId) { obj.addressBook = s.get(walletId, 'addressBook'); obj.backupOffered = s.get(walletId, 'backupOffered'); - var w = this.fromObj(obj); + var w = this.fromObj(obj, skipFields); return w; }; diff --git a/test/test.WalletFactory.js b/test/test.WalletFactory.js index 7e24aa24d..47a1ab9b8 100644 --- a/test/test.WalletFactory.js +++ b/test/test.WalletFactory.js @@ -175,6 +175,19 @@ describe('WalletFactory model', function() { assertObjectEqual(w.toObj(), JSON.parse(o)); }); + + it('#fromObj, skipping fields', function() { + var wf = new WalletFactory(config, '0.0.5'); + var w = wf.fromObj(JSON.parse(o), ['publicKeyRing']); + + should.exist(w); + w.id.should.equal("dbfe10c3fae71cea"); + should.exist(w.publicKeyRing.getCopayerId); + should.exist(w.txProposals.toObj()); + should.exist(w.privateKey.toObj()); + (function() { assertObjectEqual(w.toObj(), JSON.parse(o))}).should.throw(); + }); + it('support old index schema: #fromObj #toObj round trip', function() { var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"changeIndex":0,"receiveIndex":0},"copayersBackup":[],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{}}'; var o2 = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":0},{"copayerIndex":0,"changeIndex":0,"receiveIndex":0},{"copayerIndex":1,"changeIndex":0,"receiveIndex":0},{"copayerIndex":2,"changeIndex":0,"receiveIndex":0},{"copayerIndex":3,"changeIndex":0,"receiveIndex":0},{"copayerIndex":4,"changeIndex":0,"receiveIndex":0}],"copayersBackup":[],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{}}'; diff --git a/views/import.html b/views/import.html index 627e1b92c..da2dabc00 100644 --- a/views/import.html +++ b/views/import.html @@ -3,12 +3,14 @@ {{ importStatus }} +
Copay
+

{{title}}

@@ -20,6 +22,12 @@ +
+ + Skipping fields: {{skipFields}} +
+ +