diff --git a/app.js b/app.js index 4198b52..4b8bd6f 100644 --- a/app.js +++ b/app.js @@ -44,6 +44,8 @@ var router = express.Router(); function returnError(err, res) { if (err instanceof CopayServer.ClientError) { + +console.log('[app.js.47]'); //TODO var status = (err.code == 'NOTAUTHORIZED') ? 401 : 400; res.status(status).json({ code: err.code, @@ -78,7 +80,6 @@ function getServerWithAuth(req, res, cb) { message: req.url + '|' + JSON.stringify(req.body), signature: credentials.signature, }; - CopayServer.getInstanceWithAuth(auth, function(err, server) { if (err) return returnError(err, res); return cb(server); @@ -87,26 +88,27 @@ function getServerWithAuth(req, res, cb) { router.post('/v1/wallets/', function(req, res) { var server = CopayServer.getInstance(); - server.createWallet(req.body, function(err, wallet) { - if (err) returnError(err, res); + server.createWallet(req.body, function(err, walletId) { + if (err) return returnError(err, res); - res.json(wallet); + 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) { - if (err) returnError(err, res); + server.joinWallet(req.body, function(err, result) { + if (err) return returnError(err, res); - res.end(); + res.json(result); }); }); router.get('/v1/wallets/', function(req, res) { getServerWithAuth(req, res, function(server) { - if (err) return returnError(err, res); server.getWallet({}, function(err, wallet) { if (err) returnError(err, res); res.json(wallet); @@ -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/bit-wallet/bit b/bit-wallet/bit new file mode 100755 index 0000000..2ac9fc0 --- /dev/null +++ b/bit-wallet/bit @@ -0,0 +1,14 @@ +#!/usr/bin/env node + +var program = require('commander'); +var cli = require('../lib/clilib.js'); + +program + .version('0.0.1') + .command('create [username]', 'creates a wallet') + .command('join [username]', 'join a wallet') + .command('status', 'get wallet status') + .parse(process.argv); + + + diff --git a/bit-wallet/bit-create b/bit-wallet/bit-create new file mode 100755 index 0000000..6cdacee --- /dev/null +++ b/bit-wallet/bit-create @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +var program = require('commander'); +var CliLib = require('../lib/clilib.js'); +var common = require('./common'); + +program + .version('0.0.1') + .option('-c,--config [file]', 'Wallet config filename') + .option('-n,--network [networkname]', 'livenet|testnet', String, 'livenet') + .usage('[options] [copayerName]') + .parse(process.argv); + +var args = program.args; +if (!args[0]) + program.help(); + +var walletName = args[0]; +var copayerName = args[2] || process.env.USER; +var network = program.network; + +var mn = common.parseMN(args[1]); + +var cli = new CliLib({ + filename: program.config +}); +cli.createWallet(walletName, copayerName, mn[0], mn[1], network, function(err, secret) { + common.die(err); + console.log(' * Wallet Created.'); + console.log(' - Secret to share:\n\t' + secret); +}); + diff --git a/bit-wallet/bit-create.js b/bit-wallet/bit-create.js new file mode 100644 index 0000000..1f02e8c --- /dev/null +++ b/bit-wallet/bit-create.js @@ -0,0 +1,10 @@ +#!/usr/bin/env node + +var program = require('commander'); +var cli = require('../lib/clilib.js'); + +program + .version('0.0.1') + .parse(process.argv); + + diff --git a/bit-wallet/bit-join b/bit-wallet/bit-join new file mode 100644 index 0000000..63f9bc9 --- /dev/null +++ b/bit-wallet/bit-join @@ -0,0 +1,27 @@ +#!/usr/bin/env node + +var program = require('commander'); +var CliLib = require('../lib/clilib.js'); +var common = require('./common'); + +program + .version('0.0.1') + .option('-c,--config [file]', 'Wallet config filename') + .usage('[options] [copayerName]') + .parse(process.argv); + +var args = program.args; +if (!args[0]) + program.help(); + +var secret = args[0]; +var copayerName = args[1] || process.env.USER; + +var cli = new CliLib({ + filename: program.config +}); + +cli.joinWallet(secret, copayerName, function(err, xx) { + common.die(err); + console.log(' * Wallet Joined.', xx || ''); +}); diff --git a/bit-wallet/bit-status b/bit-wallet/bit-status new file mode 100644 index 0000000..14c632d --- /dev/null +++ b/bit-wallet/bit-status @@ -0,0 +1,21 @@ +#!/usr/bin/env node + +var program = require('commander'); +var CliLib = require('../lib/clilib.js'); +var common = require('./common'); + +program + .version('0.0.1') + .option('-c,--config [file]', 'Wallet config filename') + .parse(process.argv); + +var args = program.args; +var cli = new CliLib({ + filename: program.config +}); + +cli.status(function(err, xx) { + common.die(err); + console.log(' * Status:', xx); +}); + diff --git a/bit-wallet/common.js b/bit-wallet/common.js new file mode 100644 index 0000000..7a6c256 --- /dev/null +++ b/bit-wallet/common.js @@ -0,0 +1,27 @@ +var common = function() {}; + + +var die = common.die = function(err) { + if (err) { + console.error(err); + process.exit(1); + } +}; + +common.parseMN = function(MN) { + if (!MN) + die('No m-n parameter'); + var mn = MN.split('-'); + + var m = parseInt(mn[0]); + var n = parseInt(mn[1]); + + if (!m || ! n) { + die('Bad m-n parameter'); + } + + return [m, n]; +}; + + +module.exports = common; diff --git a/cli.js b/cli.js new file mode 100644 index 0000000..3e1e0b3 --- /dev/null +++ b/cli.js @@ -0,0 +1,30 @@ +var _ = require('lodash'); +var async = require('async'); +var log = require('npmlog'); +var fs = require('fs'); + +var CliLib = require('./lib/clilib'); + +try { + fs.unlinkSync('copay.dat'); +} catch (e) {} + +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(); + } + + cli.status(function(err, status) { + if (err) { + console.log(err); + process.exit(); + } + + console.log(status); + }) +}); diff --git a/lib/clilib.js b/lib/clilib.js index 6c0a52a..cca1b44 100644 --- a/lib/clilib.js +++ b/lib/clilib.js @@ -4,7 +4,6 @@ var _ = require('lodash'); var async = require('async'); var log = require('npmlog'); var request = require('request') -var commander = require('commander') log.debug = log.verbose; log.level = 'debug'; var fs = require('fs') @@ -14,132 +13,238 @@ var SignUtils = require('./signutils'); var BASE_URL = 'http://localhost:3001/copay/api/'; -var cli = {}; function _getUrl(path) { return BASE_URL + path; }; - -function signRequest(url, args) { - +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 save(data) { - fs.writeFileSync('./.bit', JSON.stringify(data)); +function _signRequest(url, args, privKey) { + var message = url + '|' + JSON.stringify(args); + return SignUtils.sign(message, privKey); }; -function load() { +function _createXPrivKey() { + return new Bitcore.HDPrivateKey().toString(); +}; + +function CliLib(opts) { + if (!opts.filename) { + throw new Error('Please set the config filename'); + } + this.filename = opts.filename; +}; + + +CliLib.prototype._save = function(data) { + fs.writeFileSync(this.filename, JSON.stringify(data)); +}; + +CliLib.prototype._load = function() { try { - return JSON.parse(fs.readFileSync('./.bit')); + return JSON.parse(fs.readFileSync(this.filename)); } catch (ex) {} }; - - -clilib.createWallet = function(walletName, copayerName, m, n, cb) { - var data = load(); +CliLib.prototype._loadAndCheck = function() { + var data = this._load(); if (!data) { - data = {}; - data.xPrivKey = new Bitcore.HDPrivateKey().toString(); - data.m = m; + log.error('Wallet file not found.'); + process.exit(1); } + if (data.verified == 'corrupt') { + log.error('The wallet is tagged as corrupt. Some of the copayers cannot be verified to have known the wallet secret.'); + 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.prototype.createWallet = function(walletName, copayerName, m, n, network, cb) { + var self = this; + + var data = this._load(); + if (data) return cb('File ' + this.filename + ' already contains a wallet'); + + // Generate wallet key pair to verify copayers var privKey = new Bitcore.PrivateKey(); var pubKey = privKey.toPublicKey(); + data = { + xPrivKey: _createXPrivKey(), + m: m, + n: n, + walletPrivKey: privKey.toString(), + }; + var args = { name: walletName, m: m, n: n, pubKey: pubKey.toString(), + network: network || 'livenet', }; request({ method: 'post', - url: _getUrl('v1/wallets'), + url: _getUrl('/v1/wallets/'), body: args, json: true, }, function(err, res, body) { if (err) return cb(err); - var walletId = body; - var secret = walletId + '|' + privKey.toString(); + if (res.statusCode != 200) { + _parseError(body); + return cb('Request error'); + } - joinWallet(secret, copayerName, function(err) { + var walletId = body.walletId; + var secret = walletId + ':' + privKey.toString(); + data.secret = secret; + + self._save(data); + + self._joinWallet(data, secret, copayerName, function(err) { if (err) return cb(err); - save(data); - return cb(null, secret); + return cb(null, data.secret); }); }); }; -clilib.joinWallet = function(secret, copayerName, cb) { - var data = load(); - if (!data) { - data = {}; - data.xPrivKey = new Bitcore.HDPrivateKey().toString(); - } - var secretSplit = secret.split('|'); +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 = (new Bitcore.HDPrivateKey(data.xPrivKey)).derive('m/1/0').privateKey; var args = { walletId: walletId, name: copayerName, - xPubKey: xPubKey, + xPubKey: xPubKey.toString(), xPubKeySignature: xPubKeySignature, }; request({ method: 'post', - url: _getUrl('v1/wallets/' + walletId + '/copayers'), + url: _getUrl('/v1/wallets/' + walletId + '/copayers'), body: args, json: true, }, function(err, res, body) { if (err) return cb(err); + if (res.statusCode != 200) { + _parseError(body); + return cb('Request error'); + } - var copayerId = body; - data.copayerId = copayerId; - save(data); - return status(cb); - }); -}; + 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); -clilib.status = function(cb) { - request({ - method: 'get', - url: _getUrl('v1/dump/'), - }, function(err, res, body) { - if (err) return cb(err); - - console.log(body); return cb(); }); }; -clilib.send = function(addressTo, amount, message, cb) { +CliLib.prototype.joinWallet = function(secret, copayerName, cb) { + var self = this; + + var data = this._load(); + if (data) return cb('File ' + this.filename + ' already contains a wallet'); + + data = { + xPrivKey: _createXPrivKey(), + }; + + self._joinWallet(data, secret, copayerName, cb); +}; + +CliLib.prototype.status = function(cb) { + var self = this; + + var data = this._loadAndCheck(); + console.log('[clilib.js.180:data:]', data); //TODO + + var url = '/v1/wallets/'; + var signature = _signRequest(url, {}, data.signingPrivKey); + + request({ + headers: { + 'x-identity': data.copayerId, + 'x-signature': signature, + }, + 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'); + } + var wallet = body; + + if (wallet.n > 0 && wallet.status === 'complete' && !data.verified) { + var pubKey = Bitcore.PrivateKey.fromString(data.walletPrivKey).toPublicKey().toString(); + var fake = []; + _.each(wallet.copayers, function(copayer) { + if (!SignUtils.verify(copayer.xPubKey, copayer.xPubKeySignature, pubKey)) { + fake.push(copayer); + } + }); + if (fake.length > 0) { + log.error('Some copayers in the wallet could not be verified to have known the wallet secret'); + data.verified = 'corrupt'; + } else { + data.verified = 'ok'; + } + self._save(data); + } + + return cb(null, wallet); + }); +}; + +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..44b85c8 100644 --- a/lib/server.js +++ b/lib/server.js @@ -221,11 +221,17 @@ 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, + copayerName: copayer.name, + }); + return cb(null, { + copayerId: copayer.id, + wallet: wallet }); - return cb(err, copayer.id); }); }); }); diff --git a/lib/storage.js b/lib/storage.js index 50bcf06..4971fee 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -5,6 +5,7 @@ var levelup = require('levelup'); var async = require('async'); var $ = require('preconditions').singleton(); var log = require('npmlog'); +var util = require('util'); log.debug = log.verbose; var Wallet = require('./model/wallet'); @@ -195,8 +196,8 @@ Storage.prototype.fetchNotifications = function(walletId, opts, cb) { var txs = []; opts = opts || {}; opts.limit = _.isNumber(opts.limit) ? parseInt(opts.limit) : -1; - opts.minTs = _.isNumber(opts.minTs) ? zeroPad(opts.minTs,11) : 0; - opts.maxTs = _.isNumber(opts.maxTs) ? zeroPad(opts.maxTs,11) : MAX_TS; + opts.minTs = _.isNumber(opts.minTs) ? zeroPad(opts.minTs, 11) : 0; + opts.maxTs = _.isNumber(opts.maxTs) ? zeroPad(opts.maxTs, 11) : MAX_TS; var key = KEY.NOTIFICATION(walletId, opts.minTs); var endkey = KEY.NOTIFICATION(walletId, opts.maxTs); @@ -380,7 +381,11 @@ Storage.prototype._dump = function(cb, fn) { fn = fn || console.log; this.db.readStream() - .on('data', fn) + .on('data', function(data) { + fn(util.inspect(data, { + depth: 10 + })); + }) .on('end', function() { if (cb) return cb(); }); diff --git a/test/integration.js b/test/integration.js index fac3d52..131b32b 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) { @@ -66,7 +66,7 @@ helpers.createAndJoinWallet = function(m, n, cb) { helpers.getAuthServer(copayerIds[0], function(s) { s.getWallet({}, function(err, w) { - cb(s, w, _.take(TestData.copayers, w.n), copayerIds); + cb(s, w); }); }); }); @@ -177,68 +177,70 @@ var db, storage; describe('Copay server', function() { - beforeEach(function() { - db = levelup(memdown, { - valueEncoding: 'json' - }); - storage = new Storage({ - db: db - }); - CopayServer.initialize({ - storage: storage - }); + beforeEach(function() { + db = levelup(memdown, { + valueEncoding: 'json' }); + storage = new Storage({ + db: db + }); + CopayServer.initialize({ + storage: storage + }); + }); - describe('#getInstanceWithAuth', function() { - beforeEach(function() {}); + describe('#getInstanceWithAuth', function() { + beforeEach(function() {}); - it('should get server instance for existing copayer', function(done) { - helpers.createAndJoinWallet(1, 2, function(s, wallet, copayers, copayerIds) { - var xpriv = copayers[0].xPrivKey; - var priv = Bitcore.HDPrivateKey - .fromString(xpriv) - .derive('m/1/0') - .privateKey - .toString(); + it('should get server instance for existing copayer', function(done) { + helpers.createAndJoinWallet(1, 2, function(s, wallet) { + var xpriv = TestData.copayers[0].xPrivKey; + var priv = Bitcore.HDPrivateKey + .fromString(xpriv) + .derive('m/1/0') + .privateKey + .toString(); - var message = 'hola'; - var sig = SignUtils.sign(message, priv); + var message = 'hola'; + var sig = SignUtils.sign(message, priv); - CopayServer.getInstanceWithAuth({ - copayerId: copayerIds[0], - message: message, - signature: sig, - }, function(err, server) { - should.not.exist(err); - done(); - }); - }); - }); - - it('should fail when requesting for non-existent copayer', function(done) { CopayServer.getInstanceWithAuth({ - copayerId: 'ads', - message: 'dummy', - signature: 'dummy', + copayerId: wallet.copayers[0].id, + message: message, + signature: sig, }, function(err, server) { - err.should.contain('Copayer not found'); + should.not.exist(err); done(); }); }); + }); - it('should fail when message signature cannot be verified', function(done) { - helpers.createAndJoinWallet(1, 2, function(s, wallet, copayers, copayerIds) { - CopayServer.getInstanceWithAuth({ - copayerId: copayerIds[0], - message: 'dummy', - signature: 'dummy', - }, function(err, server) { - err.should.contain('Invalid signature'); - done(); - }); + it('should fail when requesting for non-existent copayer', function(done) { + CopayServer.getInstanceWithAuth({ + copayerId: 'ads', + message: TestData.message.text, + signature: TestData.message.signature, + }, function(err, server) { + err.code.should.equal('NOTAUTHORIZED'); + err.message.should.contain('Copayer not found'); + done(); + }); + }); + + it('should fail when message signature cannot be verified', function(done) { + helpers.createAndJoinWallet(1, 2, function(s, wallet) { + CopayServer.getInstanceWithAuth({ + copayerId: wallet.copayers[0].id, + message: 'dummy', + signature: 'dummy', + }, function(err, server) { + err.code.should.equal('NOTAUTHORIZED'); + err.message.should.contain('Invalid signature'); + done(); }); }); }); + }); describe('#createWallet', function() { var server; @@ -343,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); @@ -365,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(); @@ -546,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; @@ -598,12 +601,11 @@ describe('Copay server', function() { }); describe('#createTx', function() { - var server, wallet, copayerPriv; + var server, wallet; beforeEach(function(done) { - helpers.createAndJoinWallet(2, 3, function(s, w, c) { + helpers.createAndJoinWallet(2, 3, function(s, w) { server = s; wallet = w; - copayerPriv = c; server.createAddress({}, function(err, address) { done(); }); @@ -613,7 +615,7 @@ describe('Copay server', function() { it('should create a tx', function(done) { helpers.createUtxos(server, wallet, [100, 200], function(utxos) { helpers.stubBlockExplorer(server, utxos); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', copayerPriv[0].privKey); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey); server.createTx(txOpts, function(err, tx) { should.not.exist(err); tx.should.exist; @@ -650,10 +652,10 @@ 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) { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, copayerPriv[0].privKey); + helpers.getAuthServer(result.copayerId, function(server, wallet) { + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, TestData.copayers[0].privKey); server.createTx(txOpts, function(err, tx) { should.not.exist(tx); err.should.exist; @@ -668,7 +670,7 @@ describe('Copay server', function() { it('should fail to create tx for address invalid address', function(done) { helpers.createUtxos(server, wallet, [100, 200], function(utxos) { helpers.stubBlockExplorer(server, utxos); - var txOpts = helpers.createProposalOpts('invalid address', 80, null, copayerPriv[0].privKey); + var txOpts = helpers.createProposalOpts('invalid address', 80, null, TestData.copayers[0].privKey); server.createTx(txOpts, function(err, tx) { should.not.exist(tx); @@ -683,7 +685,7 @@ describe('Copay server', function() { it('should fail to create tx for address of different network', function(done) { helpers.createUtxos(server, wallet, [100, 200], function(utxos) { helpers.stubBlockExplorer(server, utxos); - var txOpts = helpers.createProposalOpts('myE38JHdxmQcTJGP1ZiX4BiGhDxMJDvLJD', 80, null, copayerPriv[0].privKey); + var txOpts = helpers.createProposalOpts('myE38JHdxmQcTJGP1ZiX4BiGhDxMJDvLJD', 80, null, TestData.copayers[0].privKey); server.createTx(txOpts, function(err, tx) { should.not.exist(tx); @@ -698,7 +700,7 @@ describe('Copay server', function() { it('should fail to create tx when insufficient funds', function(done) { helpers.createUtxos(server, wallet, [100], function(utxos) { helpers.stubBlockExplorer(server, utxos); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 120, null, copayerPriv[0].privKey); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 120, null, TestData.copayers[0].privKey); server.createTx(txOpts, function(err, tx) { err.code.should.equal('INSUFFICIENTFUNDS'); err.message.should.equal('Insufficient funds'); @@ -723,11 +725,11 @@ describe('Copay server', function() { it('should create tx when there is a pending tx and enough UTXOs', function(done) { helpers.createUtxos(server, wallet, [10.1, 10.2, 10.3], function(utxos) { helpers.stubBlockExplorer(server, utxos); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, copayerPriv[0].privKey); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, TestData.copayers[0].privKey); server.createTx(txOpts, function(err, tx) { should.not.exist(err); tx.should.exist; - var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 8, null, copayerPriv[0].privKey); + var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 8, null, TestData.copayers[0].privKey); server.createTx(txOpts2, function(err, tx) { should.not.exist(err); tx.should.exist; @@ -749,11 +751,11 @@ describe('Copay server', function() { it('should fail to create tx when there is a pending tx and not enough UTXOs', function(done) { helpers.createUtxos(server, wallet, [10.1, 10.2, 10.3], function(utxos) { helpers.stubBlockExplorer(server, utxos); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, copayerPriv[0].privKey); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, TestData.copayers[0].privKey); server.createTx(txOpts, function(err, tx) { should.not.exist(err); tx.should.exist; - var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 24, null, copayerPriv[0].privKey); + var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 24, null, TestData.copayers[0].privKey); server.createTx(txOpts2, function(err, tx) { err.code.should.equal('INSUFFICIENTFUNDS'); err.message.should.equal('Insufficient funds'); @@ -783,7 +785,7 @@ describe('Copay server', function() { should.not.exist(err); balance.totalAmount.should.equal(helpers.toSatoshi(N * 100)); balance.lockedAmount.should.equal(helpers.toSatoshi(0)); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, copayerPriv[0].privKey); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, TestData.copayers[0].privKey); async.map(_.range(N), function(i, cb) { server.createTx(txOpts, function(err, tx) { cb(err, tx); @@ -808,18 +810,16 @@ describe('Copay server', function() { describe('#rejectTx', function() { - var server, wallet, copayerPriv, txid, copayerIds; + var server, wallet, txid; beforeEach(function(done) { - helpers.createAndJoinWallet(2, 3, function(s, w, c, ids) { + helpers.createAndJoinWallet(2, 3, function(s, w) { server = s; wallet = w; - copayerPriv = c; - copayerIds = ids; server.createAddress({}, function(err, address) { helpers.createUtxos(server, wallet, _.range(1, 9), function(utxos) { helpers.stubBlockExplorer(server, utxos); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, copayerPriv[0].privKey); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey); server.createTx(txOpts, function(err, tx) { should.not.exist(err); tx.should.exist; @@ -847,8 +847,8 @@ describe('Copay server', function() { var actors = tx.getActors(); actors.length.should.equal(1); - actors[0].should.equal(copayerIds[0]); - tx.getActionBy(copayerIds[0]).type.should.equal('reject'); + actors[0].should.equal(wallet.copayers[0].id); + tx.getActionBy(wallet.copayers[0].id).type.should.equal('reject'); done(); }); @@ -858,18 +858,16 @@ describe('Copay server', function() { }); describe('#signTx', function() { - var server, wallet, copayerPriv, txid, copayerIds; + var server, wallet, txid; beforeEach(function(done) { - helpers.createAndJoinWallet(2, 3, function(s, w, c, ids) { + helpers.createAndJoinWallet(2, 3, function(s, w) { server = s; wallet = w; - copayerPriv = c; - copayerIds = ids; server.createAddress({}, function(err, address) { helpers.createUtxos(server, wallet, _.range(1, 9), function(utxos) { helpers.stubBlockExplorer(server, utxos); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, copayerPriv[0].privKey); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey); server.createTx(txOpts, function(err, tx) { should.not.exist(err); tx.should.exist; @@ -899,8 +897,8 @@ describe('Copay server', function() { var actors = tx.getActors(); actors.length.should.equal(1); - actors[0].should.equal(copayerIds[0]); - tx.getActionBy(copayerIds[0]).type.should.equal('accept'); + actors[0].should.equal(wallet.copayers[0].id); + tx.getActionBy(wallet.copayers[0].id).type.should.equal('accept'); done(); }); @@ -1002,12 +1000,11 @@ describe('Copay server', function() { describe('#signTx and broadcast', function() { - var server, wallet, copayerPriv, utxos; + var server, wallet, utxos; beforeEach(function(done) { - helpers.createAndJoinWallet(1, 1, function(s, w, c) { + helpers.createAndJoinWallet(1, 1, function(s, w) { server = s; wallet = w; - copayerPriv = c; server.createAddress({}, function(err, address) { helpers.createUtxos(server, wallet, _.range(1, 9), function(inutxos) { utxos = inutxos; @@ -1019,7 +1016,7 @@ describe('Copay server', function() { it('should sign and broadcast a tx', function(done) { helpers.stubBlockExplorer(server, utxos, '1122334455'); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, copayerPriv[0].privKey); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey); server.createTx(txOpts, function(err, txp) { should.not.exist(err); txp.should.exist; @@ -1045,7 +1042,7 @@ describe('Copay server', function() { it('should keep tx as *accepted* if unable to broadcast it', function(done) { helpers.stubBlockExplorer(server, utxos); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, copayerPriv[0].privKey); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey); server.createTx(txOpts, function(err, txp) { should.not.exist(err); txp.should.exist; @@ -1076,12 +1073,11 @@ describe('Copay server', function() { }); describe('Tx proposal workflow', function() { - var server, wallet, copayerPriv, utxos; + var server, wallet, utxos; beforeEach(function(done) { - helpers.createAndJoinWallet(2, 3, function(s, w, c) { + helpers.createAndJoinWallet(2, 3, function(s, w) { server = s; wallet = w; - copayerPriv = c; server.createAddress({}, function(err, address) { helpers.createUtxos(server, wallet, _.range(1, 9), function(inutxos) { utxos = inutxos; @@ -1093,7 +1089,7 @@ describe('Copay server', function() { it('other copayers should see pending proposal created by one copayer', function(done) { helpers.stubBlockExplorer(server, utxos); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', copayerPriv[0].privKey); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey); server.createTx(txOpts, function(err, txp) { should.not.exist(err); should.exist.txp; @@ -1119,21 +1115,20 @@ describe('Copay server', function() { }); describe('#getTxs', function() { - var server, wallet, copayerPriv, clock; + var server, wallet, clock; beforeEach(function(done) { if (server) return done(); this.timeout(5000); console.log('\tCreating TXS...'); clock = sinon.useFakeTimers(); - helpers.createAndJoinWallet(1, 1, function(s, w, c) { + helpers.createAndJoinWallet(1, 1, function(s, w) { server = s; wallet = w; - copayerPriv = c; server.createAddress({}, function(err, address) { helpers.createUtxos(server, wallet, _.range(10), function(utxos) { helpers.stubBlockExplorer(server, utxos); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.1, null, copayerPriv[0].privKey); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.1, null, TestData.copayers[0].privKey); async.eachSeries(_.range(10), function(i, next) { clock.tick(10000); server.createTx(txOpts, function(err, tx) { @@ -1211,19 +1206,18 @@ describe('Copay server', function() { describe('Notifications', function() { - var server, wallet, copayerPriv; + var server, wallet; beforeEach(function(done) { if (server) return done(); console.log('\tCreating TXS...'); - helpers.createAndJoinWallet(1, 1, function(s, w, c) { + helpers.createAndJoinWallet(1, 1, function(s, w) { server = s; wallet = w; - copayerPriv = c; server.createAddress({}, function(err, address) { helpers.createUtxos(server, wallet, helpers.toSatoshi(_.range(4)), function(utxos) { helpers.stubBlockExplorer(server, utxos); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.01, null, copayerPriv[0].privKey); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.01, null, TestData.copayers[0].privKey); async.eachSeries(_.range(3), function(i, next) { server.createTx(txOpts, function(err, tx) { should.not.exist(err); @@ -1253,8 +1247,8 @@ describe('Copay server', function() { server.getNotifications({ limit: 5, reverse: true, - maxTs: Date.now()/1000, - minTs: Date.now()/1000-1000, + maxTs: Date.now() / 1000, + minTs: Date.now() / 1000 - 1000, }, function(err, notifications) { should.not.exist(err); var types = _.pluck(notifications, 'type'); @@ -1436,16 +1430,15 @@ describe('Copay server', function() { describe('#removePendingTx', function() { - var server, wallet, copayerPriv, txp; + var server, wallet, txp; beforeEach(function(done) { - helpers.createAndJoinWallet(2, 3, function(s, w, c) { + helpers.createAndJoinWallet(2, 3, function(s, w) { server = s; wallet = w; - copayerPriv = c; server.createAddress({}, function(err, address) { helpers.createUtxos(server, wallet, [100, 200], function(utxos) { helpers.stubBlockExplorer(server, utxos); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', copayerPriv[0].privKey); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey); server.createTx(txOpts, function(err, tx) { server.getPendingTxs({}, function(err, txs) { txp = txs[0];