diff --git a/app.js b/app.js index 388a4d1..440a1b7 100644 --- a/app.js +++ b/app.js @@ -88,19 +88,21 @@ function getServerWithAuth(req, res, cb) { router.post('/v1/wallets/', function(req, res) { var server = CopayServer.getInstance(); server.createWallet(req.body, function(err, walletId) { - if (err) returnError(err, res); + if (err) return returnError(err, res); - res.json(walletId); + res.json({ + walletId: walletId, + }); }); }); router.post('/v1/wallets/:id/copayers/', function(req, res) { req.body.walletId = req.params['id']; var server = CopayServer.getInstance(); - server.joinWallet(req.body, function(err, copayerId) { - if (err) returnError(err, res); + server.joinWallet(req.body, function(err, result) { + if (err) return returnError(err, res); - res.json(copayerId); + res.json(result); }); }); @@ -117,7 +119,7 @@ router.get('/v1/wallets/', function(req, res) { router.post('/v1/addresses/', function(req, res) { getServerWithAuth(req, res, function(server) { server.createAddress(req.body, function(err, address) { - if (err) returnError(err, res); + if (err) return returnError(err, res); res.json(address); }); }); @@ -126,7 +128,7 @@ router.post('/v1/addresses/', function(req, res) { router.get('/v1/addresses/', function(req, res) { getServerWithAuth(req, res, function(server) { server.getAddresses({}, function(err, addresses) { - if (err) returnError(err, res); + if (err) return returnError(err, res); res.json(addresses); }); }); @@ -135,7 +137,7 @@ router.get('/v1/addresses/', function(req, res) { router.get('/v1/balance/', function(req, res) { getServerWithAuth(req, res, function(server) { server.getBalance({}, function(err, balance) { - if (err) returnError(err, res); + if (err) return returnError(err, res); res.json(balance); }); }); diff --git a/cli.js b/cli.js index 39c7fd0..3e1e0b3 100644 --- a/cli.js +++ b/cli.js @@ -3,17 +3,23 @@ var async = require('async'); var log = require('npmlog'); var fs = require('fs'); -var clilib = require('./lib/clilib'); +var CliLib = require('./lib/clilib'); -fs.unlinkSync('.bit'); +try { + fs.unlinkSync('copay.dat'); +} catch (e) {} -clilib.createWallet('my wallet', 'me', 1, 1, function(err, secret) { +var cli = new CliLib({ + filename: 'copay.dat' +}); + +cli.createWallet('my wallet', 'me', 1, 1, 'testnet', function(err, secret) { if (err) { console.log(err); process.exit(); } - clilib.status(function(err, status) { + cli.status(function(err, status) { if (err) { console.log(err); process.exit(); diff --git a/lib/clilib.js b/lib/clilib.js index 3f87b77..759466f 100644 --- a/lib/clilib.js +++ b/lib/clilib.js @@ -13,35 +13,64 @@ var SignUtils = require('./signutils'); var BASE_URL = 'http://localhost:3001/copay/api/'; -var clilib = {}; function _getUrl(path) { return BASE_URL + path; }; +function _parseError(body) { + if (_.isString(body)) { + body = JSON.parse(body); + } + var code = body.code || 'ERROR'; + var message = body.error || 'There was an unknown error processing the request'; + log.error(code, message); +}; + +function _signRequest(url, args, privKey) { + var message = url + (args ? '|' + JSON.stringify(args) : ''); + return SignUtils.sign(message, privKey); +}; -function _signRequest(url, args) { +function _createXPrivKey() { + return new Bitcore.HDPrivateKey().toString(); +}; +function CliLib(opts) { + this.filename = opts.filename; }; -function _save(data) { - fs.writeFileSync('.bit', JSON.stringify(data)); +CliLib.prototype._save = function(data) { + fs.writeFileSync(this.filename, JSON.stringify(data)); }; -function _load() { +CliLib.prototype._load = function() { try { - return JSON.parse(fs.readFileSync('.bit')); + return JSON.parse(fs.readFileSync(this.filename)); } catch (ex) {} }; -function _createXPrivKey() { - return new Bitcore.HDPrivateKey().toString(); +CliLib.prototype._loadAndCheck = function() { + var data = this._load(); + if (!data) { + log.error('Wallet file not found.'); + process.exit(1); + } + if (data.n > 1) { + var pkrComplete = data.publicKeyRing && data.m && data.publicKeyRing.length === data.n; + if (!pkrComplete) { + log.warn('The file ' + this.filename + ' is incomplete. It will allow you to operate with the wallet but it should not be trusted as a backup. Please wait for all copayers to join the wallet and run the tool with -export flag.') + } + } + return data; }; -clilib.createWallet = function(walletName, copayerName, m, n, cb) { - var data = _load(); - if (data) return cb('Only one wallet can exist'); +CliLib.prototype.createWallet = function(walletName, copayerName, m, n, network, cb) { + var self = this; + + var data = this._load(); + if (data) return cb('Only one wallet is supported in this version'); data = { xPrivKey: _createXPrivKey(), @@ -56,6 +85,7 @@ clilib.createWallet = function(walletName, copayerName, m, n, cb) { m: m, n: n, pubKey: pubKey.toString(), + network: network || 'livenet', }; request({ @@ -65,12 +95,18 @@ clilib.createWallet = function(walletName, copayerName, m, n, cb) { json: true, }, function(err, res, body) { if (err) return cb(err); - var walletId = body; - data.secret = walletId + '|' + privKey.toString(); + if (res.statusCode != 200) { + _parseError(body); + return cb('Request error'); + } - _save(data); + var walletId = body.walletId; + var secret = walletId + '|' + privKey.toString(); + data.secret = secret; - clilib.joinWallet(data.secret, copayerName, function(err) { + self._save(data); + + self._joinWallet(data, secret, copayerName, function(err) { if (err) return cb(err); return cb(null, data.secret); @@ -78,27 +114,22 @@ clilib.createWallet = function(walletName, copayerName, m, n, cb) { }); }; -clilib.joinWallet = function(secret, copayerName, cb) { - var data = _load(); - if (data && data.copayerId) return cb('Only one wallet can exist'); - if (!data) { - data = { - xPrivKey: _createXPrivKey(), - }; - } +CliLib.prototype._joinWallet = function(data, secret, copayerName, cb) { + var self = this; var secretSplit = secret.split('|'); var walletId = secretSplit[0]; var privKey = Bitcore.PrivateKey.fromString(secretSplit[1]); var pubKey = privKey.toPublicKey(); - var xPubKey = new Bitcore.HDPublicKey(data.xPrivKey).toString(); - var xPubKeySignature = SignUtils.sign(xPubKey, privKey); + var xPubKey = new Bitcore.HDPublicKey(data.xPrivKey); + var xPubKeySignature = SignUtils.sign(xPubKey.toString(), privKey); + var signingPrivKey = xPubKey.derive('m/1/0'); var args = { walletId: walletId, name: copayerName, - xPubKey: xPubKey, + xPubKey: xPubKey.toString(), xPubKeySignature: xPubKeySignature, }; @@ -109,23 +140,43 @@ clilib.joinWallet = function(secret, copayerName, cb) { json: true, }, function(err, res, body) { if (err) return cb(err); - var copayerId = body; - data.copayerId = copayerId; + if (res.statusCode != 200) { + _parseError(body); + return cb('Request error'); + } + + var wallet = body.wallet; + data.copayerId = body.copayerId; + data.signingPrivKey = signingPrivKey.toString(); + data.m = wallet.m; + data.n = wallet.n; + data.publicKeyRing = wallet.publicKeyRing; + self._save(data); + + return cb(); + }); +}; - _save(data); +CliLib.prototype.joinWallet = function(secret, copayerName, cb) { + var self = this; - // TODO: call status to retrieve wallet.m + var data = this._load(); + if (data) return cb('Only one wallet is supported in this version'); - return clilib.status(cb); - }); + data = { + xPrivKey: _createXPrivKey(), + }; + + self._joinWallet(data, secret, copayerName, cb); }; -clilib.status = function(cb) { - var data = _load(); - if (!data || !data.copayerId) return cb('Not a part of an active wallet'); +CliLib.prototype.status = function(cb) { + var self = this; + + var data = this._loadAndCheck(); - var url = 'v1/dump/'; - var signature = _signRequest(url); + var url = 'v1/wallets/'; + var signature = _signRequest(url, null, data.signingPrivKey); request({ headers: { @@ -134,31 +185,36 @@ clilib.status = function(cb) { }, method: 'get', url: _getUrl(url), + json: true, }, function(err, res, body) { if (err) return cb(err); + if (res.statusCode != 200) { + _parseError(body); + return cb('Request error'); + } return cb(null, body); }); }; -clilib.send = function(addressTo, amount, message, cb) { +CliLib.prototype.send = function(addressTo, amount, message, cb) { }; -clilib.sign = function(proposalId, cb) { +CliLib.prototype.sign = function(proposalId, cb) { }; -clilib.reject = function(proposalId, cb) { +CliLib.prototype.reject = function(proposalId, cb) { }; -clilib.address = function(cb) { +CliLib.prototype.address = function(cb) { }; -clilib.history = function(limit, cb) { +CliLib.prototype.history = function(limit, cb) { }; -module.exports = clilib; +module.exports = CliLib; diff --git a/lib/server.js b/lib/server.js index f47bd39..7f125bb 100644 --- a/lib/server.js +++ b/lib/server.js @@ -221,11 +221,16 @@ CopayServer.prototype.joinWallet = function(opts, cb) { wallet.addCopayer(copayer); self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) { + if (err) return cb(err); + self._notify('NewCopayer', { walletId: opts.walletId, copayerId: copayer.id, }); - return cb(err, copayer.id); + return cb(null, { + copayerId: copayer.id, + wallet: wallet + }); }); }); }); diff --git a/test/integration.js b/test/integration.js index 35709f1..5a6eeca 100644 --- a/test/integration.js +++ b/test/integration.js @@ -57,8 +57,8 @@ helpers.createAndJoinWallet = function(m, n, cb) { xPubKeySignature: TestData.copayers[i].xPubKeySignature, }; - server.joinWallet(copayerOpts, function(err, copayerId) { - copayerIds.push(copayerId); + server.joinWallet(copayerOpts, function(err, result) { + copayerIds.push(result.copayerId); return cb(err); }); }, function(err) { @@ -345,8 +345,9 @@ describe('Copay server', function() { xPubKey: TestData.copayers[0].xPubKey, xPubKeySignature: TestData.copayers[0].xPubKeySignature, }; - server.joinWallet(copayerOpts, function(err, copayerId) { + 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); @@ -367,8 +368,8 @@ describe('Copay server', function() { xPubKey: TestData.copayers[0].xPubKey, xPubKeySignature: TestData.copayers[0].xPubKeySignature, }; - server.joinWallet(copayerOpts, function(err, copayerId) { - should.not.exist(copayerId); + server.joinWallet(copayerOpts, function(err, result) { + should.not.exist(result); err.should.exist; err.message.should.contain('name'); done(); @@ -548,9 +549,9 @@ describe('Copay server', function() { xPubKey: TestData.copayers[0].xPubKey, xPubKeySignature: TestData.copayers[0].xPubKeySignature, }; - server.joinWallet(copayerOpts, function(err, copayerId) { + server.joinWallet(copayerOpts, function(err, result) { should.not.exist(err); - helpers.getAuthServer(copayerId, function(server) { + helpers.getAuthServer(result.copayerId, function(server) { server.createAddress({}, function(err, address) { should.not.exist(address); err.should.exist; @@ -652,9 +653,9 @@ describe('Copay server', function() { xPubKey: TestData.copayers[0].xPubKey, xPubKeySignature: TestData.copayers[0].xPubKeySignature, }; - server.joinWallet(copayerOpts, function(err, copayerId) { + server.joinWallet(copayerOpts, function(err, result) { should.not.exist(err); - helpers.getAuthServer(copayerId, function(server, wallet) { + helpers.getAuthServer(result.copayerId, function(server, wallet) { var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, copayerPriv[0].privKey); server.createTx(txOpts, function(err, tx) { should.not.exist(tx);