Browse Source

Merge pull request #96 from isocolsky/ref/client

ref/client
activeAddress
Matias Alejo Garcia 10 years ago
parent
commit
b007713cd3
  1. 9
      bit-wallet/bit-address
  2. 22
      bit-wallet/bit-addresses
  3. 9
      bit-wallet/bit-balance
  4. 21
      bit-wallet/bit-broadcast
  5. 41
      bit-wallet/bit-confirm
  6. 17
      bit-wallet/bit-create
  7. 57
      bit-wallet/bit-history
  8. 11
      bit-wallet/bit-join
  9. 21
      bit-wallet/bit-reject
  10. 28
      bit-wallet/bit-rm
  11. 20
      bit-wallet/bit-send
  12. 28
      bit-wallet/bit-sign
  13. 24
      bit-wallet/bit-status
  14. 45
      bit-wallet/bit-txproposals
  15. 104
      bit-wallet/cli-utils.js
  16. 7
      bit-wallet/filestorage.js
  17. 62
      lib/client/airgapped.js
  18. 860
      lib/client/api.js
  19. 169
      lib/client/credentials.js
  20. 4
      lib/client/index.js
  21. 23
      lib/client/verifier.js
  22. 4
      lib/expressapp.js
  23. 17
      lib/model/copayer.js
  24. 2
      lib/model/txproposalaction.js
  25. 13
      lib/server.js
  26. 3
      lib/storage.js
  27. 85
      lib/walletutils.js
  28. 1035
      test/integration/client.js

9
bit-wallet/bit-address

@ -9,8 +9,9 @@ program
.parse(process.argv);
var args = program.args;
var client = utils.getClient(program);
client.createAddress(function(err, x) {
utils.die(err);
console.log('* New Address %s ', x.address);
utils.getClient(program, function (client) {
client.createAddress(function(err, x) {
utils.die(err);
console.log('* New Address %s ', x.address);
});
});

22
bit-wallet/bit-addresses

@ -10,14 +10,20 @@ program
.parse(process.argv);
var args = program.args;
var client = utils.getClient(program);
client.getMainAddresses({
doNotVerify: true
}, function(err, x) {
utils.die(err);
console.log('* Addresses:');
_.each(x, function(a) {
console.log(' ', a.address);
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.');
}
});
});

9
bit-wallet/bit-balance

@ -9,9 +9,10 @@ program
.parse(process.argv);
var args = program.args;
var client = utils.getClient(program);
client.getBalance(function(err, x) {
utils.die(err);
console.log('* Wallet balance %s (Locked %s)', utils.renderAmount(x.totalAmount), utils.renderAmount(x.lockedAmount) );
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) );
});
});

21
bit-wallet/bit-broadcast

@ -11,19 +11,16 @@ program
.parse(process.argv);
var args = program.args;
if (!args[0])
program.help();
var txpid = args[0] || '';
var txpid = args[0];
var client = utils.getClient(program);
client.getTxProposals({}, function(err, txps) {
utils.die(err);
var txp = utils.findOneTxProposal(txps, txpid);
client.broadcastTxProposal(txp, function(err, txid) {
utils.getClient(program, function (client) {
client.getTxProposals({}, function(err, txps) {
utils.die(err);
console.log('Transaction Broadcasted: TXID: ' + x.txid);
var txp = utils.findOneTxProposal(txps, txpid);
client.broadcastTxProposal(txp, function(err, txp) {
utils.die(err);
console.log('Transaction Broadcasted: TXID: ' + txp.txid);
});
});
});

41
bit-wallet/bit-confirm

@ -9,24 +9,27 @@ program = utils.configureCommander(program);
program
.parse(process.argv);
var client = utils.getClient(program);
client.getStatus(function(err, x, myCopayerId) {
utils.die(err);
console.log('\n To be sure that none Copayer has joined more that once to this wallet, you can asked them their confirmation number. They can grab them using this (bit confirm) command.');
console.log('\n * Copayer confirmations ids:');
var myConfirmationId;
_.each(x.wallet.copayers, function(x) {
var confirmationId = utils.confirmationId(x);
if (x.id != myCopayerId)
console.log('\t\t* %s : %s', x.name, confirmationId);
else
myConfirmationId = confirmationId;
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);
});
console.log('\t\t---');
console.log('\t\tYour confirmation ID: %s', myConfirmationId);
});

17
bit-wallet/bit-create

@ -8,7 +8,6 @@ program = utils.configureCommander(program);
program
.option('-t, --testnet', 'Create a Testnet Wallet')
.option('-n, --nopasswd [level]', 'Set access for no password usage: none(default), readonly, readwrite, full', 'none')
.usage('[options] <walletName> <m-n> [copayerName]')
.parse(process.argv);
@ -27,10 +26,14 @@ try {
utils.die(ex);
}
var client = utils.getClient(program);
client.createWallet(walletName, copayerName, mn[0], mn[1], network, function(err, secret) {
utils.die(err);
console.log(' * ' + _.capitalize(network) + ' Wallet Created.');
if (secret)
console.log(' - Secret to share:\n\t' + secret);
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);
}
});
});
});

57
bit-wallet/bit-history

@ -4,40 +4,39 @@ var _ = require('lodash');
var fs = require('fs');
var moment = require('moment');
var program = require('commander');
var Utils = require('./cli-utils');
program = Utils.configureCommander(program);
var utils = require('./cli-utils');
program = utils.configureCommander(program);
program
.parse(process.argv);
var args = program.args;
var client = Utils.getClient(program);
var txData;
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);
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);
});
});
});

11
bit-wallet/bit-join

@ -7,7 +7,6 @@ program = utils.configureCommander(program);
program
.usage('[options] <secret> [copayerName]')
.option('-n, --nopasswd [level]', 'Set access for no password usage: none(default), readonly, readwrite, full', 'none')
.parse(process.argv);
var args = program.args;
@ -17,8 +16,10 @@ if (!args[0])
var secret = args[0];
var copayerName = args[1] || process.env.USER;
var client = utils.getClient(program);
client.joinWallet(secret, copayerName, function(err, xx) {
utils.die(err);
console.log(' * Wallet Joined.', xx || '');
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 () {});
});
});

21
bit-wallet/bit-reject

@ -13,17 +13,18 @@ program
var args = program.args;
var txpid = args[0] || '';
var reason = args[1] || '';
var client = utils.getClient(program);
client.getTxProposals({}, function(err, txps) {
utils.die(err);
var txp = utils.findOneTxProposal(txps, txpid);
client.rejectTxProposal(txp, reason, function(err, tx) {
utils.getClient(program, function (client) {
client.getTxProposals({}, function(err, txps) {
utils.die(err);
if (tx.status == 'rejected')
console.log('Transaction finally rejected.');
else
console.log('Transaction rejected by you.');
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.');
});
});
});

28
bit-wallet/bit-rm

@ -12,26 +12,20 @@ program
.parse(process.argv);
var args = program.args;
if (!args[0])
program.help();
var txpid = args[0] || '';
var txpid = args[0];
var cli = new Client({
filename: program.config
});
cli.getTxProposals({}, function(err, txps) {
utils.die(err);
if (program.verbose)
console.log('* Raw Server Response:\n', txps); //TODO
utils.getClient(program, function (client) {
client.getTxProposals({}, function(err, txps) {
utils.die(err);
var txp = utils.findOneTxProposal(txps, txpid);
if (program.verbose)
console.log('* Raw Server Response:\n', txps); //TODO
cli.removeTxProposal(txp, function(err) {
utils.die(err);
var txp = utils.findOneTxProposal(txps, txpid);
client.removeTxProposal(txp, function(err) {
utils.die(err);
console.log('Transaction removed.');
console.log('Transaction removed.');
});
});
});

20
bit-wallet/bit-send

@ -31,14 +31,14 @@ try {
}
var note = args[2];
var client = utils.getClient(program);
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);
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);
});
});

28
bit-wallet/bit-sign

@ -15,9 +15,7 @@ program
var args = program.args;
var txpid = args[0] || '';
var client = utils.getClient(program);
function end(txp) {
function end(client, txp) {
if (program.output) {
client.getSignatures(txp, function(err, signatures) {
utils.die(err);
@ -50,14 +48,16 @@ function end(txp) {
};
if (program.input && program.output) {
var inFile = JSON.parse(fs.readFileSync(program.input));
end(inFile.txps[0]);
} else {
client.getTxProposals({}, function(err, txps) {
utils.die(err);
var txp = utils.findOneTxProposal(txps, txpid);
utils.die(err);
end(txp);
});
}
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);
});
}
});

24
bit-wallet/bit-status

@ -9,20 +9,20 @@ program
.parse(process.argv);
var args = program.args;
var client = utils.getClient(program);
utils.getClient(program, function (client) {
client.getStatus(function(err, res) {
utils.die(err);
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);
var x = res.wallet;
console.log('* Wallet %s [%s]: %d-%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(', '));
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));
var x = res.balance;
console.log('* Balance %s (Locked: %s)', utils.renderAmount(x.totalAmount), utils.renderAmount(x.lockedAmount));
utils.renderTxProposals(res.pendingTxps);
utils.renderTxProposals(res.pendingTxps);
});
});

45
bit-wallet/bit-txproposals

@ -7,39 +7,20 @@ var utils = require('./cli-utils');
program = utils.configureCommander(program);
program
.option('-i, --input [filename]', 'use input file instead of server\'s')
.option('-o, --output [filename]', 'write tx to output file')
.option('-o, --output [filename]', 'write tx to output file for offline signing')
.parse(process.argv);
var args = program.args;
var client = utils.getClient(program);
var txData;
function end(err, txps, rawtxps) {
utils.die(err);
if (program.input) {
console.log('\n* From File : %s\n', program.input);
}
utils.renderTxProposals(txps);
if (program.output) {
client.getEncryptedWalletData(function (err, toComplete) {
var txData = {
toComplete: toComplete,
txps: txps,
};
fs.writeFileSync(program.output, JSON.stringify(txData));
console.log(' * Proposals Saved to: %s\n', program.output);
});
}
};
if (program.input) {
var txData = fs.readFileSync(program.input);
txData = JSON.parse(txData);
client.parseTxProposals(txData, end);
} else {
client.getTxProposals({getRawTxps: !!program.output}, end);
}
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);
}
});
});

104
bit-wallet/cli-utils.js

@ -1,6 +1,8 @@
var _ = require('lodash');
var Client = require('../lib/client');
var FileStorage = require('./filestorage');
var read = require('read')
var log = require('npmlog');
var Utils = function() {};
@ -39,55 +41,75 @@ Utils.confirmationId = function(copayer) {
return parseInt(copayer.xPubKeySignature.substr(-4), 16).toString().substr(-4);
}
Utils.getClient = function(args) {
var storage = new Client.FileStorage({
Utils.getClient = function(args, cb) {
var storage = new FileStorage({
filename: args.file || process.env['BIT_FILE'],
});
var c = new Client({
storage: storage,
var client = new Client({
baseUrl: args.host || process.env['BIT_HOST'],
verbose: args.verbose,
});
if (args.nopasswd)
c.setNopasswdAccess(args.nopasswd);
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);
})
}
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);
}
});
});
};
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.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);
// })
// }
// });
return c;
}
Utils.findOneTxProposal = function(txps, id) {
var matches = _.filter(txps, function(tx) {
@ -173,7 +195,11 @@ Utils.renderTxProposals = function(txps) {
return a.copayerName + ' ' + (a.type == 'accept' ? '✓' : '✗') + (a.comment ? ' (' + a.comment + ')' : '');
}).join('. '));
}
console.log('\t\tMissing signatures: ' + missingSignatures);
if (missingSignatures > 0) {
console.log('\t\tMissing signatures: ' + missingSignatures);
} else {
console.log('\t\tReady to broadcast');
}
});
};

7
lib/client/filestorage.js → bit-wallet/filestorage.js

@ -1,4 +1,3 @@
var fs = require('fs')
function FileStorage(opts) {
@ -18,16 +17,14 @@ FileStorage.prototype.save = function(data, cb) {
};
FileStorage.prototype.load = function(cb) {
this.fs.readFile(this.filename, 'utf8', function(err,data) {
this.fs.readFile(this.filename, 'utf8', function(err, data) {
if (err) return cb(err);
try {
data = JSON.parse(data);
} catch (e) {
}
} catch (e) {}
return cb(null, data);
});
};
module.exports = FileStorage;

62
lib/client/airgapped.js

@ -0,0 +1,62 @@
'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;

860
lib/client/api.js

File diff suppressed because it is too large

169
lib/client/credentials.js

@ -0,0 +1,169 @@
'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;

4
lib/client/index.js

@ -1,5 +1,3 @@
//var client = ;
var client = module.exports = require('./api');
client.FileStorage = require('./filestorage');
client.Verifier = require('./verifier');
client.AirGapped = require('./airgapped');

23
lib/client/verifier.js

@ -11,16 +11,17 @@ var WalletUtils = require('../walletutils')
function Verifier(opts) {};
Verifier.checkAddress = function(data, address) {
var local = WalletUtils.deriveAddress(data.publicKeyRing, address.path, data.m, data.network);
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(copayers, walletPrivKey, myXPrivKey, n) {
$.checkArgument(walletPrivKey);
var walletPubKey = Bitcore.PrivateKey.fromString(walletPrivKey).toPublicKey().toString();
Verifier.checkCopayers = function(credentials, copayers) {
$.checkState(credentials.walletPrivKey);
var walletPubKey = Bitcore.PrivateKey.fromString(credentials.walletPrivKey).toPublicKey().toString();
if (copayers.length != n) {
if (copayers.length != credentials.n) {
log.error('Missing public keys in server response');
return false;
}
@ -44,8 +45,7 @@ Verifier.checkCopayers = function(copayers, walletPrivKey, myXPrivKey, n) {
if (error)
return false;
var myXPubKey = new Bitcore.HDPublicKey(myXPrivKey).toString();
if (!_.contains(_.pluck(copayers, 'xPubKey'), myXPubKey)) {
if (!_.contains(_.pluck(copayers, 'xPubKey'), credentials.xPubKey)) {
log.error('Server response does not contains our public keys')
return false;
}
@ -53,10 +53,11 @@ Verifier.checkCopayers = function(copayers, walletPrivKey, myXPrivKey, n) {
};
Verifier.checkTxProposal = function(data, txp) {
Verifier.checkTxProposal = function(credentials, txp) {
$.checkArgument(txp.creatorId);
$.checkState(credentials.isComplete());
var creatorXPubKey = _.find(data.publicKeyRing, function(xPubKey) {
var creatorXPubKey = _.find(credentials.publicKeyRing, function(xPubKey) {
if (WalletUtils.xPubToCopayerId(xPubKey) === txp.creatorId) return true;
});
@ -71,7 +72,7 @@ Verifier.checkTxProposal = function(data, txp) {
if (!WalletUtils.verifyMessage(hash, txp.proposalSignature, creatorSigningPubKey))
return false;
return Verifier.checkAddress(data, txp.changeAddress);
return Verifier.checkAddress(credentials, txp.changeAddress);
};
module.exports = Verifier;

4
lib/expressapp.js

@ -24,7 +24,6 @@ var ExpressApp = function() {};
ExpressApp.start = function(opts) {
opts = opts || {};
WalletService.initialize(opts.WalletService);
var app = express();
app.use(function(req, res, next) {
@ -104,13 +103,10 @@ ExpressApp.start = function(opts) {
code: 'NOTAUTHORIZED'
}), res, req);
var readOnly = req.method == 'GET';
var auth = {
copayerId: credentials.copayerId,
message: req.method.toLowerCase() + '|' + req.url + '|' + JSON.stringify(req.body),
signature: credentials.signature,
readOnly: readOnly,
};
WalletService.getInstanceWithAuth(auth, function(err, server) {
if (err) return returnError(err, res, req);

17
lib/model/copayer.js

@ -11,9 +11,6 @@ var AddressManager = require('./addressmanager');
var Utils = require('../walletutils');
var RO_SIGNING_PATH = "m/1/0";
var RW_SIGNING_PATH = "m/1/1";
function Copayer() {
this.version = '1.0.0';
};
@ -31,8 +28,7 @@ Copayer.create = function(opts) {
x.id = Utils.xPubToCopayerId(x.xPubKey);
x.name = opts.name;
x.xPubKeySignature = opts.xPubKeySignature; // So third parties can check independently
x.roPubKey = x.getROPubKey();
x.rwPubKey = x.getRWPubKey();
x.requestPubKey = x.getRequestPubKey();
x.addressManager = AddressManager.create({
copayerIndex: opts.copayerIndex
});
@ -48,8 +44,7 @@ Copayer.fromObj = function(obj) {
x.name = obj.name;
x.xPubKey = obj.xPubKey;
x.xPubKeySignature = obj.xPubKeySignature;
x.roPubKey = obj.roPubKey;
x.rwPubKey = obj.rwPubKey;
x.requestPubKey = obj.requestPubKey;
x.addressManager = AddressManager.fromObj(obj.addressManager);
return x;
@ -63,12 +58,8 @@ Copayer.prototype.getPublicKey = function(path) {
.toString();
};
Copayer.prototype.getROPubKey = function() {
return this.getPublicKey(RO_SIGNING_PATH);
};
Copayer.prototype.getRWPubKey = function() {
return this.getPublicKey(RW_SIGNING_PATH);
Copayer.prototype.getRequestPubKey = function() {
return this.getPublicKey('m/1/1');
};

2
lib/model/txproposalaction.js

@ -11,7 +11,7 @@ TxProposalAction.create = function(opts) {
x.createdOn = Math.floor(Date.now() / 1000);
x.copayerId = opts.copayerId;
x.type = opts.type || (opts.signatures ? 'accept' : 'reject');
x.type = opts.type;
x.signatures = opts.signatures;
x.xpub = opts.xpub;
x.comment = opts.comment;

13
lib/server.js

@ -66,8 +66,7 @@ WalletService.getInstance = function() {
* @param {Object} opts
* @param {string} opts.copayerId - The copayer id making the request.
* @param {string} opts.message - The contents of the request to be signed.
* @param {string} opts.signature - Signature of message to be verified using the copayer's roPubKey / rwPubKey
* @param {string} opts.readOnly - Signature of message to be verified using the copayer's roPubKey / rwPubKey
* @param {string} opts.signature - Signature of message to be verified using the copayer's requestPubKey
*/
WalletService.getInstanceWithAuth = function(opts, cb) {
@ -79,8 +78,7 @@ WalletService.getInstanceWithAuth = function(opts, cb) {
if (err) return cb(err);
if (!copayer) return cb(new ClientError('NOTAUTHORIZED', 'Copayer not found'));
var pubKey = opts.readOnly ? copayer.roPubKey : copayer.rwPubKey;
var isValid = server._verifySignature(opts.message, opts.signature, pubKey);
var isValid = server._verifySignature(opts.message, opts.signature, copayer.requestPubKey);
if (!isValid)
return cb(new ClientError('NOTAUTHORIZED', 'Invalid signature'));
@ -314,7 +312,7 @@ WalletService.prototype.verifyMessageSignature = function(opts, cb) {
var copayer = wallet.getCopayer(self.copayerId);
var isValid = self._verifySignature(opts.message, opts.signature, copayer.rwPubKey);
var isValid = self._verifySignature(opts.message, opts.signature, copayer.requestPubKey);
return cb(null, isValid);
});
};
@ -370,8 +368,7 @@ WalletService.prototype._getUtxos = function(cb) {
// Get addresses for this wallet
self.storage.fetchAddresses(self.walletId, function(err, addresses) {
if (err) return cb(err);
if (addresses.length == 0)
return cb(null, []);
if (addresses.length == 0) return cb(null, []);
var addressStrs = _.pluck(addresses, 'address');
var addressToPath = _.indexBy(addresses, 'address'); // TODO : check performance
@ -500,7 +497,7 @@ WalletService.prototype.createTx = function(opts, cb) {
var copayer = wallet.getCopayer(self.copayerId);
var hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message);
if (!self._verifySignature(hash, opts.proposalSignature, copayer.rwPubKey))
if (!self._verifySignature(hash, opts.proposalSignature, copayer.requestPubKey))
return cb(new ClientError('Invalid proposal signature'));
var toAddress;

3
lib/storage.js

@ -81,8 +81,7 @@ Storage.prototype.storeWalletAndUpdateCopayersLookup = function(wallet, cb) {
_.each(wallet.copayers, function(copayer) {
var value = {
walletId: wallet.id,
roPubKey: copayer.roPubKey,
rwPubKey: copayer.rwPubKey,
requestPubKey: copayer.requestPubKey,
};
ops.push({
type: 'put',

85
lib/walletutils.js

@ -33,33 +33,6 @@ WalletUtils.signMessage = function(text, privKey) {
};
WalletUtils.accessFromData = function(data) {
if (data.xPrivKey)
return 'full';
if (data.rwPrivKey)
return 'readwrite';
if (data.roPrivKey)
return 'readonly';
return 'none';
};
WalletUtils.accessNameToLevel = function(name) {
if (name === 'full')
return 30;
if (name === 'readwrite')
return 20;
if (name === 'readonly')
return 10;
if (name === 'none')
return 0;
throw new Error('Bad access name:' + name);
};
WalletUtils.verifyMessage = function(text, signature, pubKey) {
$.checkArgument(text);
$.checkArgument(pubKey);
@ -160,38 +133,46 @@ WalletUtils.privateKeyToAESKey = function(privKey) {
return Bitcore.crypto.Hash.sha256(pk.toBuffer()).slice(0, 16).toString('base64');
};
WalletUtils.decryptWallet = function(data, password) {
$.checkArgument(data.enc);
var extraFields = JSON.parse(sjcl.decrypt(password, data.enc));
delete data.enc;
return _.extend(data, extraFields);
};
WalletUtils.signTxp = function(txp, xPrivKey) {
var self = this;
WalletUtils.sjclOpts = {
iter: 5000,
};
//Derive proper key to sign, for each input
var privs = [],
derived = {};
WalletUtils.encryptWallet = function(data, accessWithoutEncrytion, password) {
var network = new Bitcore.Address(txp.toAddress).network.name;
var xpriv = new Bitcore.HDPrivateKey(xPrivKey, network);
// Fields to encrypt, given the NOPASSWD access level
var fieldsEncryptByLevel = {
none: _.keys(data),
readonly: ['xPrivKey', 'rwPrivKey', 'publicKeyRing' ],
readwrite: ['xPrivKey', ],
full: [],
};
_.each(txp.inputs, function(i) {
if (!derived[i.path]) {
derived[i.path] = xpriv.derive(i.path).privateKey;
privs.push(derived[i.path]);
}
});
var fieldsEncrypt = fieldsEncryptByLevel[accessWithoutEncrytion];
$.checkState(!_.isUndefined(fieldsEncrypt));
var t = new Bitcore.Transaction();
var toEncrypt = _.pick(data, fieldsEncrypt);
var enc = sjcl.encrypt(password, JSON.stringify(toEncrypt), WalletUtils.sjclOpts);
var ret = _.omit(data, fieldsEncrypt);
ret.enc = enc;
return ret;
_.each(txp.inputs, function(i) {
t.from(i, i.publicKeys, txp.requiredSignatures);
});
t.to(txp.toAddress, txp.amount)
.change(txp.changeAddress.address);
var signatures = _.map(privs, function(priv, i) {
return t.getSignatures(priv);
});
signatures = _.map(_.sortBy(_.flatten(signatures), 'inputIndex'), function(s) {
return s.signature.toDER().toString('hex');
});
return signatures;
};
WalletUtils.getNetworkFromXPubKey = function(xPubKey) {
return xPubKey.substr(0, 4) == 'tpub' ? 'testnet' : 'livenet';
};
module.exports = WalletUtils;

1035
test/integration/clientApi.js → test/integration/client.js

File diff suppressed because it is too large
Loading…
Cancel
Save