WIP: better lock

This commit is contained in:
Matias Alejo Garcia 2014-08-14 18:46:42 -04:00
commit bcb61810d5
7 changed files with 121 additions and 139 deletions

View file

@ -1,47 +1,44 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('OpenController', angular.module('copayApp.controllers').controller('OpenController', function($scope, $rootScope, $location, walletFactory, controllerUtils, Passphrase, notification) {
function($scope, $rootScope, $location, walletFactory, controllerUtils, Passphrase, notification) { controllerUtils.redirIfLogged();
controllerUtils.redirIfLogged();
var cmp = function(o1, o2) { var cmp = function(o1, o2) {
var v1 = o1.show.toLowerCase(), var v1 = o1.show.toLowerCase(),
v2 = o2.show.toLowerCase(); v2 = o2.show.toLowerCase();
return v1 > v2 ? 1 : (v1 < v2) ? -1 : 0; return v1 > v2 ? 1 : (v1 < v2) ? -1 : 0;
}; };
$rootScope.fromSetup = false; $rootScope.fromSetup = false;
$scope.loading = false; $scope.loading = false;
$scope.wallets = walletFactory.getWallets().sort(cmp); $scope.wallets = walletFactory.getWallets().sort(cmp);
$scope.selectedWalletId = walletFactory.storage.getLastOpened() || ($scope.wallets[0] && $scope.wallets[0].id); $scope.selectedWalletId = walletFactory.storage.getLastOpened() || ($scope.wallets[0] && $scope.wallets[0].id);
$scope.openPassword = ''; $scope.openPassword = '';
$scope.isMobile = !!window.cordova; $scope.isMobile = !!window.cordova;
$scope.open = function(form) { $scope.open = function(form) {
if (form && form.$invalid) { if (form && form.$invalid) {
notification.error('Error', 'Please enter the required fields'); notification.error('Error', 'Please enter the required fields');
return;
}
$scope.loading = true;
var password = form.openPassword.$modelValue;
Passphrase.getBase64Async(password, function(passphrase) {
var w, errMsg;
try {
w = walletFactory.open($scope.selectedWalletId, passphrase);
} catch (e) {
errMsg = e.message;
};
if (!w) {
$scope.loading = false;
notification.error('Error', errMsg || 'Wrong password');
$rootScope.$digest();
return; return;
} }
controllerUtils.startNetwork(w, $scope);
});
};
$scope.loading = true; });
var password = form.openPassword.$modelValue;
Passphrase.getBase64Async(password, function(passphrase) {
var w, errMsg;
try {
w = walletFactory.open($scope.selectedWalletId, {
passphrase: passphrase
});
} catch (e) {
errMsg = e.message;
};
if (!w) {
$scope.loading = false;
notification.error('Error', errMsg || 'Wrong password');
$rootScope.$digest();
return;
}
controllerUtils.startNetwork(w, $scope);
});
};
});

View file

@ -2,10 +2,10 @@
var http = require('http'); var http = require('http');
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
var nodeUtil = require('util');
var async = require('async'); var async = require('async');
var preconditions = require('preconditions').singleton(); var preconditions = require('preconditions').singleton();
var parseBitcoinURI = require('./HDPath').parseBitcoinURI; var parseBitcoinURI = require('./HDPath').parseBitcoinURI;
var util = require('util');
var bitcore = require('bitcore'); var bitcore = require('bitcore');
var bignum = bitcore.Bignum; var bignum = bitcore.Bignum;
@ -23,6 +23,7 @@ var PublicKeyRing = require('./PublicKeyRing');
var TxProposal = require('./TxProposal'); var TxProposal = require('./TxProposal');
var TxProposals = require('./TxProposals'); var TxProposals = require('./TxProposals');
var PrivateKey = require('./PrivateKey'); var PrivateKey = require('./PrivateKey');
var WalletLock = require('./WalletLock');
var copayConfig = require('../../../config'); var copayConfig = require('../../../config');
function Wallet(opts) { function Wallet(opts) {
@ -45,9 +46,11 @@ function Wallet(opts) {
this.log('creating ' + opts.requiredCopayers + ' of ' + opts.totalCopayers + ' wallet'); this.log('creating ' + opts.requiredCopayers + ' of ' + opts.totalCopayers + ' wallet');
this.id = opts.id || Wallet.getRandomId(); this.id = opts.id || Wallet.getRandomId();
this.lock = new WalletLock(this.storage, this.id, opts.lockTimeOutMin);
this.name = opts.name; this.name = opts.name;
this.ignoreLock = opts.ignoreLock;
this.verbose = opts.verbose; this.verbose = opts.verbose;
this.publicKeyRing.walletId = this.id; this.publicKeyRing.walletId = this.id;
this.txProposals.walletId = this.id; this.txProposals.walletId = this.id;
@ -64,7 +67,7 @@ function Wallet(opts) {
this.network.setHexNonces(opts.networkNonces); this.network.setHexNonces(opts.networkNonces);
} }
nodeUtil.inherits(Wallet, EventEmitter); util.inherits(Wallet, EventEmitter);
Wallet.builderOpts = { Wallet.builderOpts = {
lockTime: null, lockTime: null,
@ -98,27 +101,6 @@ Wallet.prototype.connectToAll = function() {
} }
}; };
Wallet.prototype.getLock = function() {
return this.storage.getLock(this.id);
};
Wallet.prototype.setLock = function() {
return this.storage.setLock(this.id);
};
Wallet.prototype.unlock = function() {
this.storage.removeLock(this.id);
};
Wallet.prototype.checkAndLock = function() {
if (this.getLock()) {
return true;
}
this.setLock();
return false;
};
Wallet.prototype._handleIndexes = function(senderId, data, isInbound) { Wallet.prototype._handleIndexes = function(senderId, data, isInbound) {
this.log('RECV INDEXES:', data); this.log('RECV INDEXES:', data);
var inIndexes = HDParams.fromList(data.indexes); var inIndexes = HDParams.fromList(data.indexes);
@ -438,11 +420,6 @@ Wallet.prototype.netStart = function(callback) {
var self = this; var self = this;
var net = this.network; var net = this.network;
if (this.checkAndLock() && !this.ignoreLock) {
this.emit('locked');
return;
}
net.removeAllListeners(); net.removeAllListeners();
net.on('connect', self._handleConnect.bind(self)); net.on('connect', self._handleConnect.bind(self));
net.on('disconnect', self._handleDisconnect.bind(self)); net.on('disconnect', self._handleDisconnect.bind(self));
@ -519,7 +496,13 @@ Wallet.prototype.getRegisteredPeerIds = function() {
return this.registeredPeerIds; return this.registeredPeerIds;
}; };
Wallet.prototype.keepAlive = function() {
this.lock.keepAlive();
};
Wallet.prototype.store = function() { Wallet.prototype.store = function() {
this.keepAlive();
var wallet = this.toObj(); var wallet = this.toObj();
this.storage.setFromObj(this.id, wallet); this.storage.setFromObj(this.id, wallet);
this.log('Wallet stored'); this.log('Wallet stored');
@ -556,8 +539,8 @@ Wallet.fromObj = function(o, storage, network, blockchain) {
opts.storage = storage; opts.storage = storage;
opts.network = network; opts.network = network;
opts.blockchain = blockchain; opts.blockchain = blockchain;
var w = new Wallet(opts);
return w; return new Wallet(opts);
}; };
Wallet.prototype.toEncryptedObj = function() { Wallet.prototype.toEncryptedObj = function() {
@ -709,7 +692,7 @@ Wallet.prototype.sign = function(ntxid, cb) {
var self = this; var self = this;
setTimeout(function() { setTimeout(function() {
var myId = self.getMyCopayerId(); var myId = self.getMyCopayerId();
var txp = self.txProposals.get(ntxid); var txp = self.txProposals.get(ntxid);
// if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) { // if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) {
// if (cb) cb(false); // if (cb) cb(false);
// } // }
@ -782,7 +765,9 @@ Wallet.prototype.createPaymentTx = function(options, cb) {
var self = this; var self = this;
if (typeof options === 'string') { if (typeof options === 'string') {
options = { uri: options }; options = {
uri: options
};
} }
options.uri = options.uri || options.url; options.uri = options.uri || options.url;
@ -824,7 +809,9 @@ Wallet.prototype.fetchPaymentTx = function(options, cb) {
options = options || {}; options = options || {};
if (typeof options === 'string') { if (typeof options === 'string') {
options = { uri: options }; options = {
uri: options
};
} }
options.uri = options.uri || options.url; options.uri = options.uri || options.url;
options.fetch = true; options.fetch = true;
@ -980,8 +967,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) {
var refund_outputs = []; var refund_outputs = [];
options.refund_to = options.refund_to options.refund_to = options.refund_to || this.publicKeyRing.getPubKeys(0, false, this.getMyCopayerId())[0];
|| this.publicKeyRing.getPubKeys(0, false, this.getMyCopayerId())[0];
if (options.refund_to) { if (options.refund_to) {
var total = txp.merchant.pr.pd.outputs.reduce(function(total, _, i) { var total = txp.merchant.pr.pd.outputs.reduce(function(total, _, i) {
@ -1003,23 +989,23 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) {
rpo.set('amount', +total.toString(10)); rpo.set('amount', +total.toString(10));
rpo.set('script', rpo.set('script',
Buffer.concat([ Buffer.concat([
new Buffer([ new Buffer([
118, // OP_DUP 118, // OP_DUP
169, // OP_HASH160 169, // OP_HASH160
76, // OP_PUSHDATA1 76, // OP_PUSHDATA1
20, // number of bytes 20, // number of bytes
]), ]),
// needs to be ripesha'd // needs to be ripesha'd
bitcore.util.sha256ripe160(options.refund_to), bitcore.util.sha256ripe160(options.refund_to),
new Buffer([ new Buffer([
136, // OP_EQUALVERIFY 136, // OP_EQUALVERIFY
172 // OP_CHECKSIG 172 // OP_CHECKSIG
]) ])
]) ])
); );
refund_outputs.push(rpo.message); refund_outputs.push(rpo.message);
} }
// We send this to the serve after receiving a PaymentRequest // We send this to the serve after receiving a PaymentRequest
@ -1031,8 +1017,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) {
pay.set('transactions', [tx.serialize()]); pay.set('transactions', [tx.serialize()]);
pay.set('refund_to', refund_outputs); pay.set('refund_to', refund_outputs);
options.memo = options.memo || options.comment options.memo = options.memo || options.comment || 'Hi server, I would like to give you some money.';
|| 'Hi server, I would like to give you some money.';
pay.set('memo', options.memo); pay.set('memo', options.memo);
@ -1188,8 +1173,8 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent)
merchantData.total = merchantData.total.toString(10); merchantData.total = merchantData.total.toString(10);
var b = new Builder(opts) var b = new Builder(opts)
.setUnspent(unspent) .setUnspent(unspent)
.setOutputs(outs); .setOutputs(outs);
merchantData.pr.pd.outputs.forEach(function(output, i) { merchantData.pr.pd.outputs.forEach(function(output, i) {
var script = { var script = {
@ -1250,9 +1235,7 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent)
Wallet.prototype.verifyPaymentRequest = function(ntxid) { Wallet.prototype.verifyPaymentRequest = function(ntxid) {
if (!ntxid) return false; if (!ntxid) return false;
var txp = typeof ntxid !== 'object' var txp = typeof ntxid !== 'object' ? this.txProposals.get(ntxid) : ntxid;
? this.txProposals.get(ntxid)
: ntxid;
// If we're not a payment protocol proposal, ignore. // If we're not a payment protocol proposal, ignore.
if (!txp.merchant) return true; if (!txp.merchant) return true;
@ -1358,8 +1341,7 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) {
} }
// Make sure the tx's output script and values match the payment request's. // Make sure the tx's output script and values match the payment request's.
if (av.toString('hex') !== ev.toString('hex') if (av.toString('hex') !== ev.toString('hex') || as.toString('hex') !== es.toString('hex')) {
|| as.toString('hex') !== es.toString('hex')) {
// Verifiable outputs do not match outputs of merchant // Verifiable outputs do not match outputs of merchant
// data. We should not sign this transaction proposal! // data. We should not sign this transaction proposal!
return false; return false;
@ -1384,10 +1366,9 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) {
// Actual script // Actual script
var as = new Buffer(ro.script.buffer, 'hex') var as = new Buffer(ro.script.buffer, 'hex')
.slice(ro.script.offset, ro.script.limit); .slice(ro.script.offset, ro.script.limit);
if (av.toString('hex') !== ev.toString('hex') if (av.toString('hex') !== ev.toString('hex') || as.toString('hex') !== es.toString('hex')) {
|| as.toString('hex') !== es.toString('hex')) {
return false; return false;
} }
} }
@ -1506,14 +1487,19 @@ Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb)
if (typeof amountSatStr === 'function') { if (typeof amountSatStr === 'function') {
var cb = amountSatStr; var cb = amountSatStr;
var merchant = toAddress; var merchant = toAddress;
return this.createPaymentTx({ uri: merchant }, cb); return this.createPaymentTx({
uri: merchant
}, cb);
} }
if (typeof comment === 'function') { if (typeof comment === 'function') {
var cb = comment; var cb = comment;
var merchant = toAddress; var merchant = toAddress;
var comment = amountSatStr; var comment = amountSatStr;
return this.createPaymentTx({ uri: merchant, memo: comment }, cb); return this.createPaymentTx({
uri: merchant,
memo: comment
}, cb);
} }
if (typeof opts === 'function') { if (typeof opts === 'function') {
@ -1779,7 +1765,9 @@ Wallet.prototype.verifySignedJson = function(senderId, payload, signature) {
Wallet.request = function(options, callback) { Wallet.request = function(options, callback) {
if (typeof options === 'string') { if (typeof options === 'string') {
options = { uri: options }; options = {
uri: options
};
} }
options.method = options.method || 'GET'; options.method = options.method || 'GET';
@ -1794,8 +1782,7 @@ Wallet.request = function(options, callback) {
this._error = cb; this._error = cb;
return this; return this;
}, },
_success: function() { _success: function() {;
;
}, },
_error: function(_, err) { _error: function(_, err) {
throw err; throw err;

View file

@ -108,6 +108,7 @@ WalletFactory.prototype.create = function(opts) {
var requiredCopayers = opts.requiredCopayers || this.walletDefaults.requiredCopayers; var requiredCopayers = opts.requiredCopayers || this.walletDefaults.requiredCopayers;
var totalCopayers = opts.totalCopayers || this.walletDefaults.totalCopayers; var totalCopayers = opts.totalCopayers || this.walletDefaults.totalCopayers;
opts.lockTimeoutMin = this.walletDefaults.idleDurationMin;
opts.publicKeyRing = opts.publicKeyRing || new PublicKeyRing({ opts.publicKeyRing = opts.publicKeyRing || new PublicKeyRing({
networkName: this.networkName, networkName: this.networkName,
@ -137,6 +138,7 @@ WalletFactory.prototype.create = function(opts) {
opts.requiredCopayers = requiredCopayers; opts.requiredCopayers = requiredCopayers;
opts.totalCopayers = totalCopayers; opts.totalCopayers = totalCopayers;
opts.version = opts.version || this.version; opts.version = opts.version || this.version;
var w = new Wallet(opts); var w = new Wallet(opts);
w.store(); w.store();
this.storage.setLastOpened(w.id); this.storage.setLastOpened(w.id);
@ -166,16 +168,11 @@ WalletFactory.prototype._checkNetwork = function(inNetworkName) {
} }
}; };
WalletFactory.prototype.open = function(walletId, opts) { WalletFactory.prototype.open = function(walletId, passphrase) {
opts = opts || {}; this.storage._setPassphrase(passphrase);
opts.id = walletId; var w = this.read(walletId, opts);
opts.verbose = this.verbose; if (w)
this.storage._setPassphrase(opts.passphrase);
var w = this.read(walletId);
if (w) {
w.store(); w.store();
}
this.storage.setLastOpened(walletId); this.storage.setLastOpened(walletId);
return w; return w;

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
var CryptoJS = require('node-cryptojs-aes').CryptoJS; var CryptoJS = require('node-cryptojs-aes').CryptoJS;
var bitcore = require('bitcore');
var id = 0; var id = 0;
function Storage(opts) { function Storage(opts) {
@ -93,6 +93,15 @@ Storage.prototype.removeGlobal = function(k) {
this.localStorage.removeItem(k); this.localStorage.removeItem(k);
}; };
Storage.prototype.getSessionId = function() {
var sessionId = this.sessionStorage.getItem('sessionId');
if (!sessionId) {
sessionId = bitcore.getRandomBuffer(8).toString('hex');
this.sessionStorage.setItem(sessionId, 'sessionId');
}
return sessionId;
};
Storage.prototype._key = function(walletId, k) { Storage.prototype._key = function(walletId, k) {
return walletId + '::' + k; return walletId + '::' + k;
}; };
@ -132,7 +141,8 @@ Storage.prototype.getWalletIds = function() {
if (split.length == 2) { if (split.length == 2) {
var walletId = split[0]; var walletId = split[0];
if (walletId === 'nameFor') continue; if (walletId === 'nameFor' || walletId === 'lock')
continue;
if (typeof uniq[walletId] === 'undefined') { if (typeof uniq[walletId] === 'undefined') {
walletIds.push(walletId); walletIds.push(walletId);
@ -180,8 +190,9 @@ Storage.prototype.getLastOpened = function() {
return this.getGlobal('lastOpened'); return this.getGlobal('lastOpened');
} }
// Lock related
Storage.prototype.setLock = function(walletId) { Storage.prototype.setLock = function(walletId) {
this.setGlobal(this._key(walletId, 'Lock'), true); this.setGlobal(this._key(walletId, 'Lock'), this.sessionId());
} }
Storage.prototype.getLock = function(walletId) { Storage.prototype.getLock = function(walletId) {

View file

@ -74,8 +74,8 @@ angular
.html5Mode(false) .html5Mode(false)
.hashPrefix('!'); .hashPrefix('!');
// IDLE timeout // IDLE timeout
$idleProvider.idleDuration(15 * 60); // in seconds $idleProvider.idleDuration(config.wallet.idleDurationMin * 60); // in seconds
$idleProvider.warningDuration(10); // in seconds $idleProvider.warningDuration(20); // in seconds
}) })
.run(function($rootScope, $location, $idle) { .run(function($rootScope, $location, $idle) {
$idle.watch(); $idle.watch();

View file

@ -35,6 +35,11 @@ FakeStorage.prototype.getLock = function(id) {
return this.storage[id + '::lock']; return this.storage[id + '::lock'];
} }
FakeStorage.prototype.getSessionId = function() {
return this.sessionId || 'aSessionId';
};
FakeStorage.prototype.removeLock = function(id) { FakeStorage.prototype.removeLock = function(id) {
delete this.storage[id + '::lock']; delete this.storage[id + '::lock'];
} }

View file

@ -182,7 +182,6 @@ describe('Wallet model', function() {
cachedW2obj.opts.reconnectDelay = 100; cachedW2obj.opts.reconnectDelay = 100;
} }
var w = Wallet.fromObj(cachedW2obj, cachedW2.storage, cachedW2.network, cachedW2.blockchain); var w = Wallet.fromObj(cachedW2obj, cachedW2.storage, cachedW2.network, cachedW2.blockchain);
w.unlock();
return w; return w;
}; };
@ -1025,20 +1024,6 @@ describe('Wallet model', function() {
w.network.start.getCall(0).args[0].privkey.length.should.equal(64); w.network.start.getCall(0).args[0].privkey.length.should.equal(64);
}); });
it('should check if wallet is already opened', function() {
var w = cachedCreateW2();
should.not.exist(w.getLock());
w.checkAndLock().should.equal(false);
w.getLock().should.equal(true);
});
it('should check if wallet is already opened', function() {
var w = cachedCreateW2();
should.not.exist(w.getLock());
w.checkAndLock().should.equal(false);
w.getLock().should.equal(true);
});
it('should not start if locked', function() { it('should not start if locked', function() {
var w = cachedCreateW2(); var w = cachedCreateW2();
w.netStart(); w.netStart();