From d2085c9b9e446179b40b137a0cd6cc445b58700a Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 1 Mar 2015 11:39:42 -0300 Subject: [PATCH] export/import --- lib/client/api.js | 134 ++++--------------------------------- lib/client/credentials.js | 46 +++++++++++++ test/integration/client.js | 26 +++++++ 3 files changed, 85 insertions(+), 121 deletions(-) diff --git a/lib/client/api.js b/lib/client/api.js index 62ddcc8..33346c3 100644 --- a/lib/client/api.js +++ b/lib/client/api.js @@ -192,11 +192,13 @@ API.prototype.createWallet = function(walletName, copayerName, m, n, network, cb var self = this; network = network || 'livenet'; - if (!_.contains(['testnet', 'livenet'], network)) - return cb('Invalid network'); + if (!_.contains(['testnet', 'livenet'], network)) return cb('Invalid network'); if (!self.credentials) { + log.info('Generating new keys'); self.credentials = Credentials.create(network); + } else { + log.info('Using existing keys'); } $.checkState(network == self.credentials.network); @@ -226,39 +228,6 @@ API.prototype.createWallet = function(walletName, copayerName, m, n, network, cb }); }; - -// API.prototype.reCreateWallet = function(walletName, cb) { -// var self = this; -// this._loadAndCheck(function(err, wcd) { -// if (err) return cb(err); - -// var walletPrivKey = new Bitcore.PrivateKey(); -// var args = { -// name: walletName, -// m: wcd.m, -// n: wcd.n, -// pubKey: walletPrivKey.toPublicKey().toString(), -// network: wcd.network, -// }; -// var url = '/v1/wallets/'; -// self._doPostRequest(url, args, function(err, body) { -// if (err) return cb(err); - -// var walletId = body.walletId; - -// var secret = WalletUtils.toSecret(walletId, walletPrivKey, wcd.network); -// var i = 0; -// async.each(wcd.publicKeyRing, function(xpub, next) { -// var copayerName = 'recovered Copayer #' + i; -// self._doJoinWallet(walletId, walletPrivKey, wcd.publicKeyRing[i++], copayerName, next); -// }, function(err) { -// return cb(err); -// }); -// }); -// }); -// }; - - API.prototype.joinWallet = function(secret, copayerName, cb) { var self = this; @@ -371,96 +340,19 @@ API.prototype.getBalance = function(cb) { }; /** - * Export does not try to complete the wallet from the server. Exports the - * wallet as it is now. - * - * @param opts.access =['full', 'readonly', 'readwrite'] + * Exports the wallet as it is now. */ -// API.prototype.export = function(opts, cb) { -// $.checkState(this.credentials); -// $.shouldBeFunction(cb); - -// var self = this; - -// opts = opts || {}; -// var access = opts.access || 'full'; - -// this._load(function(err, wcd) { -// if (err) return cb(err); -// var v = []; - -// var myXPubKey = wcd.xPrivKey ? (new Bitcore.HDPublicKey(wcd.xPrivKey)).toString() : ''; - -// _.each(WALLET_CRITICAL_DATA, function(k) { -// var d; - -// if (access != 'full' && k === 'xPrivKey') { -// v.push(null); -// return; -// } - -// // Skips own pub key IF priv key is exported -// if (access == 'full' && k === 'publicKeyRing') { -// d = _.without(wcd[k], myXPubKey); -// } else { -// d = wcd[k]; -// } -// v.push(d); -// }); - -// if (access != 'full') { -// v.push(wcd.copayerId); -// v.push(wcd.roPrivKey); -// if (access == 'readwrite') { -// v.push(wcd.requestPrivKey); -// } -// } - -// return cb(null, JSON.stringify(v)); -// }); -// } - - -// API.prototype.import = function(str, cb) { -// var self = this; - -// this.storage.load(function(err, wcd) { -// if (wcd) -// return cb('Storage already contains a wallet'); - -// wcd = {}; - -// var inData = JSON.parse(str); -// var i = 0; - -// _.each(WALLET_CRITICAL_DATA.concat(WALLET_EXTRA_DATA), function(k) { -// wcd[k] = inData[i++]; -// }); - -// if (wcd.xPrivKey) { -// var xpriv = new Bitcore.HDPrivateKey(wcd.xPrivKey); -// var xPubKey = new Bitcore.HDPublicKey(xpriv).toString(); -// wcd.publicKeyRing.unshift(xPubKey); -// wcd.copayerId = WalletUtils.xPubToCopayerId(xPubKey); -// wcd.roPrivKey = xpriv.derive('m/1/0').privateKey.toWIF(); -// wcd.requestPrivKey = xpriv.derive('m/1/1').privateKey.toWIF(); -// } - -// if (!wcd.publicKeyRing) -// return cb('Invalid source wallet'); +API.prototype.export = function() { + $.checkState(this.credentials); -// wcd.network = wcd.publicKeyRing[0].substr(0, 4) == 'tpub' ? 'testnet' : 'livenet'; + return this.credentials.exportCompressed(); +} -// self.save(wcd, function(err) { -// return cb(err, WalletUtils.accessFromData(wcd)); -// }); -// }); -// }; - -/** - * - */ +API.prototype.import = function(str) { + this.credentials = new Credentials(); + this.credentials.importCompressed(str); +}; /** * diff --git a/lib/client/credentials.js b/lib/client/credentials.js index c4bd7fe..2798b8b 100644 --- a/lib/client/credentials.js +++ b/lib/client/credentials.js @@ -21,6 +21,16 @@ var FIELDS = [ 'copayerName', ]; +var EXPORTABLE_FIELDS = [ + 'xPrivKey', + 'requestPrivKey', + 'xPubKey', + 'm', + 'n', + 'publicKeyRing', + 'sharedEncryptingKey', +]; + function Credentials() { this.version = '1.0.0'; }; @@ -104,6 +114,42 @@ Credentials.prototype.isComplete = function() { return true; }; +Credentials.prototype.exportCompressed = function() { + var self = this; + + var values = _.map(EXPORTABLE_FIELDS, function(field) { + if ((field == 'xPubKey' || field == 'requestPrivKey') && self.canSign()) return; + if (field == 'publicKeyRing') { + return _.without(self.publicKeyRing, self.xPubKey); + } + return self[field]; + }); + values.unshift(self.version); + + return JSON.stringify(values); +}; + +Credentials.prototype.importCompressed = function(compressed) { + var self = this; + + var list; + try { + list = JSON.parse(compressed); + } catch (ex) { + throw new Error('Invalid string'); + } + + // Remove version + var version = list[0]; + list = _.rest(list); + _.each(EXPORTABLE_FIELDS, function(field, i) { + self[field] = list[i]; + }); + self._expand(); + + self.network = self.xPubKey.substr(0, 4) == 'tpub' ? 'testnet' : 'livenet'; + self.publicKeyRing.push(self.xPubKey); +}; module.exports = Credentials; diff --git a/test/integration/client.js b/test/integration/client.js index 1b0c8a1..63bc0fc 100644 --- a/test/integration/client.js +++ b/test/integration/client.js @@ -864,6 +864,32 @@ describe('client API ', function() { it.skip('should get paginated transaction history', function(done) {}); }); + describe('Export & Import', function() { + it('should export & import', function(done) { + helpers.createAndJoinWallet(clients, 1, 1, function() { + clients[0].createAddress(function(err, address) { + should.not.exist(err); + should.exist(address.address); + + var exported = clients[0].export(); + + var importedClient = new Client({ + request: helpers.getRequest(app), + }); + importedClient.import(exported); + + importedClient.getMainAddresses({}, function(err, list) { + should.not.exist(err); + should.exist(list); + list.length.should.equal(1); + list[0].address.should.equal(address.address); + done(); + }); + }); + }) + }); + }); + describe('Air gapped related flows', function() { it('should create wallet in proxy from airgapped', function(done) { var airgapped = new AirGapped({