Browse Source

refactor client

activeAddress
Ivan Socolsky 10 years ago
parent
commit
c3a64761b6
  1. 46
      lib/client/airgapped.js
  2. 846
      lib/client/api.js
  3. 110
      lib/client/credentials.js
  4. 33
      lib/client/filestorage.js
  5. 4
      lib/client/index.js
  6. 20
      lib/client/verifier.js
  7. 4
      lib/expressapp.js
  8. 6
      lib/model/copayer.js
  9. 2
      lib/model/txproposalaction.js
  10. 9
      lib/server.js
  11. 6
      lib/walletutils.js
  12. 840
      test/integration/clientApi.js

46
lib/client/airgapped.js

@ -0,0 +1,46 @@
'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() {
var cred = this.credentials;
return {
network: cred.network,
xPubKey: cred.xPubKey,
requestPrivKey: cred.requestPrivKey,
};
};
AirGapped.prototype.signTxProposals = function(txps, cb) {
return cb(null, _.map(txps, function(txp) {
return {};
}));
};
module.exports = AirGapped;

846
lib/client/api.js

File diff suppressed because it is too large

110
lib/client/credentials.js

@ -0,0 +1,110 @@
'use strict';
var $ = require('preconditions').singleton();
var _ = require('lodash');
var Bitcore = require('bitcore');
var WalletUtils = require('../walletutils');
var FIELDS = [
'network',
'xPrivKey',
'xPubKey',
// 'roPrivKey',
'requestPrivKey',
'copayerId',
'publicKeyRing',
'walletId',
'walletName',
'm',
'n',
'walletPrivKey',
'sharedEncryptingKey',
'copayerName',
];
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(network, xPrivKey) {
var x = new Credentials();
x.network = network;
x.xPrivKey = xPrivKey;
x._expand();
return x;
};
Credentials.fromAirGapped = function(network, xPubKey, requestPrivKey) {
var x = new Credentials();
x.network = network;
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.roPrivKey = xPrivKey.derive('m/1/0').privateKey.toString();
this.requestPrivKey = xPrivKey.derive('m/1/1').privateKey.toString();
}
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.addPublicKeyRing = function(publicKeyRing) {
this.publicKeyRing = _.clone(publicKeyRing);
};
Credentials.prototype.canSign = function() {
return !!this.xPrivKey;
};
Credentials.prototype.isComplete = function() {
if (!this.walletId) return false;
if (!this.publicKeyRing || this.publicKeyRing.length != this.n) return false;
return true;
};
module.exports = Credentials;

33
lib/client/filestorage.js

@ -1,33 +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;

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');

20
lib/client/verifier.js

@ -11,16 +11,15 @@ 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) {
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) {
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 +43,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 +51,10 @@ Verifier.checkCopayers = function(copayers, walletPrivKey, myXPrivKey, n) {
};
Verifier.checkTxProposal = function(data, txp) {
Verifier.checkTxProposal = function(credentials, txp) {
$.checkArgument(txp.creatorId);
var creatorXPubKey = _.find(data.publicKeyRing, function(xPubKey) {
var creatorXPubKey = _.find(credentials.publicKeyRing, function(xPubKey) {
if (WalletUtils.xPubToCopayerId(xPubKey) === txp.creatorId) return true;
});
@ -71,7 +69,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);

6
lib/model/copayer.js

@ -11,7 +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() {
@ -31,7 +30,6 @@ 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.addressManager = AddressManager.create({
copayerIndex: opts.copayerIndex
@ -63,10 +61,6 @@ 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);
};

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;

9
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 rwPubKey
*/
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.rwPubKey);
if (!isValid)
return cb(new ClientError('NOTAUTHORIZED', 'Invalid signature'));
@ -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

6
lib/walletutils.js

@ -37,7 +37,7 @@ WalletUtils.accessFromData = function(data) {
if (data.xPrivKey)
return 'full';
if (data.rwPrivKey)
if (data.requestPrivKey)
return 'readwrite';
if (data.roPrivKey)
@ -177,7 +177,7 @@ WalletUtils.encryptWallet = function(data, accessWithoutEncrytion, password) {
// Fields to encrypt, given the NOPASSWD access level
var fieldsEncryptByLevel = {
none: _.keys(data),
readonly: ['xPrivKey', 'rwPrivKey', 'publicKeyRing' ],
readonly: ['xPrivKey', 'requestPrivKey', 'publicKeyRing'],
readwrite: ['xPrivKey', ],
full: [],
};
@ -187,7 +187,7 @@ WalletUtils.encryptWallet = function(data, accessWithoutEncrytion, password) {
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;

840
test/integration/clientApi.js

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