commit
b7999988f6
|
@ -8,6 +8,7 @@ program = utils.configureCommander(program);
|
||||||
|
|
||||||
program
|
program
|
||||||
.option('-t, --testnet', 'Create a Testnet Wallet')
|
.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]')
|
.usage('[options] <walletName> <m-n> [copayerName]')
|
||||||
.parse(process.argv);
|
.parse(process.argv);
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ program = utils.configureCommander(program);
|
||||||
|
|
||||||
program
|
program
|
||||||
.option('-t, --testnet', 'Create a Testnet Extended Private Key')
|
.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);
|
.parse(process.argv);
|
||||||
|
|
||||||
var args = program.args;
|
var args = program.args;
|
||||||
|
|
|
@ -7,6 +7,7 @@ program = utils.configureCommander(program);
|
||||||
|
|
||||||
program
|
program
|
||||||
.usage('[options] <secret> [copayerName]')
|
.usage('[options] <secret> [copayerName]')
|
||||||
|
.option('-n, --nopasswd [level]', 'Set access for no password usage: none(default), readonly, readwrite, full', 'none')
|
||||||
.parse(process.argv);
|
.parse(process.argv);
|
||||||
|
|
||||||
var args = program.args;
|
var args = program.args;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var Client = require('../lib/client');
|
var Client = require('../lib/client');
|
||||||
|
var read = require('read')
|
||||||
|
|
||||||
var Utils = function() {};
|
var Utils = function() {};
|
||||||
|
|
||||||
|
@ -38,11 +39,27 @@ Utils.getClient = function(args) {
|
||||||
var storage = new Client.FileStorage({
|
var storage = new Client.FileStorage({
|
||||||
filename: args.file || process.env['BIT_FILE'],
|
filename: args.file || process.env['BIT_FILE'],
|
||||||
});
|
});
|
||||||
return new Client({
|
var c = new Client({
|
||||||
storage: storage,
|
storage: storage,
|
||||||
baseUrl: args.host || process.env['BIT_HOST'],
|
baseUrl: args.host || process.env['BIT_HOST'],
|
||||||
verbose: args.verbose
|
verbose: args.verbose,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (args.nopasswd)
|
||||||
|
c.setNopasswdAccess(args.nopasswd);
|
||||||
|
|
||||||
|
c.on('needPassword', function(cb) {
|
||||||
|
if (args.password) {
|
||||||
|
return cb(args.password);
|
||||||
|
} else {
|
||||||
|
read({ prompt: 'Password: ', silent: true }, function(er, password) {
|
||||||
|
return cb(password);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils.findOneTxProposal = function(txps, id) {
|
Utils.findOneTxProposal = function(txps, id) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ var util = require('util');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var log = require('npmlog');
|
var log = require('npmlog');
|
||||||
var request = require('request')
|
var request = require('request')
|
||||||
|
var events = require('events');
|
||||||
log.debug = log.verbose;
|
log.debug = log.verbose;
|
||||||
|
|
||||||
var Bitcore = require('bitcore')
|
var Bitcore = require('bitcore')
|
||||||
|
@ -112,6 +113,7 @@ function API(opts) {
|
||||||
this.request = request || opts.request;
|
this.request = request || opts.request;
|
||||||
this.baseUrl = opts.baseUrl || BASE_URL;
|
this.baseUrl = opts.baseUrl || BASE_URL;
|
||||||
this.basePath = this.baseUrl.replace(/http.?:\/\/[a-zA-Z0-9:-]*\//, '/');
|
this.basePath = this.baseUrl.replace(/http.?:\/\/[a-zA-Z0-9:-]*\//, '/');
|
||||||
|
this.noPasswdAccess = opts.noPasswdAccess || 'full';
|
||||||
if (this.verbose) {
|
if (this.verbose) {
|
||||||
log.level = 'debug';
|
log.level = 'debug';
|
||||||
} else {
|
} else {
|
||||||
|
@ -119,6 +121,8 @@ function API(opts) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
util.inherits(API, events.EventEmitter);
|
||||||
|
|
||||||
API.prototype._tryToCompleteFromServer = function(wcd, cb) {
|
API.prototype._tryToCompleteFromServer = function(wcd, cb) {
|
||||||
|
|
||||||
if (!wcd.walletPrivKey)
|
if (!wcd.walletPrivKey)
|
||||||
|
@ -142,7 +146,7 @@ API.prototype._tryToCompleteFromServer = function(wcd, cb) {
|
||||||
|
|
||||||
wcd.publicKeyRing = _.pluck(wallet.copayers, 'xPubKey')
|
wcd.publicKeyRing = _.pluck(wallet.copayers, 'xPubKey')
|
||||||
|
|
||||||
self.storage.save(wcd, function(err) {
|
self.save(wcd, function(err) {
|
||||||
return cb(err, wcd);
|
return cb(err, wcd);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -162,7 +166,7 @@ API.prototype._tryToCompleteFromData = function(wcd, toComplete, cb) {
|
||||||
return cb(ex);
|
return cb(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.storage.save(wcd, function(err) {
|
this.save(wcd, function(err) {
|
||||||
return cb(err, wcd);
|
return cb(err, wcd);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -177,15 +181,82 @@ API.prototype._tryToComplete = function(opts, wcd, cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// access: 'full' > 'readwrite' > readonly'
|
||||||
|
API.prototype._processWcdAfterRead = function(rawData, requiredAccess, cb) {
|
||||||
|
var WU = WalletUtils;
|
||||||
|
requiredAccess = requiredAccess || 'full';
|
||||||
|
|
||||||
API.prototype._load = function(cb) {
|
if (!rawData)
|
||||||
|
return cb(null, rawData);
|
||||||
|
|
||||||
|
var requiredAccessLevel = WU.accessNameToLevel(requiredAccess);
|
||||||
|
|
||||||
|
var access = WU.accessFromData(rawData);
|
||||||
|
var accessLevel = WU.accessNameToLevel(access);
|
||||||
|
|
||||||
|
// Is the data available?
|
||||||
|
if (requiredAccessLevel <= accessLevel)
|
||||||
|
return cb(null, rawData);
|
||||||
|
|
||||||
|
// Has any encrypted info?
|
||||||
|
if (!rawData.enc)
|
||||||
|
return cb('NOTAUTH');
|
||||||
|
|
||||||
|
// Decrypt it and try again
|
||||||
|
this.emit('needPassword', function(password) {
|
||||||
|
if (!password) return cb('No password');
|
||||||
|
|
||||||
|
try {
|
||||||
|
rawData = WU.decryptWallet(rawData, password);
|
||||||
|
} catch (e) {};
|
||||||
|
|
||||||
|
if (!rawData)
|
||||||
|
return cb('NOTAUTH');
|
||||||
|
|
||||||
|
access = WU.accessFromData(rawData);
|
||||||
|
accessLevel = WU.accessNameToLevel(access);
|
||||||
|
|
||||||
|
// Is the data available?
|
||||||
|
if (requiredAccessLevel <= accessLevel)
|
||||||
|
return cb(null, rawData);
|
||||||
|
|
||||||
|
return cb('NOTAUTH');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
API.prototype.setNopasswdAccess = function(noPasswdAccess) {
|
||||||
|
if (!_.contains(['none', 'readonly', 'readwrite', 'full'], noPasswdAccess))
|
||||||
|
throw new Error('Bad nopasswd access:' + noPasswdAccess);
|
||||||
|
|
||||||
|
this.noPasswdAccess = noPasswdAccess;
|
||||||
|
};
|
||||||
|
|
||||||
|
API.prototype._processWcdBeforeWrite = function(wcd, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
// Is any encrypted?
|
||||||
|
if (this.noPasswdAccess == 'full') {
|
||||||
|
return cb(null, wcd);
|
||||||
|
} else {
|
||||||
|
this.emit('needPassword', function(password) {
|
||||||
|
if (!password) return cb('No password given');
|
||||||
|
var ewcd = WalletUtils.encryptWallet(wcd, self.noPasswdAccess, password);
|
||||||
|
return cb(null, ewcd);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.storage.load(function(err, wcd) {
|
|
||||||
if (err || !wcd) {
|
|
||||||
|
API.prototype._load = function(opts, cb) {
|
||||||
|
var self = this;
|
||||||
|
$.shouldBeFunction(cb);
|
||||||
|
|
||||||
|
this.storage.load(function(err, rawdata) {
|
||||||
|
if (err || !rawdata) {
|
||||||
return cb(err || 'wcd file not found.');
|
return cb(err || 'wcd file not found.');
|
||||||
}
|
}
|
||||||
return cb(null, wcd);
|
self._processWcdAfterRead(rawdata, opts.requiredAccess, cb);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -198,7 +269,7 @@ API.prototype._load = function(cb) {
|
||||||
API.prototype._loadAndCheck = function(opts, cb) {
|
API.prototype._loadAndCheck = function(opts, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._load(function(err, wcd) {
|
this._load(opts, function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
if (!wcd.n || (wcd.n > 1 && wcd.publicKeyRing.length != wcd.n)) {
|
if (!wcd.n || (wcd.n > 1 && wcd.publicKeyRing.length != wcd.n)) {
|
||||||
|
@ -275,6 +346,18 @@ API.prototype._doJoinWallet = function(walletId, walletPrivKey, xPubKey, copayer
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
API.prototype.save = function(inWcd, cb) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self._processWcdBeforeWrite(inWcd, function(err, wcd) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
self.storage.save(wcd, function(err) {
|
||||||
|
return cb(err, null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
API.prototype.generateKey = function(network, cb) {
|
API.prototype.generateKey = function(network, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
network = network || 'livenet';
|
network = network || 'livenet';
|
||||||
|
@ -286,7 +369,8 @@ API.prototype.generateKey = function(network, cb) {
|
||||||
return cb(self.storage.getName() + ' already contains a wallet');
|
return cb(self.storage.getName() + ' already contains a wallet');
|
||||||
|
|
||||||
var wcd = _initWcd(network);
|
var wcd = _initWcd(network);
|
||||||
self.storage.save(wcd, function(err) {
|
|
||||||
|
self.save(wcd, function(err) {
|
||||||
return cb(err, null);
|
return cb(err, null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -327,7 +411,7 @@ API.prototype.createWallet = function(walletName, copayerName, m, n, network, cb
|
||||||
self._doJoinWallet(walletId, walletPrivKey, wcd.publicKeyRing[0], copayerName,
|
self._doJoinWallet(walletId, walletPrivKey, wcd.publicKeyRing[0], copayerName,
|
||||||
function(err, wallet) {
|
function(err, wallet) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
self.storage.save(wcd, function(err) {
|
self.save(wcd, function(err) {
|
||||||
return cb(err, n > 1 ? secret : null);
|
return cb(err, n > 1 ? secret : null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -338,7 +422,9 @@ API.prototype.createWallet = function(walletName, copayerName, m, n, network, cb
|
||||||
|
|
||||||
API.prototype.reCreateWallet = function(walletName, cb) {
|
API.prototype.reCreateWallet = function(walletName, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this._loadAndCheck({}, function(err, wcd) {
|
this._loadAndCheck({
|
||||||
|
requiredAccess: 'readonly',
|
||||||
|
}, function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
var walletPrivKey = new Bitcore.PrivateKey();
|
var walletPrivKey = new Bitcore.PrivateKey();
|
||||||
|
@ -386,7 +472,7 @@ API.prototype.joinWallet = function(secret, copayerName, cb) {
|
||||||
function(err, joinedWallet) {
|
function(err, joinedWallet) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
_addWalletToWcd(wcd, secretData.walletPrivKey, joinedWallet.m, joinedWallet.n);
|
_addWalletToWcd(wcd, secretData.walletPrivKey, joinedWallet.m, joinedWallet.n);
|
||||||
self.storage.save(wcd, cb);
|
self.save(wcd, cb);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -394,7 +480,9 @@ API.prototype.joinWallet = function(secret, copayerName, cb) {
|
||||||
API.prototype.getStatus = function(cb) {
|
API.prototype.getStatus = function(cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._load(function(err, wcd) {
|
this._load({
|
||||||
|
requiredAccess: 'readonly'
|
||||||
|
}, function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
var url = '/v1/wallets/';
|
var url = '/v1/wallets/';
|
||||||
|
@ -419,7 +507,9 @@ API.prototype.sendTxProposal = function(opts, cb) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._loadAndCheck({}, function(err, wcd) {
|
this._loadAndCheck({
|
||||||
|
requiredAccess: 'readonly',
|
||||||
|
}, function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
if (!wcd.rwPrivKey)
|
if (!wcd.rwPrivKey)
|
||||||
|
@ -442,7 +532,9 @@ API.prototype.sendTxProposal = function(opts, cb) {
|
||||||
API.prototype.createAddress = function(cb) {
|
API.prototype.createAddress = function(cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._loadAndCheck({}, function(err, wcd) {
|
this._loadAndCheck({
|
||||||
|
requiredAccess: 'readwrite',
|
||||||
|
}, function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
var url = '/v1/addresses/';
|
var url = '/v1/addresses/';
|
||||||
|
@ -464,7 +556,9 @@ API.prototype.createAddress = function(cb) {
|
||||||
API.prototype.getMainAddresses = function(opts, cb) {
|
API.prototype.getMainAddresses = function(opts, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._loadAndCheck({}, function(err, wcd) {
|
this._loadAndCheck({
|
||||||
|
requiredAccess: 'readonly',
|
||||||
|
}, function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
var url = '/v1/addresses/';
|
var url = '/v1/addresses/';
|
||||||
|
@ -486,7 +580,9 @@ API.prototype.getMainAddresses = function(opts, cb) {
|
||||||
API.prototype.getBalance = function(cb) {
|
API.prototype.getBalance = function(cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._loadAndCheck({}, function(err, wcd) {
|
this._loadAndCheck({
|
||||||
|
requiredAccess: 'readonly',
|
||||||
|
}, function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
var url = '/v1/balance/';
|
var url = '/v1/balance/';
|
||||||
self._doGetRequest(url, wcd, cb);
|
self._doGetRequest(url, wcd, cb);
|
||||||
|
@ -505,7 +601,9 @@ API.prototype.export = function(opts, cb) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
var access = opts.access || 'full';
|
var access = opts.access || 'full';
|
||||||
|
|
||||||
this._load(function(err, wcd) {
|
this._load({
|
||||||
|
requiredAccess: access,
|
||||||
|
}, function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
var v = [];
|
var v = [];
|
||||||
|
|
||||||
|
@ -570,7 +668,7 @@ API.prototype.import = function(str, cb) {
|
||||||
return cb('Invalid source wallet');
|
return cb('Invalid source wallet');
|
||||||
|
|
||||||
wcd.network = wcd.publicKeyRing[0].substr(0, 4) == 'tpub' ? 'testnet' : 'livenet';
|
wcd.network = wcd.publicKeyRing[0].substr(0, 4) == 'tpub' ? 'testnet' : 'livenet';
|
||||||
self.storage.save(wcd, function(err) {
|
self.save(wcd, function(err) {
|
||||||
return cb(err, WalletUtils.accessFromData(wcd));
|
return cb(err, WalletUtils.accessFromData(wcd));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -584,6 +682,7 @@ API.prototype.parseTxProposals = function(txData, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._loadAndCheck({
|
this._loadAndCheck({
|
||||||
|
requiredAccess: 'readonly',
|
||||||
toComplete: txData.toComplete
|
toComplete: txData.toComplete
|
||||||
}, function(err, wcd) {
|
}, function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
@ -614,7 +713,9 @@ API.prototype.parseTxProposals = function(txData, cb) {
|
||||||
API.prototype.getTxProposals = function(opts, cb) {
|
API.prototype.getTxProposals = function(opts, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._loadAndCheck({}, function(err, wcd) {
|
this._loadAndCheck({
|
||||||
|
requiredAccess: 'readonly'
|
||||||
|
}, function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
var url = '/v1/txproposals/';
|
var url = '/v1/txproposals/';
|
||||||
self._doGetRequest(url, wcd, function(err, txps) {
|
self._doGetRequest(url, wcd, function(err, txps) {
|
||||||
|
@ -678,7 +779,9 @@ API.prototype.getSignatures = function(txp, cb) {
|
||||||
$.checkArgument(txp.creatorId);
|
$.checkArgument(txp.creatorId);
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._loadAndCheck({}, function(err, wcd) {
|
this._loadAndCheck({
|
||||||
|
requiredAccess: 'full'
|
||||||
|
}, function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
if (!Verifier.checkTxProposal(wcd, txp)) {
|
if (!Verifier.checkTxProposal(wcd, txp)) {
|
||||||
|
@ -692,7 +795,9 @@ API.prototype.getSignatures = function(txp, cb) {
|
||||||
API.prototype.getEncryptedWalletData = function(cb) {
|
API.prototype.getEncryptedWalletData = function(cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._loadAndCheck({}, function(err, wcd) {
|
this._loadAndCheck({
|
||||||
|
requiredAccess: 'readonly'
|
||||||
|
}, function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
var toComplete = JSON.stringify(_.pick(wcd, WALLET_AIRGAPPED_TOCOMPLETE));
|
var toComplete = JSON.stringify(_.pick(wcd, WALLET_AIRGAPPED_TOCOMPLETE));
|
||||||
return cb(null, _encryptMessage(toComplete, WalletUtils.privateKeyToAESKey(wcd.roPrivKey)));
|
return cb(null, _encryptMessage(toComplete, WalletUtils.privateKeyToAESKey(wcd.roPrivKey)));
|
||||||
|
@ -706,7 +811,9 @@ API.prototype.signTxProposal = function(txp, cb) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._loadAndCheck({}, function(err, wcd) {
|
this._loadAndCheck({
|
||||||
|
requiredAccess: txp.signatures ? 'readwrite' : 'full'
|
||||||
|
}, function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
if (!Verifier.checkTxProposal(wcd, txp)) {
|
if (!Verifier.checkTxProposal(wcd, txp)) {
|
||||||
|
@ -729,7 +836,9 @@ API.prototype.rejectTxProposal = function(txp, reason, cb) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._loadAndCheck({},
|
this._loadAndCheck({
|
||||||
|
requiredAccess: 'readwrite'
|
||||||
|
},
|
||||||
function(err, wcd) {
|
function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
@ -744,7 +853,9 @@ API.prototype.rejectTxProposal = function(txp, reason, cb) {
|
||||||
API.prototype.broadcastTxProposal = function(txp, cb) {
|
API.prototype.broadcastTxProposal = function(txp, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._loadAndCheck({},
|
this._loadAndCheck({
|
||||||
|
requiredAccess: 'readwrite'
|
||||||
|
},
|
||||||
function(err, wcd) {
|
function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
@ -757,7 +868,9 @@ API.prototype.broadcastTxProposal = function(txp, cb) {
|
||||||
|
|
||||||
API.prototype.removeTxProposal = function(txp, cb) {
|
API.prototype.removeTxProposal = function(txp, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this._loadAndCheck({},
|
this._loadAndCheck({
|
||||||
|
requiredAccess: 'readwrite'
|
||||||
|
},
|
||||||
function(err, wcd) {
|
function(err, wcd) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
var url = '/v1/txproposals/' + txp.id;
|
var url = '/v1/txproposals/' + txp.id;
|
||||||
|
|
|
@ -40,9 +40,26 @@ WalletUtils.accessFromData = function(data) {
|
||||||
if (data.rwPrivKey)
|
if (data.rwPrivKey)
|
||||||
return 'readwrite';
|
return 'readwrite';
|
||||||
|
|
||||||
return 'readonly';
|
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) {
|
WalletUtils.verifyMessage = function(text, signature, pubKey) {
|
||||||
$.checkArgument(text);
|
$.checkArgument(text);
|
||||||
$.checkArgument(pubKey);
|
$.checkArgument(pubKey);
|
||||||
|
@ -143,4 +160,42 @@ WalletUtils.privateKeyToAESKey = function(privKey) {
|
||||||
return Bitcore.crypto.Hash.sha256(pk.toBuffer()).slice(0, 16).toString('base64');
|
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.sjclOpts = {
|
||||||
|
iter: 5000,
|
||||||
|
};
|
||||||
|
|
||||||
|
WalletUtils.encryptWallet = function(data, accessWithoutEncrytion, password) {
|
||||||
|
|
||||||
|
// Fields to encrypt, given the NOPASSWD access level
|
||||||
|
var fieldsEncryptByLevel = {
|
||||||
|
none: _.keys(data),
|
||||||
|
readonly: ['xPrivKey', 'rwPrivKey', 'publicKeyRing' ],
|
||||||
|
readwrite: ['xPrivKey', ],
|
||||||
|
full: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
var fieldsEncrypt = fieldsEncryptByLevel[accessWithoutEncrytion];
|
||||||
|
$.checkState(!_.isUndefined(fieldsEncrypt));
|
||||||
|
|
||||||
|
if (!_.every(fieldsEncrypt, function(k) {
|
||||||
|
return data[k];
|
||||||
|
})) throw new Error('Wallet does not contain necesary info to encrypt');
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = WalletUtils;
|
module.exports = WalletUtils;
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
"npmlog": "^0.1.1",
|
"npmlog": "^0.1.1",
|
||||||
"preconditions": "^1.0.7",
|
"preconditions": "^1.0.7",
|
||||||
"qr-image": "*",
|
"qr-image": "*",
|
||||||
|
"read": "^1.0.5",
|
||||||
"request": "^2.53.0",
|
"request": "^2.53.0",
|
||||||
"sjcl": "^1.0.2",
|
"sjcl": "^1.0.2",
|
||||||
"uuid": "*"
|
"uuid": "*"
|
||||||
|
|
|
@ -221,6 +221,108 @@ describe('client API ', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('Storage Encryption', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
_.each(_.range(3), function(i) {
|
||||||
|
clients[i].on('needPassword', function(cb) {
|
||||||
|
return cb('1234#$@#%F,./.**');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('full encryption roundtrip', function(done) {
|
||||||
|
clients[0].setNopasswdAccess('none');
|
||||||
|
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
|
||||||
|
// Load it
|
||||||
|
var wcd = JSON.parse(fsmock._get('client0'));
|
||||||
|
fsmock._set('client1', wcd);
|
||||||
|
clients[1].getBalance(function(err, bal0) {
|
||||||
|
should.not.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail if wrong password', function(done) {
|
||||||
|
clients[0].setNopasswdAccess('none');
|
||||||
|
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
|
||||||
|
// Load it
|
||||||
|
var wcd = JSON.parse(fsmock._get('client0'));
|
||||||
|
fsmock._set('client4', wcd);
|
||||||
|
|
||||||
|
clients[4].on('needPassword', function(cb) {
|
||||||
|
return cb('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
clients[4].getBalance(function(err, bal0) {
|
||||||
|
err.should.equal('NOTAUTH');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should encrypt everything', function(done) {
|
||||||
|
clients[0].setNopasswdAccess('none');
|
||||||
|
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
var wcd = JSON.parse(fsmock._get('client0'));
|
||||||
|
_.keys(wcd).should.deep.equal(['enc']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should encrypt xpriv access', function(done) {
|
||||||
|
clients[0].setNopasswdAccess('readwrite');
|
||||||
|
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
var wcd = JSON.parse(fsmock._get('client0'));
|
||||||
|
should.exist(wcd.enc);
|
||||||
|
should.not.exist(wcd.xpriv);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should encrypt rwkey', function(done) {
|
||||||
|
clients[0].setNopasswdAccess('readonly');
|
||||||
|
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
var wcd = JSON.parse(fsmock._get('client0'));
|
||||||
|
should.exist(wcd.enc);
|
||||||
|
should.not.exist(wcd.xpriv);
|
||||||
|
should.not.exist(wcd.rwPrivKey);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
_.each(['full', 'readwrite', 'readonly', 'none'], function(k) {
|
||||||
|
it('full encryption roundtrip: type:' + k, function(done) {
|
||||||
|
clients[0].setNopasswdAccess(k);
|
||||||
|
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
|
||||||
|
// Load it
|
||||||
|
var wcd = JSON.parse(fsmock._get('client0'));
|
||||||
|
fsmock._set('client1', wcd);
|
||||||
|
clients[1].getBalance(function(err, bal0) {
|
||||||
|
should.not.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('should not ask for password if not needed (readonly)', function(done) {});
|
||||||
|
it.skip('should not ask for password if not needed (readwrite)', function(done) {});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('Wallet Creation', function() {
|
describe('Wallet Creation', function() {
|
||||||
it('should check balance in a 1-1 ', function(done) {
|
it('should check balance in a 1-1 ', function(done) {
|
||||||
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
|
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
|
||||||
|
@ -281,7 +383,7 @@ describe('client API ', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
|
||||||
// Get right response
|
// Get right response
|
||||||
clients[0]._load(function(err, data) {
|
clients[0]._load({}, function(err, data) {
|
||||||
var url = '/v1/wallets/';
|
var url = '/v1/wallets/';
|
||||||
clients[0]._doGetRequest(url, data, function(err, x) {
|
clients[0]._doGetRequest(url, data, function(err, x) {
|
||||||
|
|
||||||
|
@ -305,7 +407,7 @@ describe('client API ', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
|
||||||
// Get right response
|
// Get right response
|
||||||
var data = clients[0]._load(function(err, data) {
|
var data = clients[0]._load({}, function(err, data) {
|
||||||
var url = '/v1/wallets/';
|
var url = '/v1/wallets/';
|
||||||
clients[0]._doGetRequest(url, data, function(err, x) {
|
clients[0]._doGetRequest(url, data, function(err, x) {
|
||||||
|
|
||||||
|
@ -330,7 +432,7 @@ describe('client API ', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
|
||||||
// Get right response
|
// Get right response
|
||||||
var data = clients[0]._load(function(err, data) {
|
var data = clients[0]._load({}, function(err, data) {
|
||||||
var url = '/v1/wallets/';
|
var url = '/v1/wallets/';
|
||||||
clients[0]._doGetRequest(url, data, function(err, x) {
|
clients[0]._doGetRequest(url, data, function(err, x) {
|
||||||
|
|
||||||
|
@ -362,6 +464,12 @@ describe('client API ', function() {
|
||||||
delete data.rwPrivKey;
|
delete data.rwPrivKey;
|
||||||
fsmock._set('client0', JSON.stringify(data));
|
fsmock._set('client0', JSON.stringify(data));
|
||||||
data.rwPrivKey = null;
|
data.rwPrivKey = null;
|
||||||
|
|
||||||
|
// Overwrite client's API auth checks
|
||||||
|
clients[0]._processWcdAfterRead = function(rawData, xx, cb) {
|
||||||
|
return cb(null, rawData);
|
||||||
|
};
|
||||||
|
|
||||||
clients[0].createAddress(function(err, x0) {
|
clients[0].createAddress(function(err, x0) {
|
||||||
err.code.should.equal('NOTAUTHORIZED');
|
err.code.should.equal('NOTAUTHORIZED');
|
||||||
done();
|
done();
|
||||||
|
@ -378,6 +486,11 @@ describe('client API ', function() {
|
||||||
clients[1].import(str, function(err, wallet) {
|
clients[1].import(str, function(err, wallet) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
|
||||||
|
// Overwrite client's API auth checks
|
||||||
|
clients[1]._processWcdAfterRead = function(rawData, xx, cb) {
|
||||||
|
return cb(null, rawData);
|
||||||
|
};
|
||||||
|
|
||||||
clients[1].createAddress(function(err, x0) {
|
clients[1].createAddress(function(err, x0) {
|
||||||
err.code.should.equal('NOTAUTHORIZED');
|
err.code.should.equal('NOTAUTHORIZED');
|
||||||
clients[0].createAddress(function(err, x0) {
|
clients[0].createAddress(function(err, x0) {
|
||||||
|
@ -425,6 +538,12 @@ describe('client API ', function() {
|
||||||
};
|
};
|
||||||
clients[1].sendTxProposal(opts, function(err, x) {
|
clients[1].sendTxProposal(opts, function(err, x) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
|
||||||
|
// Overwrite client's API auth checks
|
||||||
|
clients[1]._processWcdAfterRead = function(rawData, xx, cb) {
|
||||||
|
return cb(null, rawData);
|
||||||
|
};
|
||||||
|
|
||||||
clients[1].signTxProposal(x, function(err, tx) {
|
clients[1].signTxProposal(x, function(err, tx) {
|
||||||
err.code.should.be.equal('BADSIGNATURES');
|
err.code.should.be.equal('BADSIGNATURES');
|
||||||
clients[1].getTxProposals({}, function(err, txs) {
|
clients[1].getTxProposals({}, function(err, txs) {
|
||||||
|
@ -656,7 +775,7 @@ describe('client API ', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
|
||||||
// Get right response
|
// Get right response
|
||||||
clients[0]._load(function(err, data) {
|
clients[0]._load({}, function(err, data) {
|
||||||
var url = '/v1/addresses/';
|
var url = '/v1/addresses/';
|
||||||
clients[0]._doPostRequest(url, {}, data, function(err, address) {
|
clients[0]._doPostRequest(url, {}, data, function(err, address) {
|
||||||
|
|
||||||
|
@ -680,7 +799,7 @@ describe('client API ', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
|
||||||
// Get right response
|
// Get right response
|
||||||
clients[0]._load(function(err, data) {
|
clients[0]._load({}, function(err, data) {
|
||||||
var url = '/v1/addresses/';
|
var url = '/v1/addresses/';
|
||||||
clients[0]._doPostRequest(url, {}, data, function(err, address) {
|
clients[0]._doPostRequest(url, {}, data, function(err, address) {
|
||||||
|
|
||||||
|
@ -865,7 +984,7 @@ describe('client API ', function() {
|
||||||
|
|
||||||
|
|
||||||
// Get right response
|
// Get right response
|
||||||
clients[0]._load(function(err, data) {
|
clients[0]._load({}, function(err, data) {
|
||||||
var url = '/v1/txproposals/';
|
var url = '/v1/txproposals/';
|
||||||
clients[0]._doGetRequest(url, data, function(err, txps) {
|
clients[0]._doGetRequest(url, data, function(err, txps) {
|
||||||
|
|
||||||
|
@ -904,7 +1023,7 @@ describe('client API ', function() {
|
||||||
|
|
||||||
|
|
||||||
// Get right response
|
// Get right response
|
||||||
clients[0]._load(function(err, data) {
|
clients[0]._load({}, function(err, data) {
|
||||||
var url = '/v1/txproposals/';
|
var url = '/v1/txproposals/';
|
||||||
clients[0]._doGetRequest(url, data, function(err, txps) {
|
clients[0]._doGetRequest(url, data, function(err, txps) {
|
||||||
|
|
||||||
|
@ -943,7 +1062,7 @@ describe('client API ', function() {
|
||||||
|
|
||||||
|
|
||||||
// Get right response
|
// Get right response
|
||||||
clients[0]._load(function(err, data) {
|
clients[0]._load({}, function(err, data) {
|
||||||
var url = '/v1/txproposals/';
|
var url = '/v1/txproposals/';
|
||||||
clients[0]._doGetRequest(url, data, function(err, txps) {
|
clients[0]._doGetRequest(url, data, function(err, txps) {
|
||||||
// Tamper data
|
// Tamper data
|
||||||
|
|
Loading…
Reference in New Issue