From 45033ec3a52594b6257a116fdae257f47147aab1 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 3 Mar 2015 18:48:10 -0300 Subject: [PATCH 1/4] splitted repo. test passing --- TODO | 13 - TODO.txt | 7 - bit-wallet/bit | 29 - bit-wallet/bit-address | 17 - bit-wallet/bit-addresses | 29 - bit-wallet/bit-balance | 18 - bit-wallet/bit-broadcast | 26 - bit-wallet/bit-confirm | 35 - bit-wallet/bit-create | 39 - bit-wallet/bit-export | 57 -- bit-wallet/bit-genkey | 21 - bit-wallet/bit-history | 42 - bit-wallet/bit-import | 27 - bit-wallet/bit-join | 25 - bit-wallet/bit-recreate | 23 - bit-wallet/bit-reject | 30 - bit-wallet/bit-rm | 31 - bit-wallet/bit-send | 44 -- bit-wallet/bit-sign | 63 -- bit-wallet/bit-status | 28 - bit-wallet/bit-txproposals | 26 - bit-wallet/cli-utils.js | 207 ----- bit-wallet/filestorage.js | 30 - bit-wallet/test/cli.js | 114 --- lib/client/airgapped.js | 62 -- lib/client/api.js | 565 -------------- lib/client/credentials.js | 169 ---- lib/client/index.js | 3 - lib/client/servercompromisederror.js | 6 - lib/client/verifier.js | 78 -- package.json | 2 +- test/integration/client.js | 1083 -------------------------- test/walletutils.js | 96 --- 33 files changed, 1 insertion(+), 3044 deletions(-) delete mode 100644 TODO delete mode 100644 TODO.txt delete mode 100755 bit-wallet/bit delete mode 100755 bit-wallet/bit-address delete mode 100755 bit-wallet/bit-addresses delete mode 100755 bit-wallet/bit-balance delete mode 100755 bit-wallet/bit-broadcast delete mode 100755 bit-wallet/bit-confirm delete mode 100755 bit-wallet/bit-create delete mode 100755 bit-wallet/bit-export delete mode 100755 bit-wallet/bit-genkey delete mode 100755 bit-wallet/bit-history delete mode 100755 bit-wallet/bit-import delete mode 100755 bit-wallet/bit-join delete mode 100755 bit-wallet/bit-recreate delete mode 100755 bit-wallet/bit-reject delete mode 100755 bit-wallet/bit-rm delete mode 100755 bit-wallet/bit-send delete mode 100755 bit-wallet/bit-sign delete mode 100755 bit-wallet/bit-status delete mode 100755 bit-wallet/bit-txproposals delete mode 100644 bit-wallet/cli-utils.js delete mode 100644 bit-wallet/filestorage.js delete mode 100644 bit-wallet/test/cli.js delete mode 100644 lib/client/airgapped.js delete mode 100644 lib/client/api.js delete mode 100644 lib/client/credentials.js delete mode 100644 lib/client/index.js delete mode 100644 lib/client/servercompromisederror.js delete mode 100644 lib/client/verifier.js delete mode 100644 test/integration/client.js delete mode 100644 test/walletutils.js diff --git a/TODO b/TODO deleted file mode 100644 index 9333c3c..0000000 --- a/TODO +++ /dev/null @@ -1,13 +0,0 @@ -ClientLib -- check derive address -- check change address -- remove storage from clientlib -- check prposal signature -- check xpriv keys correspond to wallet's network -- check secret format in join -- test raw tx have signatures -- add broadcast API to server -- enhance 'no network (internet)' error -- /Users/ematiu/devel/bitcore-wallet-service/node_modules/bitcore/lib/transaction/transaction.js:137 - throw new errors.Transaction.DustOutputs(); - and others TX errores now appear at broadcast time. diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index b2d817b..0000000 --- a/TODO.txt +++ /dev/null @@ -1,7 +0,0 @@ -- Check length < 100 for both wallet.name & copayer.name -- Proposal with spent input should be tagged as invalid or removed -- Cron job to broadcast accepted txps that failed to broadcast (we may need to track broadcast attempts for this). -- Payment protocol -- Automatically create ./db directory - -- check parameters for KEY at storage diff --git a/bit-wallet/bit b/bit-wallet/bit deleted file mode 100755 index c67ef8b..0000000 --- a/bit-wallet/bit +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env node - -var program = require('commander'); - -program - .version('0.0.1') - .command('create [username]', 'creates a wallet') - .command('join [username]', 'join a wallet') - .command('status', 'get wallet status') - .command('address', 'create a new address from server') - .command('addresses', 'list addresses') - .command('balance', 'wallet balance') - .command('send
[note]', 'send bitcoins') - .command('sign ', 'sign a transaction proposal') - .command('reject [reason]', 'reject a transaction proposal') - .command('broadcast ', 'broadcast a transaction proposal to the Bitcoin network') - .command('rm ', 'remove a transaction proposal') - .command('history', 'list of past incoming and outgoing transactions') - .command('export', 'export wallet critical data') - .command('import', 'import wallet critical data') - .command('confirm', 'show copayer\'s data for confirmation') - .command('recreate', 'recreate a wallet on a remove server given local infomation') - .command('txproposals', 'list transactions proposals') - .command('genkey', 'generates extended private key for later wallet usage') - .parse(process.argv); - -if (!program.args.length || !program.runningCommand) - program.help(); - diff --git a/bit-wallet/bit-address b/bit-wallet/bit-address deleted file mode 100755 index 057a25c..0000000 --- a/bit-wallet/bit-address +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env node - -var program = require('commander'); -var Client = require('../lib/client'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .parse(process.argv); - -var args = program.args; -utils.getClient(program, function (client) { - client.createAddress(function(err, x) { - utils.die(err); - console.log('* New Address %s ', x.address); - }); -}); diff --git a/bit-wallet/bit-addresses b/bit-wallet/bit-addresses deleted file mode 100755 index a746030..0000000 --- a/bit-wallet/bit-addresses +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env node - -var _ = require('lodash'); -var program = require('commander'); -var utils = require('./cli-utils'); -var Client = require('../lib/client'); -program = utils.configureCommander(program); - -program - .parse(process.argv); - -var args = program.args; - -utils.getClient(program, function (client) { - client.getMainAddresses({ - doNotVerify: true - }, function(err, x) { - utils.die(err); - - if (x.length > 0) { - console.log('* Addresses:'); - _.each(x, function(a) { - console.log(' ', a.address); - }); - } else { - console.log('* No addresses.'); - } - }); -}); diff --git a/bit-wallet/bit-balance b/bit-wallet/bit-balance deleted file mode 100755 index c32e8b2..0000000 --- a/bit-wallet/bit-balance +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env node - -var program = require('commander'); -var Client = require('../lib/client'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .parse(process.argv); - -var args = program.args; - -utils.getClient(program, function (client) { - client.getBalance(function(err, x) { - utils.die(err); - console.log('* Wallet balance %s (Locked %s)', utils.renderAmount(x.totalAmount), utils.renderAmount(x.lockedAmount) ); - }); -}); diff --git a/bit-wallet/bit-broadcast b/bit-wallet/bit-broadcast deleted file mode 100755 index a186d19..0000000 --- a/bit-wallet/bit-broadcast +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env node - -var _ = require('lodash'); -var program = require('commander'); -var Client = require('../lib/client'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .usage('[options] ') - .parse(process.argv); - -var args = program.args; -var txpid = args[0] || ''; - -utils.getClient(program, function (client) { - client.getTxProposals({}, function(err, txps) { - utils.die(err); - - var txp = utils.findOneTxProposal(txps, txpid); - client.broadcastTxProposal(txp, function(err, txp) { - utils.die(err); - console.log('Transaction Broadcasted: TXID: ' + txp.txid); - }); - }); -}); diff --git a/bit-wallet/bit-confirm b/bit-wallet/bit-confirm deleted file mode 100755 index 9d3002e..0000000 --- a/bit-wallet/bit-confirm +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env node - -var _ = require('lodash'); -var program = require('commander'); -var Client = require('../lib/client'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .parse(process.argv); - -utils.getClient(program, function (client) { - client.getStatus(function(err, x) { - utils.die(err); - - if (x.wallet.n == 1) { - console.log('Confirmations only work on shared wallets'); - process.exit(1); - } - console.log('\n To be sure that no copayer has joined this wallet more than once, you can asked them for their confirmation number. They can get theirs by running the bit-confirm command.'); - console.log('\n * Copayer confirmation IDs:'); - - var myConfirmationId; - _.each(x.wallet.copayers, function(x) { - var confirmationId = utils.confirmationId(x); - if (x.id != client.credentials.copayerId) - console.log('\t\t* %s : %s', x.name, confirmationId); - else - myConfirmationId = confirmationId; - }); - - console.log('\t\t---'); - console.log('\t\tYour confirmation ID: %s', myConfirmationId); - }); -}); diff --git a/bit-wallet/bit-create b/bit-wallet/bit-create deleted file mode 100755 index 6fb2bdd..0000000 --- a/bit-wallet/bit-create +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env node - -var _ = require('lodash'); -var program = require('commander'); -var ClientLib = require('../lib/client'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .option('-t, --testnet', 'Create a Testnet Wallet') - .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.testnet ? 'testnet' : 'livenet'; - -var mn; -try { - mn = utils.parseMN(args[1]); -} catch (ex) { - utils.die(ex); -} - -utils.getClient(program, function (client) { - client.createWallet(walletName, copayerName, mn[0], mn[1], network, function(err, secret) { - utils.die(err); - console.log(' * ' + _.capitalize(network) + ' Wallet Created.'); - utils.saveClient(program, client, function () { - if (secret) { - console.log(' - Secret to share:\n\t' + secret); - } - }); - }); -}); diff --git a/bit-wallet/bit-export b/bit-wallet/bit-export deleted file mode 100755 index 3b16d47..0000000 --- a/bit-wallet/bit-export +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env node - -var program = require('commander'); -var qr = require('qr-image'); -var fs = require('fs'); -var _ = require('lodash'); - -var Client = require('../lib/client'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .option('-a, --access [level]', 'access privileges for exported data (full, readwrite, readonly)', 'full') - .option('-q, --qr', 'export a QR code') - .option('-o, --output [filename]', 'output file'); - -program.on('--help', function() { - console.log(' Access Levels:'); - console.log(''); - console.log(' readonly : allows to read wallet data: balance, tx proposals '); - console.log(' readwrite: + allows to create addresses and unsigned tx prposals '); - console.log(' full : + allows sign tx prposals '); - console.log(''); -}); - -program - .parse(process.argv); - -var args = program.args; -var client = utils.getClient(program); - -if (!_.contains(['full', 'readwrite', 'readonly'], program.access)) { - program.help(); -} - -var msg = ' Access Level: ' + program.access; - -client.export({ - access: program.access -}, function(err, x) { - utils.die(err); - if (program.qr) { - var filename = program.file + '.svg'; - var qr_svg = qr.image(x, { - type: 'svg' - }); - qr_svg.pipe(fs.createWriteStream(filename)); - console.log('Wallet Critical Data: exported to %s. %s\n',filename, msg); - } else { - if (program.output) { - fs.writeFileSync(program.output, x); - console.log('Wallet Critical Data saved at %s. %s\n', program.output, msg); - } else { - console.log('Wallet Critical Data (%s)\n%s', msg, x); - } - } -}); diff --git a/bit-wallet/bit-genkey b/bit-wallet/bit-genkey deleted file mode 100755 index fe3102d..0000000 --- a/bit-wallet/bit-genkey +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env node - -var _ = require('lodash'); -var program = require('commander'); -var Client = require('../lib/client'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .option('-t, --testnet', 'Create a Testnet Extended Private Key') - .option('-n, --nopasswd [level]', 'Set access for no password usage: none(default), readonly, readwrite, full', 'none') - .parse(process.argv); - -var args = program.args; -var client = utils.getClient(program); -var network = program.testnet ? 'testnet' : 'livenet'; - -client.generateKey(network, function(err) { - utils.die(err); - console.log(' * ' + _.capitalize(network) + ' Extended Private Key Created.'); -}); diff --git a/bit-wallet/bit-history b/bit-wallet/bit-history deleted file mode 100755 index f2bb8db..0000000 --- a/bit-wallet/bit-history +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env node - -var _ = require('lodash'); -var fs = require('fs'); -var moment = require('moment'); -var program = require('commander'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .parse(process.argv); - -var args = program.args; - -utils.getClient(program, function (client) { - client.getTxHistory({}, function (err, txs) { - if (_.isEmpty(txs)) - return; - - console.log("* TX History:") - - _.each(txs, function(tx) { - var time = moment(tx.time * 1000).fromNow(); - var amount = utils.renderAmount(tx.amount); - var confirmations = tx.confirmations || 0; - var proposal = tx.proposalId ? '["' + tx.message + '" by ' + tx.creatorName + '] ' : ''; - switch (tx.action) { - case 'received': - direction = '<='; - break; - case 'moved': - direction = '=='; - break; - case 'sent': - direction = '=>'; - break; - } - - console.log("\t%s: %s %s %s %s(%s confirmations)", time, direction, tx.action, amount, proposal, confirmations); - }); - }); -}); diff --git a/bit-wallet/bit-import b/bit-wallet/bit-import deleted file mode 100755 index 2105a1f..0000000 --- a/bit-wallet/bit-import +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env node -var program = require('commander'); - -var Client = require('../lib/client'); -var fs = require('fs'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - - -program - .version('0.0.1') - .option('-n, --nopasswd [level]', 'Set access for no password usage: none(default), readonly, readwrite, full', 'none') - .usage('import [options] ') - .parse(process.argv); - -var args = program.args; - -if (!args[0]) - program.help(); - -var client = utils.getClient(program); -var str = fs.readFileSync(args[0]); - -client.import(str, function(err, x) { - utils.die(err); - console.log('Wallet Imported. Access level:' + x); -}); diff --git a/bit-wallet/bit-join b/bit-wallet/bit-join deleted file mode 100755 index 1429b38..0000000 --- a/bit-wallet/bit-join +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env node - -var program = require('commander'); -var Client = require('../lib/client'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .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; - -utils.getClient(program, function (client) { - client.joinWallet(secret, copayerName, function(err, wallet) { - utils.die(err); - console.log(' * Wallet Joined.', wallet.name); - utils.saveClient(program, client, function () {}); - }); -}); diff --git a/bit-wallet/bit-recreate b/bit-wallet/bit-recreate deleted file mode 100755 index dec0a17..0000000 --- a/bit-wallet/bit-recreate +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env node - -var _ = require('lodash'); -var program = require('commander'); -var ClientLib = require('../lib/client'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .usage('[options] walletname') - .description('Creates a wallet on the remove server given the local information') - .parse(process.argv); - -var args = program.args; -if (!args[0]) - program.help(); - -var walletName = args[0]; -var client = utils.getClient(program); -client.reCreateWallet(walletName, function(err) { - utils.die(err); - console.log(' * Wallet Created.'); -}); diff --git a/bit-wallet/bit-reject b/bit-wallet/bit-reject deleted file mode 100755 index bdf1fd6..0000000 --- a/bit-wallet/bit-reject +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env node - -var _ = require('lodash'); -var program = require('commander'); -var Client = require('../lib/client'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .usage('[options] [reason]') - .parse(process.argv); - -var args = program.args; -var txpid = args[0] || ''; -var reason = args[1] || ''; - -utils.getClient(program, function (client) { - client.getTxProposals({}, function(err, txps) { - utils.die(err); - - var txp = utils.findOneTxProposal(txps, txpid); - client.rejectTxProposal(txp, reason, function(err, tx) { - utils.die(err); - if (tx.status == 'rejected') - console.log('Transaction finally rejected.'); - else - console.log('Transaction rejected by you.'); - }); - }); -}); diff --git a/bit-wallet/bit-rm b/bit-wallet/bit-rm deleted file mode 100755 index b0472aa..0000000 --- a/bit-wallet/bit-rm +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env node - -var _ = require('lodash'); -var program = require('commander'); -var Client = require('../lib/client'); -var utils = require('./cli-utils'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .usage('[options] ') - .parse(process.argv); - -var args = program.args; -var txpid = args[0] || ''; - -utils.getClient(program, function (client) { - client.getTxProposals({}, function(err, txps) { - utils.die(err); - - if (program.verbose) - console.log('* Raw Server Response:\n', txps); //TODO - - var txp = utils.findOneTxProposal(txps, txpid); - client.removeTxProposal(txp, function(err) { - utils.die(err); - - console.log('Transaction removed.'); - }); - }); -}); diff --git a/bit-wallet/bit-send b/bit-wallet/bit-send deleted file mode 100755 index 780eba5..0000000 --- a/bit-wallet/bit-send +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env node - -var program = require('commander'); -var Client = require('../lib/client'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .usage('[options]
[note]') - .description('Create a proposal for sending bitcoins to a destination address.\n The amount can be specified in bit, btc or sat (the default).'); - -program.on('--help', function(){ - console.log(' Examples:'); - console.log(''); - console.log(' $ bit-send n2HRFgtoihgAhx1qAEXcdBMjoMvAx7AcDc 500bit'); - console.log(' $ bit-send mgWeRvUC6d1LRPKtdDbvYEpaUEmApS4XrY 0.2btc "dinner with friends"'); - console.log(''); -}); -program.parse(process.argv); - -var args = program.args; -if (!args[0] || !args[1]) - program.help(); - -var address = args[0]; -var amount; -try { - amount = utils.parseAmount(args[1]); -} catch (ex) { - utils.die(ex); -} -var note = args[2]; - -utils.getClient(program, function (client) { - client.sendTxProposal({ - toAddress: address, - amount: amount, - message: note - }, function(err, x) { - utils.die(err); - console.log(' * Tx created: ID %s [%s] RequiredSignatures:', - x.id, x.status, x.requiredSignatures); - }); -}); diff --git a/bit-wallet/bit-sign b/bit-wallet/bit-sign deleted file mode 100755 index b11c32d..0000000 --- a/bit-wallet/bit-sign +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env node - -var _ = require('lodash'); -var fs = require('fs'); -var program = require('commander'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .usage('[options] ') - .option('-i, --input [filename]', 'use signatures from file') - .option('-o, --output [filename]', 'write signatures to file') - .parse(process.argv); - -var args = program.args; -var txpid = args[0] || ''; - -function end(client, txp) { - if (program.output) { - client.getSignatures(txp, function(err, signatures) { - utils.die(err); - var out = { - id: txp.id, - signatures: signatures, - }; - fs.writeFileSync(program.output, JSON.stringify(out)); - console.log('Signatures written to file.'); - }); - } else { - - if (program.input) { - - var infile = JSON.parse(fs.readFileSync(program.input)); - if (infile.id != txp.id) - utils.die('Signatures does not match Transaction') - - txp.signatures = infile.signatures; - } - - client.signTxProposal(txp, function(err, tx) { - utils.die(err); - if (tx.status == 'broadcasted') - console.log('Transaction Broadcasted: TXID: ' + tx.txid); - else - console.log('Transaction signed by you.'); - }); - } - -}; - -utils.getClient(program, function (client) { - if (program.input && program.output) { - var inFile = JSON.parse(fs.readFileSync(program.input)); - end(client, inFile.txps[0]); - } else { - client.getTxProposals({}, function(err, txps) { - utils.die(err); - var txp = utils.findOneTxProposal(txps, txpid); - utils.die(err); - end(client, txp); - }); - } -}); diff --git a/bit-wallet/bit-status b/bit-wallet/bit-status deleted file mode 100755 index 4f29ce4..0000000 --- a/bit-wallet/bit-status +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env node - -var _ = require('lodash'); -var program = require('commander'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .parse(process.argv); - -var args = program.args; -utils.getClient(program, function (client) { - client.getStatus(function(err, res) { - utils.die(err); - - var x = res.wallet; - console.log('* Wallet %s [%s]: %d-of-%d %s ', x.name, x.network, x.m, x.n, x.status); - - if (x.status != 'complete') - console.log(' Missing copayers:', x.n - x.copayers.length); - console.log('* Copayers:', _.pluck(x.copayers,'name').join(', ')); - - var x = res.balance; - console.log('* Balance %s (Locked: %s)', utils.renderAmount(x.totalAmount), utils.renderAmount(x.lockedAmount)); - - utils.renderTxProposals(res.pendingTxps); - }); -}); diff --git a/bit-wallet/bit-txproposals b/bit-wallet/bit-txproposals deleted file mode 100755 index 2bcef75..0000000 --- a/bit-wallet/bit-txproposals +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env node - -var _ = require('lodash'); -var fs = require('fs'); -var program = require('commander'); -var utils = require('./cli-utils'); -program = utils.configureCommander(program); - -program - .option('-o, --output [filename]', 'write tx to output file for offline signing') - .parse(process.argv); - -var args = program.args; - -utils.getClient(program, function (client) { - client.getTxProposals({forAirGapped: !!program.output}, function (err, res) { - utils.die(err); - - if (program.output) { - fs.writeFileSync(program.output, JSON.stringify(res)); - console.log(' * Tx proposals saved to: %s\n', program.output); - } else { - utils.renderTxProposals(res); - } - }); -}); diff --git a/bit-wallet/cli-utils.js b/bit-wallet/cli-utils.js deleted file mode 100644 index b621c24..0000000 --- a/bit-wallet/cli-utils.js +++ /dev/null @@ -1,207 +0,0 @@ -var _ = require('lodash'); -var Client = require('../lib/client'); -var FileStorage = require('./filestorage'); -var read = require('read') -var log = require('npmlog'); - -var Utils = function() {}; - -var die = Utils.die = function(err) { - if (err) { - if (err.code && err.code == 'ECONNREFUSED') { - console.error('Could not connect to Bicore Wallet Service'); - } else { - console.error(err); - } - process.exit(1); - } -}; - -Utils.parseMN = function(text) { - if (!text) throw new Error('No m-n parameter'); - - var regex = /^(\d+)(-|of|-of-)?(\d+)$/i; - var match = regex.exec(text.trim()); - - if (!match || match.length === 0) throw new Error('Invalid m-n parameter'); - - var m = parseInt(match[1]); - var n = parseInt(match[3]); - if (m > n) throw new Error('Invalid m-n parameter'); - - return [m, n]; -}; - - -Utils.shortID = function(id) { - return id.substr(id.length - 4); -}; - -Utils.confirmationId = function(copayer) { - return parseInt(copayer.xPubKeySignature.substr(-4), 16).toString().substr(-4); -} - -Utils.getClient = function(args, cb) { - var storage = new FileStorage({ - filename: args.file || process.env['BIT_FILE'], - }); - var client = new Client({ - baseUrl: args.host || process.env['BIT_HOST'], - verbose: args.verbose, - }); - storage.load(function(err, walletData) { - if (err && err.code != 'ENOENT') die(err); - if (!walletData) return cb(client); - - client.import(walletData); - client.openWallet(function(err, justCompleted) { - if (client.isComplete() && justCompleted) { - Utils.saveClient(args, client, function() { - log.info('Your wallet has just been completed. Please backup your wallet file or use the export command.'); - return cb(client); - }); - } else { - return cb(client); - } - }); - }); -}; - -Utils.saveClient = function(args, client, cb) { - var storage = new FileStorage({ - filename: args.file || process.env['BIT_FILE'], - }); - var str = client.export(); - storage.save(str, function(err) { - die(err); - return cb(); - }); -}; - -// var setPassword; -// c.on('needPassword', function(cb) { -// if (args.password) { -// return cb(args.password); -// } else { -// if (setPassword) -// return cb(setPassword); - -// read({ -// prompt: 'Password for ' + args.file + ' : ', -// silent: true -// }, function(er, password) { -// setPassword = password; -// return cb(password); -// }) -// } -// }); - -// c.on('needNewPassword', function(cb) { -// if (args.password) { -// return cb(args.password); -// } else { -// read({ -// prompt: 'New Password: ', -// silent: true -// }, function(er, password) { -// return cb(password); -// }) -// } -// }); - - - -Utils.findOneTxProposal = function(txps, id) { - var matches = _.filter(txps, function(tx) { - return _.endsWith(Utils.shortID(tx.id), id); - }); - - if (!matches.length) - Utils.die('Could not find TX Proposal:' + id); - - if (matches.length > 1) - Utils.die('More than one TX Proposals match:' + id + ' : ' + _.map(matches, function(tx) { - return tx.id; - }).join(' '));; - - return matches[0]; -}; - -Utils.UNITS = { - 'btc': 100000000, - 'bit': 100, - 'sat': 1, -}; - -Utils.parseAmount = function(text) { - if (!_.isString(text)) - text = text.toString(); - - var regex = '^(\\d*(\\.\\d{0,8})?)\\s*(' + _.keys(Utils.UNITS).join('|') + ')?$'; - var match = new RegExp(regex, 'i').exec(text.trim()); - - if (!match || match.length === 0) throw new Error('Invalid amount'); - - var amount = parseFloat(match[1]); - if (!_.isNumber(amount) || _.isNaN(amount)) throw new Error('Invalid amount'); - - var unit = (match[3] || 'sat').toLowerCase(); - var rate = Utils.UNITS[unit]; - if (!rate) throw new Error('Invalid unit') - - var amountSat = parseFloat((amount * rate).toPrecision(12)); - if (amountSat != Math.round(amountSat)) throw new Error('Invalid amount'); - - return amountSat; -}; - -Utils.configureCommander = function(program) { - program - .version('0.0.1') - .option('-f, --file [filename]', 'Wallet file', process.env['HOME'] + '/.bit.dat') - .option('-h, --host [host]', 'Bitcore Wallet Service URL (eg: http://localhost:3001/copay/api') - .option('-v, --verbose', 'be verbose') - - return program; -}; - -Utils.renderAmount = function(amount) { - var unit = process.env.BIT_UNIT || 'bit'; - if (unit === 'SAT') { - // Do nothing - } else if (process.env.BIT_UNIT === 'btc') { - amount = amount / 1e8; - } else { - amount = amount / 100; - } - amount = (parseFloat(amount.toPrecision(12))); - return amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + ' ' + unit; -}; - -Utils.renderTxProposals = function(txps) { - if (_.isEmpty(txps)) - return; - - console.log("* TX Proposals:") - - _.each(txps, function(x) { - var missingSignatures = x.requiredSignatures - _.filter(_.values(x.actions), function(a) { - return a.type == 'accept'; - }).length; - console.log("\t%s [\"%s\" by %s] %s => %s", Utils.shortID(x.id), x.message, x.creatorName, Utils.renderAmount(x.amount), x.toAddress); - - if (!_.isEmpty(x.actions)) { - console.log('\t\tActions: ', _.map(x.actions, function(a) { - return a.copayerName + ' ' + (a.type == 'accept' ? '✓' : '✗') + (a.comment ? ' (' + a.comment + ')' : ''); - }).join('. ')); - } - if (missingSignatures > 0) { - console.log('\t\tMissing signatures: ' + missingSignatures); - } else { - console.log('\t\tReady to broadcast'); - } - }); - -}; - -module.exports = Utils; diff --git a/bit-wallet/filestorage.js b/bit-wallet/filestorage.js deleted file mode 100644 index 021a774..0000000 --- a/bit-wallet/filestorage.js +++ /dev/null @@ -1,30 +0,0 @@ -var fs = require('fs') - -function FileStorage(opts) { - if (!opts.filename) { - throw new Error('Please set wallet filename'); - } - this.filename = opts.filename; - this.fs = opts.fs || fs; -}; - -FileStorage.prototype.getName = function() { - return this.filename; -}; - -FileStorage.prototype.save = function(data, cb) { - this.fs.writeFile(this.filename, JSON.stringify(data), cb); -}; - -FileStorage.prototype.load = function(cb) { - this.fs.readFile(this.filename, 'utf8', function(err, data) { - if (err) return cb(err); - try { - data = JSON.parse(data); - } catch (e) {} - return cb(null, data); - }); -}; - - -module.exports = FileStorage; diff --git a/bit-wallet/test/cli.js b/bit-wallet/test/cli.js deleted file mode 100644 index 1d9e320..0000000 --- a/bit-wallet/test/cli.js +++ /dev/null @@ -1,114 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var chai = require('chai'); -var sinon = require('sinon'); -var should = chai.should(); -var CliUtils = require('../cli-utils'); - -describe('CliUtils', function() { - describe('#parseMN', function() { - it('should successfully parse m & n', function() { - var texts = { - '1-1': [1, 1], - '1-of-1': [1, 1], - '1of1': [1, 1], - '1-OF-2': [1, 2], - '1OF2': [1, 2], - ' 2-2': [2, 2], - '2-3 ': [2, 3], - '10-10': [10, 10], - '10-of-10': [10, 10], - }; - _.each(texts, function(expected, text) { - var result = CliUtils.parseMN(text); - result.should.deep.equal(expected); - }); - }); - it('should fail to parse incorrect m & n', function() { - var texts = [ - '', - ' ', - '1', - 'x-1', - '1-x', - 'of-1-1', - '2-2-of', - '1-1-1', - ' 1_1 ', - '2-1', - '2-of-1', - '-1-2', - '1--2', - 'x-of-2', - ]; - _.each(texts, function(text) { - var valid = true; - try { - CliUtils.parseMN(text); - } catch (e) { - valid = false; - } - valid.should.be.false; - }); - }); - }); - - describe('#parseAmount', function() { - it('should successfully parse amounts', function() { - var texts = { - '1': 1, - '0': 0, - '1.': 1, - '000000.0000': 0, - '123': 123, - '123sat': 123, - '123 sat': 123, - '00123 sat': 123, - '1.23bit': 123, - '1.23 bit': 123, - '0 bit': 0, - '.45bit': 45, - '1btc': 100000000, - ' 1btc': 100000000, - '9999btc': 999900000000, - '0.00000001btc': 1, - '00000.00000001BTC': 1, - '0.00000001 BTC': 1, - '0.123btc': 12300000, - '0.123 bTc': 12300000, - }; - _.each(texts, function(satoshi, text) { - var amount = CliUtils.parseAmount(text); - amount.should.equal(satoshi); - }); - }); - it('should fail to parse incorrect amounts', function() { - var texts = [ - '', - ' ', - 'btc', - '1satoshi', - 'no-number', - '-3', - '1 b t c', - 'btc1', - 'btc 1', - '1,234', - '0.000000001btc', - '0.1sat', - '0.123bit', - '2.000000009btc', - ]; - _.each(texts, function(text) { - var valid = true; - try { - CliUtils.parseAmount(text); - } catch (e) { - valid = false; - } - valid.should.be.false; - }); - }); - }); -}); diff --git a/lib/client/airgapped.js b/lib/client/airgapped.js deleted file mode 100644 index 8bde1e0..0000000 --- a/lib/client/airgapped.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var $ = require('preconditions').singleton(); -var util = require('util'); -var async = require('async'); -var log = require('npmlog'); -var events = require('events'); -log.debug = log.verbose; -var Bitcore = require('bitcore') - -var Credentials = require('./credentials'); -var WalletUtils = require('../walletutils'); -var Verifier = require('./verifier'); -var ServerCompromisedError = require('./servercompromisederror'); -var ClientError = require('../clienterror'); - -function AirGapped(opts) { - this.verbose = !!opts.verbose; - if (this.verbose) { - log.level = 'debug'; - } else { - log.level = 'info'; - } - this.credentials = Credentials.create(opts.network || 'livenet'); -}; - -util.inherits(AirGapped, events.EventEmitter); - -AirGapped.prototype.getSeed = function() { - return { - xPubKey: this.credentials.xPubKey, - requestPrivKey: this.credentials.requestPrivKey, - }; -}; - -AirGapped.prototype.signTxProposal = function(txp, encryptedPkr, m, n) { - var self = this; - - var publicKeyRing; - try { - publicKeyRing = JSON.parse(WalletUtils.decryptMessage(encryptedPkr, self.credentials.personalEncryptingKey)); - } catch (ex) { - console.log(ex); - throw new Error('Could not decrypt public key ring'); - } - - if (!_.isArray(publicKeyRing) || publicKeyRing.length != n) { - throw new Error('Invalid public key ring'); - } - - self.credentials.m = m; - self.credentials.n = n; - self.credentials.addPublicKeyRing(publicKeyRing); - - if (!Verifier.checkTxProposal(self.credentials, txp)) { - throw new Error('Fake transaction proposal'); - } - return WalletUtils.signTxp(txp, self.credentials.xPrivKey); -}; - -module.exports = AirGapped; diff --git a/lib/client/api.js b/lib/client/api.js deleted file mode 100644 index 82fbcee..0000000 --- a/lib/client/api.js +++ /dev/null @@ -1,565 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var $ = require('preconditions').singleton(); -var util = require('util'); -var async = require('async'); -var log = require('npmlog'); -var request = require('request') -var events = require('events'); -log.debug = log.verbose; -var Bitcore = require('bitcore') -var sjcl = require('sjcl'); - -var Credentials = require('./credentials'); -var WalletUtils = require('../walletutils'); -var Verifier = require('./verifier'); -var ServerCompromisedError = require('./servercompromisederror'); -var ClientError = require('../clienterror'); - -var BASE_URL = 'http://localhost:3001/copay/api'; -var WALLET_ENCRYPTION_OPTS = { - iter: 5000 -}; - -function _encryptMessage(message, encryptingKey) { - if (!message) return null; - return WalletUtils.encryptMessage(message, encryptingKey); -}; - -function _decryptMessage(message, encryptingKey) { - if (!message) return ''; - try { - return WalletUtils.decryptMessage(message, encryptingKey); - } catch (ex) { - return ''; - } -}; - -function _processTxps(txps, encryptingKey) { - if (!txps) return; - _.each([].concat(txps), function(txp) { - txp.encryptedMessage = txp.message; - txp.message = _decryptMessage(txp.message, encryptingKey); - _.each(txp.actions, function(action) { - action.comment = _decryptMessage(action.comment, encryptingKey); - }); - }); -}; - -function _parseError(body) { - if (_.isString(body)) { - try { - body = JSON.parse(body); - } catch (e) { - body = { - error: body - }; - } - } - var ret; - if (body.code) { - ret = new ClientError(body.code, body.message); - } else { - ret = { - code: 'ERROR', - error: body.error || 'There was an unknown error processing the request', - }; - } - log.error(ret); - return ret; -}; - -function _signRequest(method, url, args, privKey) { - var message = method.toLowerCase() + '|' + url + '|' + JSON.stringify(args); - return WalletUtils.signMessage(message, privKey); -}; - -function API(opts) { - opts = opts || {}; - - this.verbose = !!opts.verbose; - this.request = opts.request || request; - this.baseUrl = opts.baseUrl || BASE_URL; - this.basePath = this.baseUrl.replace(/http.?:\/\/[a-zA-Z0-9:-]*\//, '/'); - if (this.verbose) { - log.level = 'debug'; - } else { - log.level = 'info'; - } -}; - -util.inherits(API, events.EventEmitter); - -API.prototype.seedFromExtendedPrivateKey = function(xPrivKey) { - this.credentials = Credentials.fromExtendedPrivateKey(xPrivKey); -}; - -API.prototype.seedFromAirGapped = function(seed) { - this.credentials = Credentials.fromExtendedPublicKey(seed.xPubKey, seed.requestPrivKey); -}; - -/** - * export - * - * @param opts - * @param opts.compressed - * @param opts.password - */ -API.prototype.export = function(opts) { - $.checkState(this.credentials); - - opts = opts || {}; - - var output; - if (opts.compressed) { - output = this.credentials.exportCompressed(); - } else { - output = JSON.stringify(this.credentials.toObj()); - } - - if (opts.password) { - output = sjcl.encrypt(opts.password, output, WALLET_ENCRYPTION_OPTS); - } - - return output; -} - - -/** - * export - * - * @param opts - * @param opts.compressed - * @param opts.password - */ -API.prototype.import = function(str, opts) { - opts = opts || {}; - - var input = str; - if (opts.password) { - try { - input = sjcl.decrypt(opts.password, input); - } catch (ex) { - throw new Error('Incorrect password'); - } - } - - var credentials; - try { - if (opts.compressed) { - credentials = Credentials.importCompressed(input); - // TODO: complete missing fields that live on the server only such as: walletId, walletName, copayerName - } else { - credentials = Credentials.fromObj(JSON.parse(input)); - } - } catch (ex) { - throw new Error('Error importing from source'); - } - this.credentials = credentials; -}; - -API.prototype.toString = function(password) { - $.checkState(this.credentials); - return this.credentials.toObject(); -}; - -API.prototype.fromString = function(str) { - this.credentials = Credentials.fromObject(str); -}; - -API.prototype._doRequest = function(method, url, args, cb) { - $.checkState(this.credentials); - - var reqSignature; - - if (this.credentials.requestPrivKey) { - reqSignature = _signRequest(method, url, args, this.credentials.requestPrivKey); - } - - var absUrl = this.baseUrl + url; - var args = { - // relUrl: only for testing with `supertest` - relUrl: this.basePath + url, - headers: { - 'x-identity': this.credentials.copayerId, - 'x-signature': reqSignature, - }, - method: method, - url: absUrl, - body: args, - json: true, - }; - - log.verbose('Request Args', util.inspect(args, { - depth: 10 - })); - this.request(args, function(err, res, body) { - log.verbose(util.inspect(body, { - depth: 10 - })); - if (err) return cb(err); - - if (res.statusCode != 200) { - return cb(_parseError(body)); - } - - return cb(null, body, res.header); - }); -}; - - -API.prototype._doPostRequest = function(url, args, cb) { - return this._doRequest('post', url, args, cb); -}; - -API.prototype._doGetRequest = function(url, cb) { - return this._doRequest('get', url, {}, cb); -}; - -API.prototype._doDeleteRequest = function(url, cb) { - return this._doRequest('delete', url, {}, cb); -}; - -API.prototype._doJoinWallet = function(walletId, walletPrivKey, xPubKey, copayerName, cb) { - var args = { - walletId: walletId, - name: copayerName, - xPubKey: xPubKey, - xPubKeySignature: WalletUtils.signMessage(xPubKey, walletPrivKey), - }; - var url = '/v1/wallets/' + walletId + '/copayers'; - this._doPostRequest(url, args, function(err, body) { - if (err) return cb(err); - return cb(null, body.wallet); - }); -}; - -API.prototype.isComplete = function() { - return this.credentials && this.credentials.isComplete(); -}; - -/** - * Opens a wallet and tries to complete the public key ring. - * @param {Function} cb - Returns an error and a flag indicating that the wallet has just been completed and needs to be persisted - */ -API.prototype.openWallet = function(cb) { - $.checkState(this.credentials); - - var self = this; - - if (self.credentials.isComplete()) return cb(null, false); - - self._doGetRequest('/v1/wallets/', function(err, ret) { - if (err) return cb(err); - var wallet = ret.wallet; - - if (wallet.status != 'complete') return cb('Wallet Incomplete'); - - if (!!self.credentials.walletPrivKey) { - if (!Verifier.checkCopayers(self.credentials, wallet.copayers)) { - return cb(new ServerCompromisedError( - 'Copayers in the wallet could not be verified to have known the wallet secret')); - } - } else { - log.warn('Could not perform verification of other copayers in the wallet'); - } - - self.credentials.addPublicKeyRing(_.pluck(wallet.copayers, 'xPubKey')); - if (!self.credentials.hasWalletInfo()) { - var me = _.find(wallet.copayers, { - id: self.credentials.copayerId - }); - self.credentials.addWalletInfo(wallet.id, wallet.name, wallet.m, wallet.n, null, me.name); - } - - return cb(null, true); - }); -}; - -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 (!self.credentials) { - log.info('Generating new keys'); - self.credentials = Credentials.create(network); - } else { - log.info('Using existing keys'); - } - - $.checkState(network == self.credentials.network); - - var walletPrivKey = new Bitcore.PrivateKey(); - var args = { - name: walletName, - m: m, - n: n, - pubKey: walletPrivKey.toPublicKey().toString(), - network: network, - }; - - self._doPostRequest('/v1/wallets/', args, function(err, body) { - if (err) return cb(err); - - var walletId = body.walletId; - - var secret = WalletUtils.toSecret(walletId, walletPrivKey, network); - self.credentials.addWalletInfo(walletId, walletName, m, n, walletPrivKey.toString(), copayerName); - - self._doJoinWallet(walletId, walletPrivKey, self.credentials.xPubKey, copayerName, - function(err, wallet) { - if (err) return cb(err); - return cb(null, n > 1 ? secret : null); - }); - }); -}; - -API.prototype.joinWallet = function(secret, copayerName, cb) { - var self = this; - - try { - var secretData = WalletUtils.fromSecret(secret); - } catch (ex) { - return cb(ex); - } - - if (!self.credentials) { - self.credentials = Credentials.create(secretData.network); - } - - self._doJoinWallet(secretData.walletId, secretData.walletPrivKey, self.credentials.xPubKey, copayerName, - function(err, wallet) { - if (err) return cb(err); - self.credentials.addWalletInfo(wallet.id, wallet.name, wallet.m, wallet.n, secretData.walletPrivKey, copayerName); - return cb(null, wallet); - }); -}; - -API.prototype.getStatus = function(cb) { - $.checkState(this.credentials); - var self = this; - - self._doGetRequest('/v1/wallets/', function(err, result) { - _processTxps(result.pendingTxps, self.credentials.sharedEncryptingKey); - return cb(err, result); - }); -}; - -/** - * send - * - * @param opts - * @param opts.toAddress - * @param opts.amount - * @param opts.message - */ -API.prototype.sendTxProposal = function(opts, cb) { - $.checkState(this.credentials && this.credentials.isComplete()); - $.checkArgument(opts); - $.shouldBeNumber(opts.amount); - - var self = this; - - var args = { - toAddress: opts.toAddress, - amount: opts.amount, - message: _encryptMessage(opts.message, self.credentials.sharedEncryptingKey), - }; - var hash = WalletUtils.getProposalHash(args.toAddress, args.amount, args.message); - args.proposalSignature = WalletUtils.signMessage(hash, self.credentials.requestPrivKey); - log.debug('Generating & signing tx proposal hash -> Hash: ', hash, ' Signature: ', args.proposalSignature); - - self._doPostRequest('/v1/txproposals/', args, function(err, txp) { - if (err) return cb(err); - return cb(null, txp); - }); -}; - -API.prototype.createAddress = function(cb) { - $.checkState(this.credentials && this.credentials.isComplete()); - - var self = this; - - self._doPostRequest('/v1/addresses/', {}, function(err, address) { - if (err) return cb(err); - if (!Verifier.checkAddress(self.credentials, address)) { - return cb(new ServerCompromisedError('Server sent fake address')); - } - - return cb(null, address); - }); -}; - -/* - * opts.doNotVerify - */ - -API.prototype.getMainAddresses = function(opts, cb) { - $.checkState(this.credentials && this.credentials.isComplete()); - - var self = this; - - self._doGetRequest('/v1/addresses/', function(err, addresses) { - if (err) return cb(err); - - if (!opts.doNotVerify) { - var fake = _.any(addresses, function(address) { - return !Verifier.checkAddress(self.credentials, address); - }); - if (fake) - return cb(new ServerCompromisedError('Server sent fake address')); - } - return cb(null, addresses); - }); -}; - -API.prototype.getBalance = function(cb) { - $.checkState(this.credentials && this.credentials.isComplete()); - var self = this; - - self._doGetRequest('/v1/balance/', cb); -}; - - -/** - * - * opts.doNotVerify - * opts.forAirGapped - * @return {undefined} - */ - -API.prototype.getTxProposals = function(opts, cb) { - $.checkState(this.credentials && this.credentials.isComplete()); - - var self = this; - - self._doGetRequest('/v1/txproposals/', function(err, txps) { - if (err) return cb(err); - - _processTxps(txps, self.credentials.sharedEncryptingKey); - - var fake = _.any(txps, function(txp) { - return (!opts.doNotVerify && !Verifier.checkTxProposal(self.credentials, txp)); - }); - - if (fake) - return cb(new ServerCompromisedError('Server sent fake transaction proposal')); - - var result; - if (opts.forAirGapped) { - result = { - txps: JSON.parse(JSON.stringify(txps)), - publicKeyRing: WalletUtils.encryptMessage(JSON.stringify(self.credentials.publicKeyRing), self.credentials.personalEncryptingKey), - m: self.credentials.m, - n: self.credentials.n, - }; - } else { - result = txps; - } - - return cb(null, result); - }); -}; - -API.prototype.getSignatures = function(txp, cb) { - $.checkState(this.credentials && this.credentials.isComplete()); - $.checkArgument(txp.creatorId); - - var self = this; - - if (!self.credentials.canSign()) - return cb('You do not have the required keys to sign transactions'); - - if (!Verifier.checkTxProposal(self.credentials, txp)) { - return cb(new ServerCompromisedError('Transaction proposal is invalid')); - } - - return cb(null, WalletUtils.signTxp(txp, self.credentials.xPrivKey)); -}; - -API.prototype.signTxProposal = function(txp, cb) { - $.checkState(this.credentials && this.credentials.isComplete()); - $.checkArgument(txp.creatorId); - - var self = this; - - if (!self.credentials.canSign() && !txp.signatures) - return cb(new Error('You do not have the required keys to sign transactions')); - - if (!Verifier.checkTxProposal(self.credentials, txp)) { - return cb(new ServerCompromisedError('Server sent fake transaction proposal')); - } - - var signatures = txp.signatures || WalletUtils.signTxp(txp, self.credentials.xPrivKey); - - var url = '/v1/txproposals/' + txp.id + '/signatures/'; - var args = { - signatures: signatures - }; - - self._doPostRequest(url, args, function(err, txp) { - if (err) return cb(err); - return cb(null, txp); - }); -}; - -API.prototype.rejectTxProposal = function(txp, reason, cb) { - $.checkState(this.credentials && this.credentials.isComplete()); - $.checkArgument(cb); - - var self = this; - - var url = '/v1/txproposals/' + txp.id + '/rejections/'; - var args = { - reason: _encryptMessage(reason, self.credentials.sharedEncryptingKey) || '', - }; - self._doPostRequest(url, args, function(err, txp) { - if (err) return cb(err); - return cb(null, txp); - }); -}; - -API.prototype.broadcastTxProposal = function(txp, cb) { - $.checkState(this.credentials && this.credentials.isComplete()); - - var self = this; - - var url = '/v1/txproposals/' + txp.id + '/broadcast/'; - self._doPostRequest(url, {}, function(err, txp) { - if (err) return cb(err); - return cb(null, txp); - }); -}; - - - -API.prototype.removeTxProposal = function(txp, cb) { - $.checkState(this.credentials && this.credentials.isComplete()); - - var self = this; - - var url = '/v1/txproposals/' + txp.id; - self._doDeleteRequest(url, function(err) { - if (err) return cb(err); - return cb(); - }); -}; - -API.prototype.getTxHistory = function(opts, cb) { - $.checkState(this.credentials && this.credentials.isComplete()); - - var self = this; - - self._doGetRequest('/v1/txhistory/', function(err, txs) { - if (err) return cb(err); - - _processTxps(txs, self.credentials.sharedEncryptingKey); - - return cb(null, txs); - }); -}; - -module.exports = API; diff --git a/lib/client/credentials.js b/lib/client/credentials.js deleted file mode 100644 index becf254..0000000 --- a/lib/client/credentials.js +++ /dev/null @@ -1,169 +0,0 @@ -'use strict'; - -var $ = require('preconditions').singleton(); -var _ = require('lodash'); -var Bitcore = require('bitcore'); -var WalletUtils = require('../walletutils'); - -var FIELDS = [ - 'network', - 'xPrivKey', - 'xPubKey', - 'requestPrivKey', - 'copayerId', - 'publicKeyRing', - 'walletId', - 'walletName', - 'm', - 'n', - 'walletPrivKey', - 'personalEncryptingKey', - 'sharedEncryptingKey', - 'copayerName', -]; - -var EXPORTABLE_FIELDS = [ - 'xPrivKey', - 'requestPrivKey', - 'xPubKey', - 'm', - 'n', - 'publicKeyRing', - 'sharedEncryptingKey', -]; - -function Credentials() { - this.version = '1.0.0'; -}; - -Credentials.create = function(network) { - var x = new Credentials(); - - x.network = network; - x.xPrivKey = (new Bitcore.HDPrivateKey(network)).toString(); - x._expand(); - return x; -}; - -Credentials.fromExtendedPrivateKey = function(xPrivKey) { - var x = new Credentials(); - x.xPrivKey = xPrivKey; - x._expand(); - return x; -}; - -Credentials.fromExtendedPublicKey = function(xPubKey, requestPrivKey) { - var x = new Credentials(); - x.xPubKey = xPubKey; - x.requestPrivKey = requestPrivKey; - x._expand(); - return x; -}; - -Credentials.prototype._expand = function() { - $.checkState(this.xPrivKey || this.xPubKey); - - if (this.xPrivKey) { - var xPrivKey = new Bitcore.HDPrivateKey.fromString(this.xPrivKey); - this.xPubKey = (new Bitcore.HDPublicKey(xPrivKey)).toString(); - this.requestPrivKey = xPrivKey.derive('m/1/1').privateKey.toString(); - } - var network = WalletUtils.getNetworkFromXPubKey(this.xPubKey); - if (this.network) { - $.checkState(this.network == network); - } else { - this.network = network; - } - - this.personalEncryptingKey = WalletUtils.privateKeyToAESKey(this.requestPrivKey); - this.copayerId = WalletUtils.xPubToCopayerId(this.xPubKey); -}; - -Credentials.fromObj = function(obj) { - var x = new Credentials(); - - _.each(FIELDS, function(k) { - x[k] = obj[k]; - }); - - return x; -}; - -Credentials.prototype.toObj = function() { - return this; -}; - -Credentials.prototype.addWalletInfo = function(walletId, walletName, m, n, walletPrivKey, copayerName) { - this.walletId = walletId; - this.walletName = walletName; - this.m = m; - this.n = n; - this.walletPrivKey = walletPrivKey; - this.sharedEncryptingKey = WalletUtils.privateKeyToAESKey(walletPrivKey); - this.copayerName = copayerName; - if (n == 1) { - this.addPublicKeyRing([this.xPubKey]); - } -}; - -Credentials.prototype.hasWalletInfo = function() { - return !!this.walletId; -}; - -Credentials.prototype.addPublicKeyRing = function(publicKeyRing) { - this.publicKeyRing = _.clone(publicKeyRing); -}; - -Credentials.prototype.canSign = function() { - return !!this.xPrivKey; -}; - -Credentials.prototype.isComplete = function() { - if (!this.m || !this.n) return false; - if (!this.publicKeyRing || this.publicKeyRing.length != this.n) return false; - 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 == 'requestPrivKey') { - return Bitcore.PrivateKey.fromString(self.requestPrivKey).toWIF(); - } - if (field == 'publicKeyRing') { - return _.without(self.publicKeyRing, self.xPubKey); - } - return self[field]; - }); - values.unshift(self.version); - - return JSON.stringify(values); -}; - -Credentials.importCompressed = function(compressed) { - var list; - try { - list = JSON.parse(compressed); - } catch (ex) { - throw new Error('Invalid compressed format'); - } - - var x = new Credentials(); - - // Remove version - var version = list[0]; - list = _.rest(list); - - _.each(EXPORTABLE_FIELDS, function(field, i) { - x[field] = list[i]; - }); - x._expand(); - - x.network = WalletUtils.getNetworkFromXPubKey(x.xPubKey); - x.publicKeyRing.push(x.xPubKey); - return x; -}; - -module.exports = Credentials; diff --git a/lib/client/index.js b/lib/client/index.js deleted file mode 100644 index 7e97815..0000000 --- a/lib/client/index.js +++ /dev/null @@ -1,3 +0,0 @@ -var client = module.exports = require('./api'); -client.Verifier = require('./verifier'); -client.AirGapped = require('./airgapped'); diff --git a/lib/client/servercompromisederror.js b/lib/client/servercompromisederror.js deleted file mode 100644 index 7c4df64..0000000 --- a/lib/client/servercompromisederror.js +++ /dev/null @@ -1,6 +0,0 @@ -function ServerCompromisedError(message) { - this.code = 'SERVERCOMPROMISED'; - this.message = message; -}; - -module.exports = ServerCompromisedError; diff --git a/lib/client/verifier.js b/lib/client/verifier.js deleted file mode 100644 index f45f101..0000000 --- a/lib/client/verifier.js +++ /dev/null @@ -1,78 +0,0 @@ -var $ = require('preconditions').singleton(); -var _ = require('lodash'); -var log = require('npmlog'); - -var Bitcore = require('bitcore'); -var WalletUtils = require('../walletutils') - -/* - * Checks data given by the server - */ - -function Verifier(opts) {}; - -Verifier.checkAddress = function(credentials, address) { - $.checkState(credentials.isComplete()); - var local = WalletUtils.deriveAddress(credentials.publicKeyRing, address.path, credentials.m, credentials.network); - return (local.address == address.address && JSON.stringify(local.publicKeys) == JSON.stringify(address.publicKeys)); -}; - -Verifier.checkCopayers = function(credentials, copayers) { - $.checkState(credentials.walletPrivKey); - var walletPubKey = Bitcore.PrivateKey.fromString(credentials.walletPrivKey).toPublicKey().toString(); - - if (copayers.length != credentials.n) { - log.error('Missing public keys in server response'); - return false; - } - - // Repeated xpub kes? - var uniq = []; - var error; - _.each(copayers, function(copayer) { - if (uniq[copayers.xPubKey]++) { - log.error('Repeated public keys in server response'); - error = true; - } - - // Not signed pub keys - if (!copayer.xPubKey || !copayer.xPubKeySignature || - !WalletUtils.verifyMessage(copayer.xPubKey, copayer.xPubKeySignature, walletPubKey)) { - log.error('Invalid signatures in server response'); - error = true; - } - }); - if (error) - return false; - - if (!_.contains(_.pluck(copayers, 'xPubKey'), credentials.xPubKey)) { - log.error('Server response does not contains our public keys') - return false; - } - return true; -}; - - -Verifier.checkTxProposal = function(credentials, txp) { - $.checkArgument(txp.creatorId); - $.checkState(credentials.isComplete()); - - var creatorXPubKey = _.find(credentials.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); - log.debug('Regenerating & verifying tx proposal hash -> Hash: ', hash, ' Signature: ', txp.proposalSignature); - - - if (!WalletUtils.verifyMessage(hash, txp.proposalSignature, creatorSigningPubKey)) - return false; - - return Verifier.checkAddress(credentials, txp.changeAddress); -}; - -module.exports = Verifier; diff --git a/package.json b/package.json index a174e0e..e281a2e 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "inherits": "^2.0.1", "leveldown": "^0.10.0", "levelup": "^0.19.0", - "lodash": "*", + "lodash": "^3.3.1", "mocha-lcov-reporter": "0.0.1", "moment": "^2.9.0", "morgan": "*", diff --git a/test/integration/client.js b/test/integration/client.js deleted file mode 100644 index 0f4adac..0000000 --- a/test/integration/client.js +++ /dev/null @@ -1,1083 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var $ = require('preconditions').singleton(); -var chai = require('chai'); -var sinon = require('sinon'); -var should = chai.should(); -var levelup = require('levelup'); -var memdown = require('memdown'); -var async = require('async'); -var request = require('supertest'); -var Client = require('../../lib/client'); -var AirGapped = Client.AirGapped; -var Bitcore = require('bitcore'); -var WalletUtils = require('../../lib/walletutils'); -var ExpressApp = require('../../lib/expressapp'); -var Storage = require('../../lib/storage'); -var TestData = require('../testdata'); - - -var helpers = {}; - -helpers.getRequest = function(app) { - $.checkArgument(app); - return function(args, cb) { - var req = request(app); - var r = req[args.method](args.relUrl); - - if (args.headers) { - _.each(args.headers, function(v, k) { - r.set(k, v); - }) - } - if (!_.isEmpty(args.body)) { - r.send(args.body); - }; - r.end(function(err, res) { - return cb(err, res, res.body); - }); - }; -}; - -helpers.newClient = function(app) { - $.checkArgument(app); - return new Client({ - request: helpers.getRequest(app), - }); -}; - -helpers.createAndJoinWallet = function(clients, m, n, cb) { - clients[0].createWallet('wallet name', 'creator', m, n, 'testnet', - function(err, secret) { - should.not.exist(err); - - if (n > 1) { - should.exist(secret); - } - - async.series([ - - function(next) { - async.each(_.range(1, n), function(i, cb) { - clients[i].joinWallet(secret, 'copayer ' + i, cb); - }, next); - }, - function(next) { - async.each(_.range(n), function(i, cb) { - clients[i].openWallet(cb); - }, next); - }, - ], - function(err) { - should.not.exist(err); - return cb({ - m: m, - n: n, - secret: secret, - }); - }); - }); -}; - -helpers.tamperResponse = function(clients, method, url, args, tamper, cb) { - clients = [].concat(clients); - // Use first client to get a clean response from server - clients[0]._doRequest(method, url, args, function(err, result) { - should.not.exist(err); - tamper(result); - // Return tampered data for every client in the list - _.each(clients, function(client) { - client._doRequest = sinon.stub().withArgs(method, url).yields(null, result); - }); - return cb(); - }); -}; - - -var blockExplorerMock = {}; - -blockExplorerMock.getUnspentUtxos = function(dummy, cb) { - var ret = _.map(blockExplorerMock.utxos || [], function(x) { - var y = _.clone(x); - y.toObject = function() { - return this; - }; - return y; - }); - return cb(null, ret); -}; - -blockExplorerMock.setUtxo = function(address, amount, m) { - blockExplorerMock.utxos.push({ - txid: Bitcore.crypto.Hash.sha256(new Buffer(Math.random() * 100000)).toString('hex'), - vout: Math.floor((Math.random() * 10) + 1), - amount: amount, - address: address.address, - scriptPubKey: Bitcore.Script.buildMultisigOut(address.publicKeys, m).toScriptHashOut().toString(), - }); -}; - -blockExplorerMock.broadcast = function(raw, cb) { - blockExplorerMock.lastBroadcasted = raw; - return cb(null, (new Bitcore.Transaction(raw)).id); -}; - -blockExplorerMock.setHistory = function(txs) { - blockExplorerMock.txHistory = txs; -}; - -blockExplorerMock.getTransactions = function(addresses, cb) { - return cb(null, blockExplorerMock.txHistory || []); -}; - -blockExplorerMock.reset = function() { - blockExplorerMock.utxos = []; - blockExplorerMock.txHistory = []; -}; - - - -describe('client API ', function() { - var clients, app; - - beforeEach(function() { - var db = levelup(memdown, { - valueEncoding: 'json' - }); - var storage = new Storage({ - db: db - }); - app = ExpressApp.start({ - WalletService: { - storage: storage, - blockExplorer: blockExplorerMock, - }, - disableLogs: true, - }); - // Generates 5 clients - clients = _.map(_.range(5), function(i) { - return helpers.newClient(app); - }); - blockExplorerMock.reset(); - }); - - describe('Server internals', function() { - it('should allow cors', function(done) { - clients[0].credentials = {}; - clients[0]._doRequest('options', '/', {}, function(err, x, headers) { - headers['access-control-allow-origin'].should.equal('*'); - should.exist(headers['access-control-allow-methods']); - should.exist(headers['access-control-allow-headers']); - done(); - }); - }); - - it('should handle critical errors', function(done) { - var s = sinon.stub(); - s.storeWallet = sinon.stub().yields('bigerror'); - s.fetchWallet = sinon.stub().yields(null); - app = ExpressApp.start({ - WalletService: { - storage: s, - blockExplorer: blockExplorerMock, - }, - disableLogs: true, - }); - var s2 = sinon.stub(); - s2.load = sinon.stub().yields(null); - var client = helpers.newClient(app); - client.storage = s2; - client.createWallet('1', '2', 1, 1, 'testnet', - function(err) { - err.code.should.equal('ERROR'); - done(); - }); - }); - - it('should handle critical errors (Case2)', function(done) { - var s = sinon.stub(); - s.storeWallet = sinon.stub().yields({ - code: 501, - message: 'wow' - }); - s.fetchWallet = sinon.stub().yields(null); - app = ExpressApp.start({ - WalletService: { - storage: s, - blockExplorer: blockExplorerMock, - }, - disableLogs: true, - }); - var s2 = sinon.stub(); - s2.load = sinon.stub().yields(null); - var client = helpers.newClient(app); - client.storage = s2; - client.createWallet('1', '2', 1, 1, 'testnet', - function(err) { - err.code.should.equal('ERROR'); - done(); - }); - }); - }); - - describe('Wallet Creation', function() { - it('should check balance in a 1-1 ', function(done) { - helpers.createAndJoinWallet(clients, 1, 1, function() { - clients[0].getBalance(function(err, x) { - should.not.exist(err); - done(); - }) - }); - }); - it('should be able to complete wallets in copayer that joined later', function(done) { - helpers.createAndJoinWallet(clients, 2, 3, function() { - clients[0].getBalance(function(err, x) { - should.not.exist(err); - clients[1].getBalance(function(err, x) { - should.not.exist(err); - clients[2].getBalance(function(err, x) { - should.not.exist(err); - done(); - }) - }) - }) - }); - }); - - it('should not allow to join a full wallet ', function(done) { - helpers.createAndJoinWallet(clients, 2, 2, function(w) { - should.exist(w.secret); - clients[4].joinWallet(w.secret, 'copayer', function(err, result) { - err.code.should.contain('WFULL'); - done(); - }); - }); - }); - it('should fail with an invalid secret', function(done) { - // Invalid - clients[0].joinWallet('dummy', 'copayer', function(err, result) { - err.message.should.contain('Invalid secret'); - // Right length, invalid char for base 58 - clients[0].joinWallet('DsZbqNQQ9LrTKU8EknR7gFKyCQMPg2UUHNPZ1BzM5EbJwjRZaUNBfNtdWLluuFc0f7f7sTCkh7T', 'copayer', function(err, result) { - err.message.should.contain('Invalid secret'); - done(); - }); - }); - }); - it('should fail with an unknown secret', function(done) { - // Unknown walletId - var oldSecret = '3bJKRn1HkQTpwhVaJMaJ22KwsjN24ML9uKfkSrP7iDuq91vSsTEygfGMMpo6kWLp1pXG9wZSKcT'; - clients[0].joinWallet(oldSecret, 'copayer', function(err, result) { - err.code.should.contain('BADREQUEST'); - done(); - }); - }); - - it('should reject wallets with bad signatures', function(done) { - // Do not complete clients[1] pkr - var openWalletStub = sinon.stub(clients[1], 'openWallet').yields(); - - helpers.createAndJoinWallet(clients, 2, 3, function() { - helpers.tamperResponse([clients[0], clients[1]], 'get', '/v1/wallets/', {}, function(status) { - status.wallet.copayers[0].xPubKey = status.wallet.copayers[1].xPubKey; - }, function() { - openWalletStub.restore(); - clients[1].openWallet(function(err, x) { - err.code.should.contain('SERVERCOMPROMISED'); - done(); - }); - }); - }); - }); - - it('should reject wallets with missing signatures', function(done) { - // Do not complete clients[1] pkr - var openWalletStub = sinon.stub(clients[1], 'openWallet').yields(); - - helpers.createAndJoinWallet(clients, 2, 3, function() { - helpers.tamperResponse([clients[0], clients[1]], 'get', '/v1/wallets/', {}, function(status) { - delete status.wallet.copayers[1].xPubKey; - }, function() { - openWalletStub.restore(); - clients[1].openWallet(function(err, x) { - err.code.should.contain('SERVERCOMPROMISED'); - done(); - }); - }); - }); - }); - - it('should reject wallets missing callers pubkey', function(done) { - // Do not complete clients[1] pkr - var openWalletStub = sinon.stub(clients[1], 'openWallet').yields(); - - helpers.createAndJoinWallet(clients, 2, 3, function() { - helpers.tamperResponse([clients[0], clients[1]], 'get', '/v1/wallets/', {}, function(status) { - // Replace caller's pubkey - status.wallet.copayers[1].xPubKey = (new Bitcore.HDPrivateKey()).publicKey; - // Add a correct signature - status.wallet.copayers[1].xPubKeySignature = WalletUtils.signMessage(status.wallet.copayers[1].xPubKey, clients[0].credentials.walletPrivKey); - }, function() { - openWalletStub.restore(); - clients[1].openWallet(function(err, x) { - err.code.should.contain('SERVERCOMPROMISED'); - done(); - }); - }); - }); - }); - it.skip('should return wallet status even if wallet is not yet complete', function(done) {}); - }); - - describe('Address Creation', function() { - it('should be able to create address in all copayers in a 2-3 wallet', function(done) { - this.timeout(5000); - helpers.createAndJoinWallet(clients, 2, 3, function() { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - should.exist(x0.address); - clients[1].createAddress(function(err, x1) { - should.not.exist(err); - should.exist(x1.address); - clients[2].createAddress(function(err, x2) { - should.not.exist(err); - should.exist(x2.address); - done(); - }); - }); - }); - }); - }); - it('should see balance on address created by others', function(done) { - this.timeout(5000); - helpers.createAndJoinWallet(clients, 2, 2, function(w) { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - should.exist(x0.address); - - blockExplorerMock.setUtxo(x0, 10, w.m); - clients[0].getBalance(function(err, bal0) { - should.not.exist(err); - bal0.totalAmount.should.equal(10 * 1e8); - bal0.lockedAmount.should.equal(0); - clients[1].getBalance(function(err, bal1) { - bal1.totalAmount.should.equal(10 * 1e8); - bal1.lockedAmount.should.equal(0); - done(); - }); - }); - }); - }); - }); - it('should detect fake addresses', function(done) { - helpers.createAndJoinWallet(clients, 1, 1, function() { - helpers.tamperResponse(clients[0], 'post', '/v1/addresses/', {}, function(address) { - address.address = '2N86pNEpREGpwZyHVC5vrNUCbF9nM1Geh4K'; - }, function() { - clients[0].createAddress(function(err, x0) { - err.code.should.contain('SERVERCOMPROMISED'); - done(); - }); - }); - }); - }); - it('should detect fake public keys', function(done) { - helpers.createAndJoinWallet(clients, 1, 1, function() { - helpers.tamperResponse(clients[0], 'post', '/v1/addresses/', {}, function(address) { - address.publicKeys = [ - '0322defe0c3eb9fcd8bc01878e6dbca7a6846880908d214b50a752445040cc5c54', - '02bf3aadc17131ca8144829fa1883c1ac0a8839067af4bca47a90ccae63d0d8037' - ]; - }, function() { - clients[0].createAddress(function(err, x0) { - err.code.should.contain('SERVERCOMPROMISED'); - done(); - }); - }); - }); - }); - }); - - describe('Transaction Proposals Creation and Locked funds', function() { - it('Should lock and release funds through rejection', function(done) { - helpers.createAndJoinWallet(clients, 2, 2, function(w) { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - should.exist(x0.address); - blockExplorerMock.setUtxo(x0, 1, 2); - blockExplorerMock.setUtxo(x0, 1, 2); - var opts = { - amount: 120000000, - toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', - message: 'hello 1-1', - }; - clients[0].sendTxProposal(opts, function(err, x) { - should.not.exist(err); - - clients[0].sendTxProposal(opts, function(err, y) { - err.code.should.contain('INSUFFICIENTFUNDS'); - - clients[0].rejectTxProposal(x, 'no', function(err, z) { - should.not.exist(err); - z.status.should.equal('rejected'); - clients[0].sendTxProposal(opts, function(err, x) { - should.not.exist(err); - done(); - }); - }); - }); - }); - }); - }); - }); - it('Should lock and release funds through removal', function(done) { - helpers.createAndJoinWallet(clients, 2, 2, function(w) { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - should.exist(x0.address); - blockExplorerMock.setUtxo(x0, 1, 2); - blockExplorerMock.setUtxo(x0, 1, 2); - var opts = { - amount: 120000000, - toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', - message: 'hello 1-1', - }; - clients[0].sendTxProposal(opts, function(err, x) { - should.not.exist(err); - - clients[0].sendTxProposal(opts, function(err, y) { - err.code.should.contain('INSUFFICIENTFUNDS'); - - clients[0].removeTxProposal(x, function(err) { - should.not.exist(err); - - clients[0].sendTxProposal(opts, function(err, x) { - should.not.exist(err); - done(); - }); - }); - }); - }); - }); - }); - }); - it('Should keep message and refusal texts', function(done) { - helpers.createAndJoinWallet(clients, 2, 3, function(w) { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - blockExplorerMock.setUtxo(x0, 10, 2); - var opts = { - amount: 10000, - toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', - message: 'some message', - }; - clients[0].sendTxProposal(opts, function(err, x) { - should.not.exist(err); - clients[1].rejectTxProposal(x, 'rejection comment', function(err, tx1) { - should.not.exist(err); - - clients[2].getTxProposals({}, function(err, txs) { - should.not.exist(err); - txs[0].message.should.equal('some message'); - txs[0].actions[0].comment.should.equal('rejection comment'); - done(); - }); - }); - }); - }); - }); - }); - it('Should encrypt proposal message', function(done) { - helpers.createAndJoinWallet(clients, 2, 3, function(w) { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - blockExplorerMock.setUtxo(x0, 10, 2); - var opts = { - amount: 10000, - toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', - message: 'some message', - }; - var spy = sinon.spy(clients[0], '_doPostRequest'); - clients[0].sendTxProposal(opts, function(err, x) { - should.not.exist(err); - spy.calledOnce.should.be.true; - JSON.stringify(spy.getCall(0).args).should.not.contain('some message'); - done(); - }); - }); - }); - }); - it('Should encrypt proposal refusal comment', function(done) { - helpers.createAndJoinWallet(clients, 2, 3, function(w) { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - blockExplorerMock.setUtxo(x0, 10, 2); - var opts = { - amount: 10000, - toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', - }; - clients[0].sendTxProposal(opts, function(err, x) { - should.not.exist(err); - var spy = sinon.spy(clients[1], '_doPostRequest'); - clients[1].rejectTxProposal(x, 'rejection comment', function(err, tx1) { - should.not.exist(err); - spy.calledOnce.should.be.true; - JSON.stringify(spy.getCall(0).args).should.not.contain('rejection comment'); - done(); - }); - }); - }); - }); - }); - it('should detect fake tx proposals (wrong signature)', function(done) { - helpers.createAndJoinWallet(clients, 1, 1, function() { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - blockExplorerMock.setUtxo(x0, 10, 2); - var opts = { - amount: 10000, - toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', - message: 'hello', - }; - clients[0].sendTxProposal(opts, function(err, x) { - should.not.exist(err); - - helpers.tamperResponse(clients[0], 'get', '/v1/txproposals/', {}, function(txps) { - txps[0].proposalSignature = '304402206e4a1db06e00068582d3be41cfc795dcf702451c132581e661e7241ef34ca19202203e17598b4764913309897d56446b51bc1dcd41a25d90fdb5f87a6b58fe3a6920'; - }, function() { - clients[0].getTxProposals({}, function(err, txps) { - should.exist(err); - err.code.should.contain('SERVERCOMPROMISED'); - done(); - }); - }); - }); - }); - }); - }); - - it('should detect fake tx proposals (tampered amount)', function(done) { - helpers.createAndJoinWallet(clients, 1, 1, function() { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - blockExplorerMock.setUtxo(x0, 10, 2); - var opts = { - amount: 10000, - toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', - message: 'hello', - }; - clients[0].sendTxProposal(opts, function(err, x) { - should.not.exist(err); - - helpers.tamperResponse(clients[0], 'get', '/v1/txproposals/', {}, function(txps) { - txps[0].amount = 100000; - }, function() { - clients[0].getTxProposals({}, function(err, txps) { - should.exist(err); - err.code.should.contain('SERVERCOMPROMISED'); - done(); - }); - }); - }); - }); - }); - }); - it('should detect fake tx proposals (change address not it wallet)', function(done) { - helpers.createAndJoinWallet(clients, 1, 1, function() { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - blockExplorerMock.setUtxo(x0, 10, 2); - var opts = { - amount: 10000, - toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', - message: 'hello', - }; - clients[0].sendTxProposal(opts, function(err, x) { - should.not.exist(err); - - helpers.tamperResponse(clients[0], 'get', '/v1/txproposals/', {}, function(txps) { - txps[0].changeAddress.address = 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5'; - }, function() { - clients[0].getTxProposals({}, function(err, txps) { - should.exist(err); - err.code.should.contain('SERVERCOMPROMISED'); - done(); - }); - }); - }); - }); - }); - }); - it('Should return only main addresses (case 1)', function(done) { - helpers.createAndJoinWallet(clients, 1, 1, function(w) { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - blockExplorerMock.setUtxo(x0, 1, 1); - var opts = { - amount: 10000000, - toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', - message: 'hello 1-1', - }; - clients[0].sendTxProposal(opts, function(err, x) { - should.not.exist(err); - clients[0].getMainAddresses({}, function(err, addr) { - should.not.exist(err); - addr.length.should.equal(1); - done(); - }); - }); - }); - }); - }); - it('Should return only main addresses (case 2)', function(done) { - helpers.createAndJoinWallet(clients, 1, 1, function(w) { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - clients[0].getMainAddresses({ - doNotVerify: true - }, function(err, addr) { - should.not.exist(err); - addr.length.should.equal(2); - done(); - }); - }); - }); - }); - }); - }); - - describe('Transactions Signatures and Rejection', function() { - this.timeout(5000); - it('Send and broadcast in 1-1 wallet', function(done) { - helpers.createAndJoinWallet(clients, 1, 1, function(w) { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - should.exist(x0.address); - blockExplorerMock.setUtxo(x0, 1, 1); - var opts = { - amount: 10000000, - toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', - message: 'hello 1-1', - }; - clients[0].sendTxProposal(opts, function(err, txp) { - should.not.exist(err); - txp.requiredRejections.should.equal(1); - txp.requiredSignatures.should.equal(1); - txp.status.should.equal('pending'); - txp.changeAddress.path.should.equal('m/2147483647/1/0'); - clients[0].signTxProposal(txp, function(err, txp) { - should.not.exist(err); - txp.status.should.equal('accepted'); - clients[0].broadcastTxProposal(txp, function(err, txp) { - txp.status.should.equal('broadcasted'); - txp.txid.should.equal((new Bitcore.Transaction(blockExplorerMock.lastBroadcasted)).id); - done(); - }); - }); - }); - }); - }); - }); - it('Send and broadcast in 2-3 wallet', function(done) { - helpers.createAndJoinWallet(clients, 2, 3, function(w) { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - should.exist(x0.address); - blockExplorerMock.setUtxo(x0, 10, 1); - var opts = { - amount: 10000, - toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', - message: 'hello 1-1', - }; - clients[0].sendTxProposal(opts, function(err, txp) { - should.not.exist(err); - clients[0].getStatus(function(err, st) { - should.not.exist(err); - var txp = st.pendingTxps[0]; - txp.status.should.equal('pending'); - txp.requiredRejections.should.equal(2); - txp.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(txp, function(err, txp) { - should.not.exist(err, err); - txp.status.should.equal('pending'); - clients[1].signTxProposal(txp, function(err, txp) { - should.not.exist(err); - txp.status.should.equal('accepted'); - clients[1].broadcastTxProposal(txp, function(err, txp) { - txp.status.should.equal('broadcasted'); - txp.txid.should.equal((new Bitcore.Transaction(blockExplorerMock.lastBroadcasted)).id); - done(); - }); - }); - }); - }); - }); - }); - }); - }); - - it('Send, reject, 2 signs and broadcast in 2-3 wallet', function(done) { - helpers.createAndJoinWallet(clients, 2, 3, function(w) { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - should.exist(x0.address); - blockExplorerMock.setUtxo(x0, 10, 1); - var opts = { - amount: 10000, - toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', - message: 'hello 1-1', - }; - clients[0].sendTxProposal(opts, function(err, txp) { - should.not.exist(err); - txp.status.should.equal('pending'); - txp.requiredRejections.should.equal(2); - txp.requiredSignatures.should.equal(2); - clients[0].rejectTxProposal(txp, 'wont sign', function(err, txp) { - should.not.exist(err, err); - txp.status.should.equal('pending'); - clients[1].signTxProposal(txp, function(err, txp) { - should.not.exist(err); - clients[2].signTxProposal(txp, function(err, txp) { - should.not.exist(err); - txp.status.should.equal('accepted'); - clients[2].broadcastTxProposal(txp, function(err, txp) { - txp.status.should.equal('broadcasted'); - txp.txid.should.equal((new Bitcore.Transaction(blockExplorerMock.lastBroadcasted)).id); - done(); - }); - }); - }); - }); - }); - }); - }); - }); - - it('Send, reject in 3-4 wallet', function(done) { - helpers.createAndJoinWallet(clients, 3, 4, function(w) { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - should.exist(x0.address); - blockExplorerMock.setUtxo(x0, 10, 1); - var opts = { - amount: 10000, - toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', - message: 'hello 1-1', - }; - clients[0].sendTxProposal(opts, function(err, txp) { - should.not.exist(err); - txp.status.should.equal('pending'); - txp.requiredRejections.should.equal(2); - txp.requiredSignatures.should.equal(3); - - clients[0].rejectTxProposal(txp, 'wont sign', function(err, txp) { - should.not.exist(err, err); - txp.status.should.equal('pending'); - clients[1].signTxProposal(txp, function(err, txp) { - should.not.exist(err); - txp.status.should.equal('pending'); - clients[2].rejectTxProposal(txp, 'me neither', function(err, txp) { - should.not.exist(err); - txp.status.should.equal('rejected'); - done(); - }); - }); - }); - }); - }); - }); - }); - - it('Should not allow to reject or sign twice', function(done) { - helpers.createAndJoinWallet(clients, 2, 3, function(w) { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - should.exist(x0.address); - blockExplorerMock.setUtxo(x0, 10, 1); - var opts = { - amount: 10000, - toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', - message: 'hello 1-1', - }; - clients[0].sendTxProposal(opts, function(err, txp) { - should.not.exist(err); - txp.status.should.equal('pending'); - txp.requiredRejections.should.equal(2); - txp.requiredSignatures.should.equal(2); - clients[0].signTxProposal(txp, function(err, txp) { - should.not.exist(err); - txp.status.should.equal('pending'); - clients[0].signTxProposal(txp, function(err) { - should.exist(err); - err.code.should.contain('CVOTED'); - clients[1].rejectTxProposal(txp, 'xx', function(err, txp) { - should.not.exist(err); - clients[1].rejectTxProposal(txp, 'xx', function(err) { - should.exist(err); - err.code.should.contain('CVOTED'); - done(); - }); - }); - }); - }); - }); - }); - }); - }); - }); - - describe('Transaction history', function() { - it('should get transaction history', function(done) { - blockExplorerMock.setHistory(TestData.history); - helpers.createAndJoinWallet(clients, 1, 1, function(w) { - clients[0].createAddress(function(err, x0) { - should.not.exist(err); - should.exist(x0.address); - clients[0].getTxHistory({}, function(err, txs) { - should.not.exist(err); - should.exist(txs); - txs.length.should.equal(2); - done(); - }); - }); - }); - }); - it('should get empty transaction history when there are no addresses', function(done) { - blockExplorerMock.setHistory(TestData.history); - helpers.createAndJoinWallet(clients, 1, 1, function(w) { - clients[0].getTxHistory({}, function(err, txs) { - should.not.exist(err); - should.exist(txs); - txs.length.should.equal(0); - done(); - }); - }); - }); - it.skip('should get transaction history decorated with proposal', function(done) {}); - it.skip('should get paginated transaction history', function(done) {}); - }); - - describe('Mobility, backup & recovery', function() { - describe('Export & Import', function() { - describe('Success', function() { - var address, importedClient; - beforeEach(function(done) { - importedClient = null; - helpers.createAndJoinWallet(clients, 1, 1, function() { - clients[0].createAddress(function(err, addr) { - should.not.exist(err); - should.exist(addr.address); - address = addr.address; - done(); - }); - }); - }); - afterEach(function(done) { - importedClient.getMainAddresses({}, function(err, list) { - should.not.exist(err); - should.exist(list); - list.length.should.equal(1); - list[0].address.should.equal(address); - done(); - }); - }); - - it('should export & import', function() { - var exported = clients[0].export(); - - importedClient = helpers.newClient(app); - importedClient.import(exported); - }); - it.skip('should export & import compressed', function() { - var walletId = clients[0].credentials.walletId; - var walletName = clients[0].credentials.walletName; - var copayerName = clients[0].credentials.copayerName; - - var exported = clients[0].export({ - compressed: true - }); - - importedClient = helpers.newClient(app); - importedClient.import(exported, { - compressed: true - }); - importedClient.credentials.walletId.should.equal(walletId); - importedClient.credentials.walletName.should.equal(walletName); - importedClient.credentials.copayerName.should.equal(copayerName); - }); - it('should export & import encrypted', function() { - var xPrivKey = clients[0].credentials.xPrivKey; - should.exist(xPrivKey); - - var exported = clients[0].export({ - password: '123' - }); - exported.should.not.contain(xPrivKey); - - importedClient = helpers.newClient(app); - importedClient.import(exported, { - password: '123' - }); - should.exist(importedClient.credentials.xPrivKey); - importedClient.credentials.xPrivKey.should.equal(xPrivKey); - }); - it('should export & import compressed & encrypted', function() { - var exported = clients[0].export({ - compressed: true, - password: '123' - }); - - importedClient = helpers.newClient(app); - importedClient.import(exported, { - compressed: true, - password: '123' - }); - }); - }); - describe('Fail', function() { - it.skip('should fail to export compressed & import uncompressed', function() {}); - it.skip('should fail to export uncompressed & import compressed', function() {}); - it.skip('should fail to export unencrypted & import with password', function() {}); - it.skip('should fail to export encrypted & import with incorrect password', function() {}); - }); - }); - - describe('Recovery', function() { - it('should be able to regain access to a 1-1 wallet with just the xPriv', function(done) { - helpers.createAndJoinWallet(clients, 1, 1, function() { - var xpriv = clients[0].credentials.xPrivKey; - var walletName = clients[0].credentials.walletName; - var copayerName = clients[0].credentials.copayerName; - - clients[0].createAddress(function(err, addr) { - should.not.exist(err); - should.exist(addr); - - var recoveryClient = helpers.newClient(app); - recoveryClient.seedFromExtendedPrivateKey(xpriv); - recoveryClient.openWallet(function(err) { - console.log(err); - should.not.exist(err); - recoveryClient.credentials.walletName.should.equal(walletName); - recoveryClient.credentials.copayerName.should.equal(copayerName); - recoveryClient.getMainAddresses({}, function(err, list) { - should.not.exist(err); - should.exist(list); - list[0].address.should.equal(addr.address); - done(); - }); - }); - }); - }); - }); - }); - }); - - describe('Air gapped related flows', function() { - it('should create wallet in proxy from airgapped', function(done) { - var airgapped = new AirGapped({ - network: 'testnet' - }); - var seed = airgapped.getSeed(); - - var proxy = helpers.newClient(app); - proxy.seedFromAirGapped(seed); - should.not.exist(proxy.credentials.xPrivKey); - proxy.createWallet('wallet name', 'creator', 1, 1, 'testnet', function(err) { - should.not.exist(err); - proxy.getStatus(function(err, status) { - should.not.exist(err); - status.wallet.name.should.equal('wallet name'); - done(); - }); - }); - }); - - it('should be able to sign from airgapped client and broadcast from proxy', function(done) { - var airgapped = new AirGapped({ - network: 'testnet' - }); - var seed = airgapped.getSeed(); - - var proxy = helpers.newClient(app); - proxy.seedFromAirGapped(seed); - - async.waterfall([ - - function(next) { - proxy.createWallet('wallet name', 'creator', 1, 1, 'testnet', function(err) { - should.not.exist(err); - proxy.createAddress(function(err, address) { - should.not.exist(err); - should.exist(address.address); - blockExplorerMock.setUtxo(address, 1, 1); - var opts = { - amount: 1200000, - toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', - message: 'hello 1-1', - }; - proxy.sendTxProposal(opts, next); - }); - }); - }, - function(txp, next) { - should.exist(txp); - proxy.signTxProposal(txp, function(err, txp) { - should.exist(err); - should.not.exist(txp); - err.message.should.equal('You do not have the required keys to sign transactions'); - next(null, txp); - }); - }, - function(txp, next) { - proxy.getTxProposals({ - forAirGapped: true - }, next); - }, - function(bundle, next) { - var signatures = airgapped.signTxProposal(bundle.txps[0], bundle.publicKeyRing, bundle.m, bundle.n); - next(null, signatures); - }, - function(signatures, next) { - proxy.getTxProposals({}, function(err, txps) { - should.not.exist(err); - var txp = txps[0]; - txp.signatures = signatures; - async.each(txps, function(txp, cb) { - proxy.signTxProposal(txp, function(err, txp) { - should.not.exist(err); - proxy.broadcastTxProposal(txp, function(err, txp) { - should.not.exist(err); - txp.status.should.equal('broadcasted'); - should.exist(txp.txid); - cb(); - }); - }); - }, function(err) { - next(err); - }); - }); - }, - ], - function(err) { - should.not.exist(err); - done(); - } - ); - }); - it.skip('should be able to detect tampered PKR when signing on airgapped client', function(done) {}); - it.skip('should be able to detect tampered proposal when signing on airgapped client', function(done) {}); - it.skip('should be able to detect tampered change address when signing on airgapped client', function(done) {}); - }); -}); diff --git a/test/walletutils.js b/test/walletutils.js deleted file mode 100644 index b3e187e..0000000 --- a/test/walletutils.js +++ /dev/null @@ -1,96 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var Uuid = require('uuid'); -var chai = require('chai'); -var sinon = require('sinon'); -var should = chai.should(); -var Bitcore = require('bitcore'); -var WalletUtils = require('../lib/walletutils'); - -var aText = 'hola'; -var aPubKey = '03bec86ad4a8a91fe7c11ec06af27246ec55094db3d86098b7d8b2f12afe47627f'; -var aPrivKey = '09458c090a69a38368975fb68115df2f4b0ab7d1bc463fc60c67aa1730641d6c'; -var aSignature = '3045022100d6186930e4cd9984e3168e15535e2297988555838ad10126d6c20d4ac0e74eb502201095a6319ea0a0de1f1e5fb50f7bf10b8069de10e0083e23dbbf8de9b8e02785'; - -var otherPubKey = '02555a2d45e309c00cc8c5090b6ec533c6880ab2d3bc970b3943def989b3373f16'; - -describe('WalletUtils', function() { - - describe('#hashMessage', function() { - it('should create a hash', function() { - var res = WalletUtils.hashMessage(aText); - res.toString('hex').should.equal('4102b8a140ec642feaa1c645345f714bc7132d4fd2f7f6202db8db305a96172f'); - }); - }); - - describe('#signMessage', function() { - it('should sign a message', function() { - var sig = WalletUtils.signMessage(aText, aPrivKey); - should.exist(sig); - sig.should.equal(aSignature); - }); - it('should fail to sign with wrong args', function() { - (function() { - WalletUtils.signMessage(aText, aPubKey); - }).should.throw('Number'); - }); - }); - - describe('#verifyMessage', function() { - it('should fail to verify a malformed signature', function() { - var res = WalletUtils.verifyMessage(aText, 'badsignature', otherPubKey); - should.exist(res); - res.should.equal(false); - }); - it('should fail to verify a null signature', function() { - var res = WalletUtils.verifyMessage(aText, null, otherPubKey); - should.exist(res); - res.should.equal(false); - }); - it('should fail to verify with wrong pubkey', function() { - var res = WalletUtils.verifyMessage(aText, aSignature, otherPubKey); - should.exist(res); - res.should.equal(false); - }); - it('should verify', function() { - var res = WalletUtils.verifyMessage(aText, aSignature, aPubKey); - should.exist(res); - res.should.equal(true); - }); - }); - - describe('#signMessage #verifyMessage round trip', function() { - it('should sign and verify', function() { - var aLongerText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; - var sig = WalletUtils.signMessage(aLongerText, aPrivKey); - WalletUtils.verifyMessage(aLongerText, sig, aPubKey).should.equal(true); - }); - }); - - describe('#encryptMessage #decryptMessage round trip', function() { - it('should encrypt and decrypt', function() { - var pwd = "ezDRS2NRchMJLf1IWtjL5A=="; - var ct = WalletUtils.encryptMessage('hello world', pwd); - var msg = WalletUtils.decryptMessage(ct, pwd); - msg.should.equal('hello world'); - }); - }); - - describe('#toSecret #fromSecret round trip', function() { - it('should create secret and parse secret', function() { - var i = 0; - while (i++ < 100) { - var walletId = Uuid.v4(); - var walletPrivKey = new Bitcore.PrivateKey(); - var network = 'testnet'; - var secret = WalletUtils.toSecret(walletId, walletPrivKey, network); - var result = WalletUtils.fromSecret(secret); - result.walletId.should.equal(walletId); - result.walletPrivKey.toString().should.equal(walletPrivKey.toString()); - result.network.should.equal(network); - }; - }); - }); - -}); From 4d76585f655cd87369fdf3054e4213b83632667c Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 3 Mar 2015 18:57:38 -0300 Subject: [PATCH 2/4] add index, export express app --- index.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 index.js diff --git a/index.js b/index.js new file mode 100644 index 0000000..fde08c6 --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/expressapp'); From 537ccb45d24eb90be57a7bf6915daefc323b6350 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 3 Mar 2015 19:06:26 -0300 Subject: [PATCH 3/4] export storage --- index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index fde08c6..5f059a2 100644 --- a/index.js +++ b/index.js @@ -1 +1,2 @@ -module.exports = require('./lib/expressapp'); +module.exports.ExpressApp = require('./lib/expressapp'); +module.exports.Storage = require('./lib/storage'); From 327c38e126877499f1f413e4adadadca1e94202c Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 3 Mar 2015 19:07:05 -0300 Subject: [PATCH 4/4] export storage 2 --- index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 5f059a2..991317d 100644 --- a/index.js +++ b/index.js @@ -1,2 +1,7 @@ -module.exports.ExpressApp = require('./lib/expressapp'); -module.exports.Storage = require('./lib/storage'); +var BWS = {}; + +BWS.ExpressApp = require('./lib/expressapp'); +BWS.Storage = require('./lib/storage'); + + +module.exports = BWS;