Fix Conflicts:
views/unsupported.html
This commit is contained in:
commit
0c2141c380
53 changed files with 1983 additions and 1297 deletions
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('CopayersController',
|
||||
function($scope, $rootScope, $location, backupService) {
|
||||
function($scope, $rootScope, $location, backupService, walletFactory, controllerUtils) {
|
||||
|
||||
$scope.backup = function() {
|
||||
var w = $rootScope.wallet;
|
||||
|
|
@ -18,4 +18,12 @@ angular.module('copayApp.controllers').controller('CopayersController',
|
|||
$location.path('/addresses');
|
||||
};
|
||||
|
||||
$scope.deleteWallet = function() {
|
||||
var w = $rootScope.wallet;
|
||||
w.disconnect();
|
||||
walletFactory.delete(w.id, function() {
|
||||
controllerUtils.logout();
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,5 +9,5 @@ angular.module('copayApp.controllers').controller('HomeController',
|
|||
if ($rootScope.pendingPayment) {
|
||||
notification.info('Login Required', 'Please open wallet to complete payment');
|
||||
}
|
||||
$scope.hasWallets = walletFactory.getWallets().length > 0 ? true : false;
|
||||
$scope.hasWallets = (walletFactory.getWallets() && walletFactory.getWallets().length > 0) ? true : false;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ angular.module('copayApp.controllers').controller('OpenController',
|
|||
};
|
||||
$scope.loading = false;
|
||||
$scope.wallets = walletFactory.getWallets().sort(cmp);
|
||||
$scope.selectedWalletId = $scope.wallets.length ? $scope.wallets[0].id : null;
|
||||
$scope.selectedWalletId = walletFactory.storage.getLastOpened() || ($scope.wallets[0] && $scope.wallets[0].id);
|
||||
$scope.openPassword = '';
|
||||
|
||||
$scope.open = function(form) {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ angular.module('copayApp.controllers').controller('SendController',
|
|||
var w = $rootScope.wallet;
|
||||
|
||||
w.createTx(address, amount, commentText, function(ntxid) {
|
||||
if (w.totalCopayers > 1) {
|
||||
if (w.isShared()) {
|
||||
$scope.loading = false;
|
||||
var message = 'The transaction proposal has been created';
|
||||
notification.success('Success!', message);
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ angular.module('copayApp.controllers').controller('SettingsController',
|
|||
unitToSatoshi: $scope.selectedUnit.value,
|
||||
}));
|
||||
|
||||
window.location.reload();
|
||||
// Go home reloading the application
|
||||
var hashIndex = window.location.href.indexOf('#!/');
|
||||
window.location = window.location.href.substr(0, hashIndex);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
angular.module('copayApp.controllers').controller('SidebarController',
|
||||
function($scope, $rootScope, $sce, $location, $http, notification, controllerUtils) {
|
||||
|
||||
$scope.version = copay.version;
|
||||
$scope.networkName = config.networkName;
|
||||
$scope.menu = [{
|
||||
'title': 'Addresses',
|
||||
'icon': 'fi-address-book',
|
||||
|
|
@ -62,23 +60,6 @@ angular.module('copayApp.controllers').controller('SidebarController',
|
|||
return new Array(num);
|
||||
}
|
||||
|
||||
$http.get('https://api.github.com/repos/bitpay/copay/tags').success(function(data) {
|
||||
var toInt = function(s) {
|
||||
return parseInt(s);
|
||||
};
|
||||
var latestVersion = data[0].name.replace('v', '').split('.').map(toInt);
|
||||
var currentVersion = copay.version.split('.').map(toInt);
|
||||
var title = 'Copay ' + data[0].name + ' available.';
|
||||
var content;
|
||||
if (currentVersion[0] < latestVersion[0]) {
|
||||
content = 'It\'s important that you update your wallet at https://copay.io';
|
||||
notification.version(title, content, true);
|
||||
} else if (currentVersion[0] == latestVersion[0] && currentVersion[1] < latestVersion[1]) {
|
||||
var content = 'Please update your wallet at https://copay.io';
|
||||
notification.version(title, content, false);
|
||||
}
|
||||
});
|
||||
|
||||
// Init socket handlers (with no wallet yet)
|
||||
controllerUtils.setSocketHandlers();
|
||||
|
||||
|
|
|
|||
26
js/controllers/version.js
Normal file
26
js/controllers/version.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('VersionController',
|
||||
function($scope, $rootScope, $http, notification) {
|
||||
|
||||
$scope.version = copay.version;
|
||||
$scope.networkName = config.networkName;
|
||||
|
||||
$http.get('https://api.github.com/repos/bitpay/copay/tags').success(function(data) {
|
||||
var toInt = function(s) {
|
||||
return parseInt(s);
|
||||
};
|
||||
var latestVersion = data[0].name.replace('v', '').split('.').map(toInt);
|
||||
var currentVersion = copay.version.split('.').map(toInt);
|
||||
var title = 'Copay ' + data[0].name + ' available.';
|
||||
var content;
|
||||
if (currentVersion[0] < latestVersion[0]) {
|
||||
content = 'It\'s important that you update your wallet at https://copay.io';
|
||||
notification.version(title, content, true);
|
||||
} else if (currentVersion[0] == latestVersion[0] && currentVersion[1] < latestVersion[1]) {
|
||||
var content = 'Please update your wallet at https://copay.io';
|
||||
notification.version(title, content, false);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
var imports = require('soop').imports();
|
||||
var bitcore = require('bitcore');
|
||||
var coinUtil = bitcore.util;
|
||||
var preconditions = require('preconditions').singleton();
|
||||
|
||||
var http;
|
||||
|
|
@ -37,33 +38,6 @@ function _asyncForEach(array, fn, callback) {
|
|||
}
|
||||
};
|
||||
|
||||
function removeRepeatedElements(ar) {
|
||||
var ya = false,
|
||||
v = "",
|
||||
aux = [].concat(ar),
|
||||
r = Array();
|
||||
for (var i in aux) { //
|
||||
v = aux[i];
|
||||
ya = false;
|
||||
for (var a in aux) {
|
||||
if (v == aux[a]) {
|
||||
if (ya == false) {
|
||||
ya = true;
|
||||
} else {
|
||||
aux[a] = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var a in aux) {
|
||||
if (aux[a] != "") {
|
||||
r.push(aux[a]);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
Insight.prototype._getOptions = function(method, path, data) {
|
||||
return {
|
||||
host: this.host,
|
||||
|
|
@ -78,6 +52,25 @@ Insight.prototype._getOptions = function(method, path, data) {
|
|||
};
|
||||
};
|
||||
|
||||
|
||||
// This is vulneable to txid maneability
|
||||
// TODO: if ret = false,
|
||||
// check output address from similar transactions.
|
||||
//
|
||||
Insight.prototype.checkSentTx = function(tx, cb) {
|
||||
var hash = coinUtil.formatHashFull(tx.getHash());
|
||||
var options = this._getOptions('GET', '/api/tx/' + hash);
|
||||
|
||||
this._request(options, function(err, res) {
|
||||
if (err) return cb(err);
|
||||
var ret = false;
|
||||
if (res && res.txid === hash) {
|
||||
ret = hash;
|
||||
}
|
||||
return cb(err, ret);
|
||||
});
|
||||
};
|
||||
|
||||
Insight.prototype.getTransactions = function(addresses, cb) {
|
||||
preconditions.shouldBeArray(addresses);
|
||||
preconditions.shouldBeFunction(cb);
|
||||
|
|
@ -101,8 +94,11 @@ Insight.prototype.getTransactions = function(addresses, cb) {
|
|||
callback();
|
||||
});
|
||||
}, function() {
|
||||
var clean_txids = removeRepeatedElements(txids);
|
||||
_asyncForEach(clean_txids, function(txid, callback2) {
|
||||
var uniqueTxids = {};
|
||||
for (var k in txids) {
|
||||
uniqueTxids[txids[k]] = 1;
|
||||
}
|
||||
_asyncForEach(Object.keys(uniqueTxids), function(txid, callback2) {
|
||||
var options = self._getOptions('GET', '/api/tx/' + txid);
|
||||
self._request(options, function(err, res) {
|
||||
txs.push(res);
|
||||
|
|
@ -164,8 +160,8 @@ Insight.prototype.checkActivity = function(addresses, cb) {
|
|||
var getOutputs = function(t) {
|
||||
return flatArray(
|
||||
t.vout.map(function(vout) {
|
||||
return vout.scriptPubKey.addresses;
|
||||
})
|
||||
return vout.scriptPubKey.addresses;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ HDPath.FullBranch = function(addressIndex, isChange, copayerIndex) {
|
|||
return BIP45_PUBLIC_PREFIX + '/' + sub;
|
||||
};
|
||||
|
||||
HDPath.indicesForPath = function(path) {
|
||||
HDPath.indexesForPath = function(path) {
|
||||
preconditions.shouldBeString(path);
|
||||
var s = path.split('/');
|
||||
return {
|
||||
isChange: s[3] === '1',
|
||||
index: parseInt(s[4]),
|
||||
addressIndex: parseInt(s[4]),
|
||||
copayerIndex: parseInt(s[2])
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,14 +23,13 @@ function PublicKeyRing(opts) {
|
|||
|
||||
this.copayersHK = opts.copayersHK || [];
|
||||
|
||||
this.indexes = opts.indexes ? HDParams.fromList(opts.indexes)
|
||||
: HDParams.init(this.totalCopayers);
|
||||
this.indexes = opts.indexes ? HDParams.fromList(opts.indexes) : HDParams.init(this.totalCopayers);
|
||||
|
||||
this.publicKeysCache = opts.publicKeysCache || {};
|
||||
this.nicknameFor = opts.nicknameFor || {};
|
||||
this.copayerIds = [];
|
||||
this.copayersBackup = opts.copayersBackup || [];
|
||||
this.addressToPath = {};
|
||||
this.publicKeysCache = opts.publicKeysCache || {};
|
||||
this.nicknameFor = opts.nicknameFor || {};
|
||||
this.copayerIds = [];
|
||||
this.copayersBackup = opts.copayersBackup || [];
|
||||
this.addressToPath = {};
|
||||
}
|
||||
|
||||
PublicKeyRing.fromObj = function(data) {
|
||||
|
|
@ -100,14 +99,6 @@ PublicKeyRing.prototype._checkKeys = function() {
|
|||
throw new Error('dont have required keys yet');
|
||||
};
|
||||
|
||||
PublicKeyRing.prototype._newExtendedPublicKey = function() {
|
||||
return new PrivateKey({
|
||||
networkName: this.network.name
|
||||
})
|
||||
.deriveBIP45Branch()
|
||||
.extendedPublicKeyString();
|
||||
};
|
||||
|
||||
PublicKeyRing.prototype._updateBip = function(index) {
|
||||
var hk = this.copayersHK[index].derive(HDPath.IdBranch);
|
||||
this.copayerIds[index] = hk.eckey.public.toString('hex');
|
||||
|
|
@ -126,6 +117,8 @@ PublicKeyRing.prototype.nicknameForCopayer = function(copayerId) {
|
|||
};
|
||||
|
||||
PublicKeyRing.prototype.addCopayer = function(newEpk, nickname) {
|
||||
preconditions.checkArgument(newEpk);
|
||||
|
||||
if (this.isComplete())
|
||||
throw new Error('PKR already has all required key:' + this.totalCopayers);
|
||||
|
||||
|
|
@ -134,10 +127,6 @@ PublicKeyRing.prototype.addCopayer = function(newEpk, nickname) {
|
|||
throw new Error('PKR already has that key');
|
||||
});
|
||||
|
||||
if (!newEpk) {
|
||||
newEpk = this._newExtendedPublicKey();
|
||||
}
|
||||
|
||||
var i = this.copayersHK.length;
|
||||
var bip = new HK(newEpk);
|
||||
this.copayersHK.push(bip);
|
||||
|
|
@ -192,7 +181,9 @@ PublicKeyRing.prototype.getAddress = function(index, isChange, id) {
|
|||
// Overloaded to receive a PubkeyString or a consigner index
|
||||
PublicKeyRing.prototype.getHDParams = function(id) {
|
||||
var copayerIndex = this.getCosigner(id);
|
||||
var index = this.indexes.filter(function(i) { return i.copayerIndex == copayerIndex });
|
||||
var index = this.indexes.filter(function(i) {
|
||||
return i.copayerIndex == copayerIndex
|
||||
});
|
||||
if (index.length != 1) throw new Error('no index for copayerIndex');
|
||||
|
||||
return index[0];
|
||||
|
|
@ -231,9 +222,11 @@ PublicKeyRing.prototype.getCosigner = function(pubKey) {
|
|||
if (typeof pubKey == 'undefined') return HDPath.SHARED_INDEX;
|
||||
if (typeof pubKey == 'number') return pubKey;
|
||||
|
||||
var sorted = this.copayersHK.map(function(h, i){
|
||||
var sorted = this.copayersHK.map(function(h, i) {
|
||||
return h.eckey.public.toString('hex');
|
||||
}).sort(function(h1, h2){ return h1.localeCompare(h2); });
|
||||
}).sort(function(h1, h2) {
|
||||
return h1.localeCompare(h2);
|
||||
});
|
||||
|
||||
var index = sorted.indexOf(pubKey);
|
||||
if (index == -1) throw new Error('no public key in ring');
|
||||
|
|
@ -255,41 +248,87 @@ PublicKeyRing.prototype.getAddressesInfo = function(opts, pubkey) {
|
|||
PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts, copayerIndex) {
|
||||
opts = opts || {};
|
||||
|
||||
var isOwned = index.copayerIndex == HDPath.SHARED_INDEX
|
||||
|| index.copayerIndex == copayerIndex;
|
||||
var isOwned = index.copayerIndex == HDPath.SHARED_INDEX || index.copayerIndex == copayerIndex;
|
||||
|
||||
var ret = [];
|
||||
if (!opts.excludeChange) {
|
||||
for (var i = 0; i < index.changeIndex; i++) {
|
||||
var a = this.getAddress(i, true, index.copayerIndex);
|
||||
ret.unshift({
|
||||
address: a,
|
||||
addressStr: a.toString(),
|
||||
isChange: true,
|
||||
owned: isOwned
|
||||
});
|
||||
}
|
||||
var ret = [];
|
||||
if (!opts.excludeChange) {
|
||||
for (var i = 0; i < index.changeIndex; i++) {
|
||||
var a = this.getAddress(i, true, index.copayerIndex);
|
||||
ret.unshift({
|
||||
address: a,
|
||||
addressStr: a.toString(),
|
||||
isChange: true,
|
||||
owned: isOwned
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts.excludeMain) {
|
||||
for (var i = 0; i < index.receiveIndex; i++) {
|
||||
var a = this.getAddress(i, false, index.copayerIndex);
|
||||
ret.unshift({
|
||||
address: a,
|
||||
addressStr: a.toString(),
|
||||
isChange: false,
|
||||
owned: isOwned
|
||||
});
|
||||
}
|
||||
if (!opts.excludeMain) {
|
||||
for (var i = 0; i < index.receiveIndex; i++) {
|
||||
var a = this.getAddress(i, false, index.copayerIndex);
|
||||
ret.unshift({
|
||||
address: a,
|
||||
addressStr: a.toString(),
|
||||
isChange: false,
|
||||
owned: isOwned
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
return ret;
|
||||
};
|
||||
|
||||
PublicKeyRing.prototype.getForPath = function(path) {
|
||||
var p = HDPath.indexesForPath(path);
|
||||
var pubKeys = this.getPubKeys(p.addressIndex, p.isChange, p.copayerIndex);
|
||||
return pubKeys;
|
||||
};
|
||||
|
||||
PublicKeyRing.prototype.getForPaths = function(paths) {
|
||||
preconditions.checkArgument(paths);
|
||||
return paths.map(this.getForPath.bind(this));
|
||||
};
|
||||
|
||||
|
||||
PublicKeyRing.prototype.forPaths = function(paths) {
|
||||
return {
|
||||
pubKeys: paths.map(this.getForPath.bind(this)),
|
||||
copayerIds: this.copayerIds,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// returns pubkey -> copayerId.
|
||||
PublicKeyRing.prototype.copayersForPubkeys = function(pubkeys, paths) {
|
||||
preconditions.checkArgument(pubkeys);
|
||||
preconditions.checkArgument(paths);
|
||||
|
||||
var inKeyMap = {}, ret = {};
|
||||
for(var i in pubkeys ){
|
||||
inKeyMap[pubkeys[i]] = 1;
|
||||
};
|
||||
|
||||
var keys = this.getForPaths(paths);
|
||||
for(var i in keys ){
|
||||
for(var copayerIndex in keys[i] ){
|
||||
var kHex = keys[i][copayerIndex].toString('hex');
|
||||
if (inKeyMap[kHex]) {
|
||||
ret[kHex] =this.copayerIds[copayerIndex];
|
||||
delete inKeyMap[kHex];
|
||||
}
|
||||
}
|
||||
}
|
||||
for(var i in inKeyMap)
|
||||
throw new Error('Pubkey not identified')
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
// TODO this could be cached
|
||||
PublicKeyRing.prototype._addScriptMap = function(map, path) {
|
||||
var p = HDPath.indicesForPath(path);
|
||||
var script = this.getRedeemScript(p.index, p.isChange, p.copayerIndex);
|
||||
var p = HDPath.indexesForPath(path);
|
||||
var script = this.getRedeemScript(p.addressIndex, p.isChange, p.copayerIndex);
|
||||
map[Address.fromScript(script, this.network.name).toString()] = script.getBuffer().toString('hex');
|
||||
};
|
||||
|
||||
|
|
|
|||
321
js/models/core/TxProposal.js
Normal file
321
js/models/core/TxProposal.js
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var util = bitcore.util;
|
||||
var Transaction = bitcore.Transaction;
|
||||
var BuilderMockV0 = require('./BuilderMockV0');;
|
||||
var TransactionBuilder = bitcore.TransactionBuilder;
|
||||
var Script = bitcore.Script;
|
||||
var Key = bitcore.Key;
|
||||
var buffertools = bitcore.buffertools;
|
||||
var preconditions = require('preconditions').instance();
|
||||
|
||||
var VERSION = 1;
|
||||
var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version', 'comment'];
|
||||
|
||||
|
||||
function TxProposal(opts) {
|
||||
preconditions.checkArgument(opts);
|
||||
preconditions.checkArgument(opts.inputChainPaths, 'no inputChainPaths');
|
||||
preconditions.checkArgument(opts.builder, 'no builder');
|
||||
preconditions.checkArgument(opts.inputChainPaths, 'no inputChainPaths');
|
||||
|
||||
this.inputChainPaths = opts.inputChainPaths;
|
||||
this.version = opts.version;
|
||||
this.builder = opts.builder;
|
||||
this.createdTs = opts.createdTs;
|
||||
this.createdTs = opts.createdTs;
|
||||
this._inputSignatures = [];
|
||||
|
||||
// CopayerIds
|
||||
this.creator = opts.creator;
|
||||
this.signedBy = opts.signedBy || {};
|
||||
this.seenBy = opts.seenBy || {};
|
||||
this.rejectedBy = opts.rejectedBy || {};
|
||||
this.sentTs = opts.sentTs || null;
|
||||
this.sentTxid = opts.sentTxid || null;
|
||||
this.comment = opts.comment || null;
|
||||
this.readonly = opts.readonly || null;
|
||||
this._sync();
|
||||
}
|
||||
|
||||
|
||||
TxProposal.prototype._check = function() {
|
||||
|
||||
if (this.builder.signhash && this.builder.signhash !== Transaction.SIGHASH_ALL) {
|
||||
throw new Error('Invalid tx proposal');
|
||||
}
|
||||
|
||||
var tx = this.builder.build();
|
||||
if (!tx.ins.length)
|
||||
throw new Error('Invalid tx proposal: no ins');
|
||||
|
||||
for (var i in tx.ins) {
|
||||
var scriptSig = tx.ins[i].s;
|
||||
if (!scriptSig || !scriptSig.length) {
|
||||
throw new Error('Invalid tx proposal: no signatures');
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < tx.ins.length; i++) {
|
||||
var hashType = tx.getHashType(i);
|
||||
if (hashType && hashType !== Transaction.SIGHASH_ALL)
|
||||
throw new Error('Invalid tx proposal: bad signatures');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TxProposal.prototype._updateSignedBy = function() {
|
||||
this._inputSignatures = [];
|
||||
|
||||
var tx = this.builder.build();
|
||||
for (var i in tx.ins) {
|
||||
var scriptSig = new Script(tx.ins[i].s);
|
||||
var signatureCount = scriptSig.countSignatures();
|
||||
var info = TxProposal._infoFromRedeemScript(scriptSig);
|
||||
var txSigHash = tx.hashForSignature(info.script, parseInt(i), Transaction.SIGHASH_ALL);
|
||||
var signatureIndexes = TxProposal._verifySignatures(info.keys, scriptSig, txSigHash);
|
||||
if (signatureIndexes.length !== signatureCount)
|
||||
throw new Error('Invalid signature');
|
||||
this._inputSignatures[i] = signatureIndexes.map(function(i) {
|
||||
var r = info.keys[i].toString('hex');
|
||||
return r;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
TxProposal.prototype._sync = function() {
|
||||
this._check();
|
||||
this._updateSignedBy();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
TxProposal.prototype.getId = function() {
|
||||
preconditions.checkState(this.builder);
|
||||
return this.builder.build().getNormalizedHash().toString('hex');
|
||||
};
|
||||
|
||||
TxProposal.prototype.toObj = function() {
|
||||
var o = JSON.parse(JSON.stringify(this));
|
||||
delete o['builder'];
|
||||
o.builderObj = this.builder.toObj();
|
||||
return o;
|
||||
};
|
||||
|
||||
|
||||
TxProposal._trim = function(o) {
|
||||
var ret = {};
|
||||
CORE_FIELDS.forEach(function(k) {
|
||||
ret[k] = o[k];
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
TxProposal.fromObj = function(o, forceOpts) {
|
||||
preconditions.checkArgument(o.builderObj);
|
||||
delete o['builder'];
|
||||
|
||||
try {
|
||||
// force opts is requested.
|
||||
for (var k in forceOpts) {
|
||||
o.builderObj.opts[k] = forceOpts[k];
|
||||
}
|
||||
o.builder = TransactionBuilder.fromObj(o.builderObj);
|
||||
} catch (e) {
|
||||
|
||||
// backwards (V0) compatatibility fix.
|
||||
if (!o.version) {
|
||||
o.builder = new BuilderMockV0(o.builderObj);
|
||||
o.readonly = 1;
|
||||
};
|
||||
}
|
||||
return new TxProposal(o);
|
||||
};
|
||||
|
||||
TxProposal.fromUntrustedObj = function(o, forceOpts) {
|
||||
return TxProposal.fromObj(TxProposal._trim(o), forceOpts);
|
||||
};
|
||||
|
||||
TxProposal.prototype.toObjTrim = function() {
|
||||
return TxProposal._trim(this.toObj());
|
||||
};
|
||||
|
||||
TxProposal._formatKeys = function(keys) {
|
||||
var ret = [];
|
||||
for (var i in keys) {
|
||||
if (!Buffer.isBuffer(keys[i]))
|
||||
throw new Error('keys must be buffers');
|
||||
|
||||
var k = new Key();
|
||||
k.public = keys[i];
|
||||
ret.push(k);
|
||||
};
|
||||
return ret;
|
||||
};
|
||||
|
||||
TxProposal._verifySignatures = function(inKeys, scriptSig, txSigHash) {
|
||||
preconditions.checkArgument(Buffer.isBuffer(txSigHash));
|
||||
preconditions.checkArgument(inKeys);
|
||||
preconditions.checkState(Buffer.isBuffer(inKeys[0]));
|
||||
|
||||
if (scriptSig.chunks[0] !== 0)
|
||||
throw new Error('Invalid scriptSig');
|
||||
|
||||
var keys = TxProposal._formatKeys(inKeys);
|
||||
var ret = [];
|
||||
for (var i = 1; i <= scriptSig.countSignatures(); i++) {
|
||||
var chunk = scriptSig.chunks[i];
|
||||
var sigRaw = new Buffer(chunk.slice(0, chunk.length - 1));
|
||||
for (var j in keys) {
|
||||
var k = keys[j];
|
||||
if (k.verifySignatureSync(txSigHash, sigRaw)) {
|
||||
ret.push(parseInt(j));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
TxProposal._infoFromRedeemScript = function(s) {
|
||||
var redeemScript = new Script(s.chunks[s.chunks.length - 1]);
|
||||
if (!redeemScript)
|
||||
throw new Error('Bad scriptSig (no redeemscript)');
|
||||
|
||||
var pubkeys = redeemScript.capture();
|
||||
if (!pubkeys || !pubkeys.length)
|
||||
throw new Error('Bad scriptSig (no pubkeys)');
|
||||
|
||||
return {
|
||||
keys: pubkeys,
|
||||
script: redeemScript,
|
||||
};
|
||||
};
|
||||
|
||||
TxProposal.prototype.mergeBuilder = function(incoming) {
|
||||
var b0 = this.builder;
|
||||
var b1 = incoming.builder;
|
||||
|
||||
var before = JSON.stringify(b0.toObj());
|
||||
b0.merge(b1);
|
||||
var after = JSON.stringify(b0.toObj());
|
||||
return after !== before;
|
||||
};
|
||||
|
||||
|
||||
TxProposal.prototype.setSeen = function(copayerId) {
|
||||
if (!this.seenBy[copayerId])
|
||||
this.seenBy[copayerId] = Date.now();
|
||||
};
|
||||
|
||||
TxProposal.prototype.setRejected = function(copayerId) {
|
||||
|
||||
if (this.signedBy[copayerId])
|
||||
throw new Error('Can not reject a signed TX');
|
||||
|
||||
if (!this.rejectedBy[copayerId])
|
||||
this.rejectedBy[copayerId] = Date.now();
|
||||
};
|
||||
|
||||
TxProposal.prototype.setSent = function(sentTxid) {
|
||||
this.sentTxid = sentTxid;
|
||||
this.sentTs = Date.now();
|
||||
};
|
||||
|
||||
|
||||
|
||||
TxProposal.prototype._allSignatures = function() {
|
||||
var ret = {};
|
||||
for (var i in this._inputSignatures)
|
||||
for (var j in this._inputSignatures[i])
|
||||
ret[this._inputSignatures[i][j]] = true;
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) {
|
||||
var newCopayer = {},
|
||||
oldCopayers = {},
|
||||
newSignedBy = {},
|
||||
readOnlyPeers = {},
|
||||
isNew = 1;
|
||||
|
||||
for (var k in this.signedBy) {
|
||||
oldCopayers[k] = 1;
|
||||
isNew = 0;
|
||||
};
|
||||
|
||||
if (isNew == 0) {
|
||||
if (!this.creator || !this.createdTs)
|
||||
throw new Error('Existing TX has no creator');
|
||||
|
||||
if (!this.signedBy[this.creator])
|
||||
throw new Error('Existing TX is not signed by creator');
|
||||
|
||||
|
||||
if (Object.keys(this.signedBy).length === 0)
|
||||
throw new Error('Existing TX has no signatures');
|
||||
}
|
||||
|
||||
|
||||
var iSig = this._inputSignatures[0];
|
||||
for (var i in iSig) {
|
||||
var copayerId = keyMap[iSig[i]];
|
||||
if (!copayerId)
|
||||
throw new Error('Found unknown signature')
|
||||
|
||||
if (oldCopayers[copayerId]) {
|
||||
//Already have it. Do nothing
|
||||
} else {
|
||||
newCopayer[copayerId] = Date.now();
|
||||
delete oldCopayers[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Seems unncessary to check this:
|
||||
// if (!newCopayer[senderId] && !readOnlyPeers[senderId])
|
||||
// throw new Error('TX must have a (new) senders signature')
|
||||
|
||||
if (Object.keys(newCopayer).length > 1)
|
||||
throw new Error('New TX must have only 1 new signature');
|
||||
|
||||
// Handler creator / createdTs.
|
||||
// from senderId, and must be signed by senderId
|
||||
if (isNew) {
|
||||
this.creator = Object.keys(newCopayer)[0];
|
||||
this.seenBy[this.creator] = this.createdTs = Date.now();
|
||||
}
|
||||
|
||||
//Ended. Update this.
|
||||
for (var i in newCopayer) {
|
||||
this.signedBy[i] = newCopayer[i];
|
||||
}
|
||||
|
||||
// signedBy has preference over rejectedBy
|
||||
for (var i in this.signedBy) {
|
||||
delete this.rejectedBy[i];
|
||||
}
|
||||
|
||||
return Object.keys(newCopayer);
|
||||
};
|
||||
|
||||
// merge will not merge any metadata.
|
||||
TxProposal.prototype.merge = function(incoming) {
|
||||
var hasChanged = this.mergeBuilder(incoming);
|
||||
this._sync();
|
||||
return hasChanged;
|
||||
};
|
||||
|
||||
//This should be on bitcore / Transaction
|
||||
TxProposal.prototype.countSignatures = function() {
|
||||
var tx = this.builder.build();
|
||||
var ret = 0;
|
||||
for (var i in tx.ins) {
|
||||
ret += tx.countInputSignatures(i);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
module.exports = TxProposal;
|
||||
|
|
@ -1,174 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
var imports = require('soop').imports();
|
||||
var BuilderMockV0 = require('./BuilderMockV0');;
|
||||
var bitcore = require('bitcore');
|
||||
var util = bitcore.util;
|
||||
var Transaction = bitcore.Transaction;
|
||||
var BuilderMockV0 = require('./BuilderMockV0');;
|
||||
var TransactionBuilder = bitcore.TransactionBuilder;
|
||||
var TxProposal = require('./TxProposal');;
|
||||
var Script = bitcore.Script;
|
||||
var Key = bitcore.Key;
|
||||
var buffertools = bitcore.buffertools;
|
||||
var preconditions = require('preconditions').instance();
|
||||
|
||||
function TxProposal(opts) {
|
||||
this.creator = opts.creator;
|
||||
this.createdTs = opts.createdTs;
|
||||
this.seenBy = opts.seenBy || {};
|
||||
this.signedBy = opts.signedBy || {};
|
||||
this.rejectedBy = opts.rejectedBy || {};
|
||||
this.builder = opts.builder;
|
||||
this.sentTs = opts.sentTs || null;
|
||||
this.sentTxid = opts.sentTxid || null;
|
||||
this.inputChainPaths = opts.inputChainPaths || [];
|
||||
this.comment = opts.comment || null;
|
||||
}
|
||||
|
||||
TxProposal.prototype.getID = function() {
|
||||
return this.builder.build().getNormalizedHash().toString('hex');
|
||||
};
|
||||
|
||||
TxProposal.prototype.toObj = function() {
|
||||
var o = JSON.parse(JSON.stringify(this));
|
||||
delete o['builder'];
|
||||
o.builderObj = this.builder.toObj();
|
||||
return o;
|
||||
};
|
||||
|
||||
|
||||
TxProposal.prototype.setSent = function(sentTxid) {
|
||||
this.sentTxid = sentTxid;
|
||||
this.sentTs = Date.now();
|
||||
};
|
||||
|
||||
TxProposal.fromObj = function(o, forceOpts) {
|
||||
var t = new TxProposal(o);
|
||||
|
||||
try {
|
||||
// force opts is requested.
|
||||
for (var k in forceOpts) {
|
||||
o.builderObj.opts[k] = forceOpts[k];
|
||||
}
|
||||
t.builder = TransactionBuilder.fromObj(o.builderObj);
|
||||
|
||||
} catch (e) {
|
||||
if (!o.version) {
|
||||
t.builder = new BuilderMockV0(o.builderObj);
|
||||
t.readonly = 1;
|
||||
};
|
||||
}
|
||||
|
||||
return t;
|
||||
};
|
||||
|
||||
|
||||
TxProposal.prototype.isValid = function() {
|
||||
if (this.builder.signhash && this.builder.signhash !== Transaction.SIGHASH_ALL) {
|
||||
return false;
|
||||
}
|
||||
var tx = this.builder.build();
|
||||
for (var i = 0; i < tx.ins.length; i++) {
|
||||
var hashType = tx.getHashType(i);
|
||||
if (hashType && hashType !== Transaction.SIGHASH_ALL) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
TxProposal.getSentTs = function() {
|
||||
return this.sentTs;
|
||||
};
|
||||
|
||||
TxProposal.prototype.merge = function(other, author) {
|
||||
var ret = {};
|
||||
ret.events = this.mergeMetadata(other, author);
|
||||
ret.hasChanged = this.mergeBuilder(other);
|
||||
return ret;
|
||||
};
|
||||
|
||||
TxProposal.prototype.mergeBuilder = function(other) {
|
||||
var b0 = this.builder;
|
||||
var b1 = other.builder;
|
||||
|
||||
// TODO: improve this comparison
|
||||
var before = JSON.stringify(b0.toObj());
|
||||
b0.merge(b1);
|
||||
var after = JSON.stringify(b0.toObj());
|
||||
return after !== before;
|
||||
};
|
||||
|
||||
TxProposal.prototype.mergeMetadata = function(v1, author) {
|
||||
var events = [];
|
||||
var v0 = this;
|
||||
|
||||
var ntxid = this.getID();
|
||||
|
||||
Object.keys(v1.seenBy).forEach(function(k) {
|
||||
if (!v0.seenBy[k]) {
|
||||
// TODO: uncomment below and change protocol to make this work
|
||||
//if (k != author) throw new Error('Non authoritative seenBy change by ' + author);
|
||||
v0.seenBy[k] = v1.seenBy[k];
|
||||
events.push({
|
||||
type: 'seen',
|
||||
cId: k,
|
||||
txId: ntxid
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(v1.signedBy).forEach(function(k) {
|
||||
if (!v0.signedBy[k]) {
|
||||
// TODO: uncomment below and change protocol to make this work
|
||||
//if (k != author) throw new Error('Non authoritative signedBy change by ' + author);
|
||||
v0.signedBy[k] = v1.signedBy[k];
|
||||
events.push({
|
||||
type: 'signed',
|
||||
cId: k,
|
||||
txId: ntxid
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(v1.rejectedBy).forEach(function(k) {
|
||||
if (!v0.rejectedBy[k]) {
|
||||
// TODO: uncomment below and change protocol to make this work
|
||||
//if (k != author) throw new Error('Non authoritative rejectedBy change by ' + author);
|
||||
v0.rejectedBy[k] = v1.rejectedBy[k];
|
||||
events.push({
|
||||
type: 'rejected',
|
||||
cId: k,
|
||||
txId: ntxid
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!v0.sentTxid && v1.sentTxid) {
|
||||
v0.sentTs = v1.sentTs;
|
||||
v0.sentTxid = v1.sentTxid;
|
||||
events.push({
|
||||
type: 'broadcast',
|
||||
txId: ntxid
|
||||
});
|
||||
}
|
||||
|
||||
return events;
|
||||
|
||||
};
|
||||
|
||||
//This should be on bitcore / Transaction
|
||||
TxProposal.prototype.countSignatures = function() {
|
||||
var tx = this.builder.build();
|
||||
|
||||
var ret = 0;
|
||||
for (var i in tx.ins) {
|
||||
ret += tx.countInputSignatures(i);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
module.exports = require('soop')(TxProposal);
|
||||
|
||||
|
||||
function TxProposals(opts) {
|
||||
opts = opts || {};
|
||||
|
|
@ -178,6 +20,7 @@ function TxProposals(opts) {
|
|||
this.txps = {};
|
||||
}
|
||||
|
||||
// fromObj => from a trusted source
|
||||
TxProposals.fromObj = function(o, forceOpts) {
|
||||
var ret = new TxProposals({
|
||||
networkName: o.networkName,
|
||||
|
|
@ -187,7 +30,7 @@ TxProposals.fromObj = function(o, forceOpts) {
|
|||
o.txps.forEach(function(o2) {
|
||||
var t = TxProposal.fromObj(o2, forceOpts);
|
||||
if (t.builder) {
|
||||
var id = t.getID();
|
||||
var id = t.getId();
|
||||
ret.txps[id] = t;
|
||||
}
|
||||
});
|
||||
|
|
@ -198,14 +41,9 @@ TxProposals.prototype.getNtxids = function() {
|
|||
return Object.keys(this.txps);
|
||||
};
|
||||
|
||||
TxProposals.prototype.toObj = function(onlyThisNtxid) {
|
||||
if (onlyThisNtxid) throw new Error();
|
||||
TxProposals.prototype.toObj = function() {
|
||||
var ret = [];
|
||||
for (var id in this.txps) {
|
||||
|
||||
if (onlyThisNtxid && id != onlyThisNtxid)
|
||||
continue;
|
||||
|
||||
var t = this.txps[id];
|
||||
if (!t.sent)
|
||||
ret.push(t.toObj());
|
||||
|
|
@ -217,50 +55,53 @@ TxProposals.prototype.toObj = function(onlyThisNtxid) {
|
|||
};
|
||||
};
|
||||
|
||||
TxProposals.prototype.merge = function(inTxp, author) {
|
||||
var myTxps = this.txps;
|
||||
|
||||
var ntxid = inTxp.getID();
|
||||
var ret = {};
|
||||
ret.events = [];
|
||||
ret.events.hasChanged = false;
|
||||
TxProposals.prototype.merge = function(inObj, builderOpts) {
|
||||
var incomingTx = TxProposal.fromUntrustedObj(inObj, builderOpts);
|
||||
incomingTx._sync();
|
||||
|
||||
var myTxps = this.txps;
|
||||
var ntxid = incomingTx.getId();
|
||||
var ret = {
|
||||
ntxid: ntxid
|
||||
};
|
||||
|
||||
if (myTxps[ntxid]) {
|
||||
var v0 = myTxps[ntxid];
|
||||
var v1 = inTxp;
|
||||
ret = v0.merge(v1, author);
|
||||
|
||||
// Merge an existing txProposal
|
||||
ret.hasChanged = myTxps[ntxid].merge(incomingTx);
|
||||
|
||||
|
||||
} else {
|
||||
this.txps[ntxid] = inTxp;
|
||||
ret.hasChanged = true;
|
||||
ret.events.push({
|
||||
type: 'new',
|
||||
cid: inTxp.creator,
|
||||
tx: ntxid
|
||||
});
|
||||
// Create a new one
|
||||
ret.new = ret.hasChanged = 1;
|
||||
this.txps[ntxid] = incomingTx;
|
||||
}
|
||||
|
||||
ret.txp = this.txps[ntxid];
|
||||
return ret;
|
||||
};
|
||||
|
||||
TxProposals.prototype.add = function(data) {
|
||||
preconditions.checkArgument(data.inputChainPaths);
|
||||
preconditions.checkArgument(data.signedBy);
|
||||
preconditions.checkArgument(data.creator);
|
||||
preconditions.checkArgument(data.createdTs);
|
||||
preconditions.checkArgument(data.builder);
|
||||
var txp = new TxProposal(data);
|
||||
var ntxid = txp.getID();
|
||||
// Add a LOCALLY CREATED (trusted) tx proposal
|
||||
TxProposals.prototype.add = function(txp) {
|
||||
txp._sync();
|
||||
var ntxid = txp.getId();
|
||||
this.txps[ntxid] = txp;
|
||||
return ntxid;
|
||||
};
|
||||
|
||||
TxProposals.prototype.setSent = function(ntxid, txid) {
|
||||
//sent TxProposals are local an not broadcasted.
|
||||
this.txps[ntxid].setSent(txid);
|
||||
|
||||
TxProposals.prototype.get = function(ntxid) {
|
||||
var ret = this.txps[ntxid];
|
||||
if (!ret)
|
||||
throw new Error('Unknown TXP: '+ntxid);
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
TxProposals.prototype.getTxProposal = function(ntxid, copayers) {
|
||||
var txp = this.txps[ntxid];
|
||||
var txp = this.get(ntxid);
|
||||
|
||||
var i = JSON.parse(JSON.stringify(txp));
|
||||
i.builder = txp.builder;
|
||||
i.ntxid = ntxid;
|
||||
|
|
@ -296,6 +137,17 @@ TxProposals.prototype.getTxProposal = function(ntxid, copayers) {
|
|||
return i;
|
||||
};
|
||||
|
||||
|
||||
TxProposals.prototype.reject = function(ntxid, copayerId) {
|
||||
var txp = this.get(ntxid);
|
||||
txp.setRejected(copayerId);
|
||||
};
|
||||
|
||||
TxProposals.prototype.seen = function(ntxid, copayerId) {
|
||||
var txp = this.get(ntxid);
|
||||
txp.setSeen(copayerId);
|
||||
};
|
||||
|
||||
//returns the unspent txid-vout used in PENDING Txs
|
||||
TxProposals.prototype.getUsedUnspent = function(maxRejectCount) {
|
||||
var ret = {};
|
||||
|
|
@ -312,5 +164,4 @@ TxProposals.prototype.getUsedUnspent = function(maxRejectCount) {
|
|||
return ret;
|
||||
};
|
||||
|
||||
TxProposals.TxProposal = TxProposal;
|
||||
module.exports = require('soop')(TxProposals);
|
||||
module.exports = TxProposals;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ var Address = bitcore.Address;
|
|||
|
||||
var HDParams = require('./HDParams');
|
||||
var PublicKeyRing = require('./PublicKeyRing');
|
||||
var TxProposal = require('./TxProposal');
|
||||
var TxProposals = require('./TxProposals');
|
||||
var PrivateKey = require('./PrivateKey');
|
||||
var copayConfig = require('../../../config');
|
||||
|
|
@ -36,7 +37,7 @@ function Wallet(opts) {
|
|||
});
|
||||
if (copayConfig.forceNetwork && this.getNetworkName() !== copayConfig.networkName)
|
||||
throw new Error('Network forced to ' + copayConfig.networkName +
|
||||
' and tried to create a Wallet with network ' + this.getNetworkName());
|
||||
' and tried to create a Wallet with network ' + this.getNetworkName());
|
||||
|
||||
this.log('creating ' + opts.requiredCopayers + ' of ' + opts.totalCopayers + ' wallet');
|
||||
|
||||
|
|
@ -58,11 +59,11 @@ function Wallet(opts) {
|
|||
}
|
||||
|
||||
|
||||
Wallet.builderOpts = {
|
||||
lockTime: null,
|
||||
signhash: bitcore.Transaction.SIGNHASH_ALL,
|
||||
fee: null,
|
||||
feeSat: null,
|
||||
Wallet.builderOpts = {
|
||||
lockTime: null,
|
||||
signhash: bitcore.Transaction.SIGNHASH_ALL,
|
||||
fee: null,
|
||||
feeSat: null,
|
||||
};
|
||||
|
||||
Wallet.parent = EventEmitter;
|
||||
|
|
@ -129,39 +130,158 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) {
|
|||
};
|
||||
|
||||
|
||||
|
||||
Wallet.prototype._handleTxProposal = function(senderId, data) {
|
||||
this.log('RECV TXPROPOSAL: ', data);
|
||||
var inTxp = TxProposals.TxProposal.fromObj(data.txProposal, Wallet.builderOpts);
|
||||
|
||||
|
||||
|
||||
var valid = inTxp.isValid();
|
||||
if (!valid) {
|
||||
var corruptEvent = {
|
||||
Wallet.prototype._processProposalEvents = function(senderId, m) {
|
||||
var ev;
|
||||
if (m) {
|
||||
if (m.new) {
|
||||
ev = {
|
||||
type: 'new',
|
||||
cid: senderId
|
||||
}
|
||||
} else if (m.newCopayer) {
|
||||
ev = {
|
||||
type: 'signed',
|
||||
cid: m.newCopayer
|
||||
};
|
||||
}
|
||||
} else {
|
||||
ev = {
|
||||
type: 'corrupt',
|
||||
cId: inTxp.creator
|
||||
cId: senderId,
|
||||
};
|
||||
this.emit('txProposalEvent', corruptEvent);
|
||||
return;
|
||||
}
|
||||
var mergeInfo = this.txProposals.merge(inTxp, senderId);
|
||||
var added = this.addSeenToTxProposals();
|
||||
|
||||
if (added) {
|
||||
this.log('### BROADCASTING txProposals with my seenBy updated.');
|
||||
this.sendTxProposal(inTxp.getID());
|
||||
}
|
||||
|
||||
this.emit('txProposalsUpdated');
|
||||
this.store();
|
||||
|
||||
for (var i = 0; i < mergeInfo.events.length; i++) {
|
||||
this.emit('txProposalEvent', mergeInfo.events[i]);
|
||||
}
|
||||
if (ev)
|
||||
this.emit('txProposalEvent', ev);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* OTDO
|
||||
events.push({
|
||||
type: 'signed',
|
||||
cId: k,
|
||||
txId: ntxid
|
||||
});
|
||||
*/
|
||||
Wallet.prototype._getKeyMap = function(txp) {
|
||||
preconditions.checkArgument(txp);
|
||||
|
||||
var keyMap = this.publicKeyRing.copayersForPubkeys(txp._inputSignatures[0], txp.inputChainPaths);
|
||||
|
||||
var inSig = JSON.stringify(txp._inputSignatures[0].sort());
|
||||
|
||||
if (JSON.stringify(Object.keys(keyMap).sort()) !== inSig) {
|
||||
throw new Error('inputSignatures dont match know copayers pubkeys');
|
||||
}
|
||||
|
||||
var keyMapStr = JSON.stringify(keyMap);
|
||||
// All inputs must be signed with the same copayers
|
||||
for (var i in txp._inputSignatures) {
|
||||
if (!i) continue;
|
||||
var inSigX = JSON.stringify(txp._inputSignatures[i].sort());
|
||||
if (inSigX !== inSig)
|
||||
throw new Error('found inputs with different signatures:');
|
||||
}
|
||||
return keyMap;
|
||||
};
|
||||
|
||||
|
||||
Wallet.prototype._checkSentTx = function(ntxid, cb) {
|
||||
var txp = this.txProposals.get(ntxid);
|
||||
var tx = txp.builder.build();
|
||||
|
||||
this.blockchain.checkSentTx(tx, function(err, txid) {
|
||||
var ret = false;
|
||||
if (txid) {
|
||||
txp.setSent(txid);
|
||||
ret = txid;
|
||||
}
|
||||
return cb(ret);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Wallet.prototype._handleTxProposal = function(senderId, data) {
|
||||
var self = this;
|
||||
this.log('RECV TXPROPOSAL: ', data);
|
||||
var m;
|
||||
|
||||
try {
|
||||
m = this.txProposals.merge(data.txProposal, Wallet.builderOpts);
|
||||
var keyMap = this._getKeyMap(m.txp);
|
||||
ret.newCopayer = m.txp.setCopayers(senderId, keyMap);
|
||||
|
||||
} catch (e) {
|
||||
this.log('Corrupt TX proposal received from:', senderId, e);
|
||||
}
|
||||
|
||||
if (m) {
|
||||
|
||||
if (m.hasChanged) {
|
||||
this.sendSeen(m.ntxid);
|
||||
var tx = m.txp.builder.build();
|
||||
if (tx.isComplete()) {
|
||||
this._checkSentTx(m.ntxid, function(ret) {
|
||||
if (ret) {
|
||||
self.emit('txProposalsUpdated');
|
||||
self.store();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.sendTxProposal(m.ntxid);
|
||||
}
|
||||
}
|
||||
this.emit('txProposalsUpdated');
|
||||
this.store();
|
||||
}
|
||||
this._processProposalEvents(senderId, m);
|
||||
};
|
||||
|
||||
|
||||
Wallet.prototype._handleReject = function(senderId, data, isInbound) {
|
||||
preconditions.checkState(data.ntxid);
|
||||
this.log('RECV REJECT:', data);
|
||||
|
||||
var txp = this.txProposals.get(data.ntxid);
|
||||
|
||||
if (!txp)
|
||||
throw new Error('Received Reject for an unknown TX from:' + senderId);
|
||||
|
||||
if (txp.signedBy[senderId])
|
||||
throw new Error('Received Reject for an already signed TX from:' + senderId);
|
||||
|
||||
txp.setRejected(senderId);
|
||||
this.store();
|
||||
|
||||
this.emit('txProposalsUpdated');
|
||||
this.emit('txProposalEvent', {
|
||||
type: 'rejected',
|
||||
cId: senderId,
|
||||
txId: data.ntxid,
|
||||
});
|
||||
};
|
||||
|
||||
Wallet.prototype._handleSeen = function(senderId, data, isInbound) {
|
||||
preconditions.checkState(data.ntxid);
|
||||
this.log('RECV SEEN:', data);
|
||||
|
||||
var txp = this.txProposals.get(data.ntxid);
|
||||
txp.setSeen(senderId);
|
||||
this.store();
|
||||
this.emit('txProposalsUpdated');
|
||||
this.emit('txProposalEvent', {
|
||||
type: 'seen',
|
||||
cId: senderId,
|
||||
txId: data.ntxid,
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
Wallet.prototype._handleAddressBook = function(senderId, data, isInbound) {
|
||||
preconditions.checkState(data.addressBook);
|
||||
this.log('RECV ADDRESSBOOK:', data);
|
||||
var rcv = data.addressBook;
|
||||
var hasChange;
|
||||
|
|
@ -193,24 +313,30 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) {
|
|||
// This handler is repeaded on WalletFactory (#join). TODO
|
||||
case 'walletId':
|
||||
this.sendWalletReady(senderId);
|
||||
break;
|
||||
break;
|
||||
case 'walletReady':
|
||||
this.sendPublicKeyRing(senderId);
|
||||
this.sendAddressBook(senderId);
|
||||
this.sendAllTxProposals(senderId); // send old txps
|
||||
break;
|
||||
this.sendAddressBook(senderId);
|
||||
this.sendAllTxProposals(senderId); // send old txps
|
||||
break;
|
||||
case 'publicKeyRing':
|
||||
this._handlePublicKeyRing(senderId, data, isInbound);
|
||||
break;
|
||||
break;
|
||||
case 'reject':
|
||||
this._handleReject(senderId, data, isInbound);
|
||||
break;
|
||||
case 'seen':
|
||||
this._handleSeen(senderId, data, isInbound);
|
||||
break;
|
||||
case 'txProposal':
|
||||
this._handleTxProposal(senderId, data, isInbound);
|
||||
break;
|
||||
break;
|
||||
case 'indexes':
|
||||
this._handleIndexes(senderId, data, isInbound);
|
||||
break;
|
||||
break;
|
||||
case 'addressbook':
|
||||
this._handleAddressBook(senderId, data, isInbound);
|
||||
break;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -384,6 +510,7 @@ Wallet.prototype.toObj = function() {
|
|||
return walletObj;
|
||||
};
|
||||
|
||||
// fromObj => from a trusted source
|
||||
Wallet.fromObj = function(o, storage, network, blockchain) {
|
||||
var opts = JSON.parse(JSON.stringify(o.opts));
|
||||
opts.addressBook = o.addressBook;
|
||||
|
|
@ -418,11 +545,31 @@ Wallet.prototype.sendAllTxProposals = function(recipients) {
|
|||
|
||||
Wallet.prototype.sendTxProposal = function(ntxid, recipients) {
|
||||
preconditions.checkArgument(ntxid);
|
||||
preconditions.checkState(this.txProposals.txps[ntxid]);
|
||||
|
||||
this.log('### SENDING txProposal ' + ntxid + ' TO:', recipients || 'All', this.txProposals);
|
||||
this.send(recipients, {
|
||||
type: 'txProposal',
|
||||
txProposal: this.txProposals.txps[ntxid].toObj(),
|
||||
txProposal: this.txProposals.get(ntxid).toObjTrim(),
|
||||
walletId: this.id,
|
||||
});
|
||||
};
|
||||
|
||||
Wallet.prototype.sendSeen = function(ntxid) {
|
||||
preconditions.checkArgument(ntxid);
|
||||
this.log('### SENDING seen: ' + ntxid + ' TO: All');
|
||||
this.send(null, {
|
||||
type: 'seen',
|
||||
ntxid: ntxid,
|
||||
walletId: this.id,
|
||||
});
|
||||
};
|
||||
|
||||
Wallet.prototype.sendReject = function(ntxid) {
|
||||
preconditions.checkArgument(ntxid);
|
||||
this.log('### SENDING reject: ' + ntxid + ' TO: All');
|
||||
this.send(null, {
|
||||
type: 'reject',
|
||||
ntxid: ntxid,
|
||||
walletId: this.id,
|
||||
});
|
||||
};
|
||||
|
|
@ -517,30 +664,22 @@ Wallet.prototype.getTxProposals = function() {
|
|||
|
||||
|
||||
Wallet.prototype.reject = function(ntxid) {
|
||||
var myId = this.getMyCopayerId();
|
||||
var txp = this.txProposals.txps[ntxid];
|
||||
if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) {
|
||||
throw new Error('Invalid transaction to reject: ' + ntxid);
|
||||
}
|
||||
|
||||
txp.rejectedBy[myId] = Date.now();
|
||||
this.sendTxProposal(ntxid);
|
||||
var txp = this.txProposals.reject(ntxid, this.getMyCopayerId());
|
||||
this.sendReject(ntxid);
|
||||
this.store();
|
||||
this.emit('txProposalsUpdated');
|
||||
};
|
||||
|
||||
|
||||
|
||||
Wallet.prototype.sign = function(ntxid, cb) {
|
||||
preconditions.checkState(typeof this.getMyCopayerId() !== 'undefined');
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
var myId = self.getMyCopayerId();
|
||||
var txp = self.txProposals.txps[ntxid];
|
||||
if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) {
|
||||
if (cb) cb(false);
|
||||
}
|
||||
|
||||
var txp = self.txProposals.get(ntxid);
|
||||
// if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) {
|
||||
// if (cb) cb(false);
|
||||
// }
|
||||
//
|
||||
var keys = self.privateKey.getForPaths(txp.inputChainPaths);
|
||||
|
||||
var b = txp.builder;
|
||||
|
|
@ -559,14 +698,13 @@ Wallet.prototype.sign = function(ntxid, cb) {
|
|||
}, 10);
|
||||
};
|
||||
|
||||
|
||||
Wallet.prototype.sendTx = function(ntxid, cb) {
|
||||
var txp = this.txProposals.txps[ntxid];
|
||||
if (!txp) return;
|
||||
|
||||
var txp = this.txProposals.get(ntxid);
|
||||
var tx = txp.builder.build();
|
||||
if (!tx.isComplete()) return;
|
||||
if (!tx.isComplete())
|
||||
throw new Error('Tx is not complete. Can not broadcast');
|
||||
this.log('Broadcasting Transaction');
|
||||
|
||||
var scriptSig = tx.ins[0].getScript();
|
||||
var size = scriptSig.serialize().length;
|
||||
|
||||
|
|
@ -577,28 +715,23 @@ Wallet.prototype.sendTx = function(ntxid, cb) {
|
|||
this.blockchain.sendRawTransaction(txHex, function(txid) {
|
||||
self.log('BITCOIND txid:', txid);
|
||||
if (txid) {
|
||||
self.txProposals.setSent(ntxid, txid);
|
||||
self.txProposals.get(ntxid).setSent(txid);
|
||||
self.sendTxProposal(ntxid);
|
||||
self.store();
|
||||
return cb(txid);
|
||||
} else {
|
||||
self.log('Sent failed. Checking is the TX was sent already');
|
||||
self._checkSentTx(ntxid, function(txid) {
|
||||
console.log('[Wallet.js.730:txid:]', txid); //TODO
|
||||
if (txid)
|
||||
self.store();
|
||||
|
||||
return cb(txid);
|
||||
});
|
||||
}
|
||||
return cb(txid);
|
||||
});
|
||||
};
|
||||
|
||||
Wallet.prototype.addSeenToTxProposals = function() {
|
||||
var ret = false;
|
||||
var myId = this.getMyCopayerId();
|
||||
|
||||
for (var k in this.txProposals.txps) {
|
||||
var txp = this.txProposals.txps[k];
|
||||
if (!txp.seenBy[myId]) {
|
||||
|
||||
txp.seenBy[myId] = Date.now();
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
// TODO: remove this method and use getAddressesInfo everywhere
|
||||
Wallet.prototype.getAddresses = function(opts) {
|
||||
|
|
@ -719,8 +852,9 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
|
|||
var priv = this.privateKey;
|
||||
opts = opts || {};
|
||||
|
||||
preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName());
|
||||
preconditions.checkState(pkr.isComplete());
|
||||
preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName(), 'networkname mismatch');
|
||||
preconditions.checkState(pkr.isComplete(), 'pubkey ring incomplete');
|
||||
preconditions.checkState(priv, 'no private key');
|
||||
if (comment) preconditions.checkArgument(comment.length <= 100);
|
||||
|
||||
if (!opts.remainderOut) {
|
||||
|
|
@ -729,16 +863,16 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
|
|||
};
|
||||
}
|
||||
|
||||
for (var k in Wallet.builderOpts){
|
||||
for (var k in Wallet.builderOpts) {
|
||||
opts[k] = Wallet.builderOpts[k];
|
||||
}
|
||||
|
||||
var b = new Builder(opts)
|
||||
.setUnspent(utxos)
|
||||
.setOutputs([{
|
||||
address: toAddress,
|
||||
amountSatStr: amountSatStr,
|
||||
}]);
|
||||
.setUnspent(utxos)
|
||||
.setOutputs([{
|
||||
address: toAddress,
|
||||
amountSatStr: amountSatStr,
|
||||
}]);
|
||||
|
||||
var selectedUtxos = b.getSelectedUnspent();
|
||||
var inputChainPaths = selectedUtxos.map(function(utxo) {
|
||||
|
|
@ -747,22 +881,23 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
|
|||
|
||||
b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths));
|
||||
|
||||
if (priv) {
|
||||
var keys = priv.getForPaths(inputChainPaths);
|
||||
var signed = b.sign(keys);
|
||||
}
|
||||
var keys = priv.getForPaths(inputChainPaths);
|
||||
var signed = b.sign(keys);
|
||||
var myId = this.getMyCopayerId();
|
||||
var now = Date.now();
|
||||
|
||||
var me = {};
|
||||
|
||||
var tx = b.build();
|
||||
if (priv && tx.countInputSignatures(0)) me[myId] = now;
|
||||
if (!tx.countInputSignatures(0))
|
||||
throw new Error('Could not sign generated tx');
|
||||
|
||||
var me = {};
|
||||
me[myId] = now;
|
||||
|
||||
var meSeen = {};
|
||||
if (priv) meSeen[myId] = now;
|
||||
|
||||
var data = {
|
||||
var ntxid = this.txProposals.add(new TxProposal({
|
||||
inputChainPaths: inputChainPaths,
|
||||
signedBy: me,
|
||||
seenBy: meSeen,
|
||||
|
|
@ -770,9 +905,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
|
|||
createdTs: now,
|
||||
builder: b,
|
||||
comment: comment
|
||||
};
|
||||
|
||||
var ntxid = this.txProposals.add(data);
|
||||
}));
|
||||
return ntxid;
|
||||
};
|
||||
|
||||
|
|
@ -831,29 +964,29 @@ Wallet.prototype.indexDiscovery = function(start, change, cosigner, gap, cb) {
|
|||
var self = this;
|
||||
async.doWhilst(
|
||||
function _do(next) {
|
||||
// Optimize window to minimize the derivations.
|
||||
var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1;
|
||||
var addresses = self.deriveAddresses(scanIndex, scanWindow, change, cosigner);
|
||||
self.blockchain.checkActivity(addresses, function(err, actives) {
|
||||
if (err) throw err;
|
||||
// Optimize window to minimize the derivations.
|
||||
var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1;
|
||||
var addresses = self.deriveAddresses(scanIndex, scanWindow, change, cosigner);
|
||||
self.blockchain.checkActivity(addresses, function(err, actives) {
|
||||
if (err) throw err;
|
||||
|
||||
// Check for new activities in the newlly scanned addresses
|
||||
var recentActive = actives.reduce(function(r, e, i) {
|
||||
return e ? scanIndex + i : r;
|
||||
}, lastActive);
|
||||
hasActivity = lastActive != recentActive;
|
||||
lastActive = recentActive;
|
||||
scanIndex += scanWindow;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function _while() {
|
||||
return hasActivity;
|
||||
},
|
||||
function _finnaly(err) {
|
||||
if (err) return cb(err);
|
||||
cb(null, lastActive);
|
||||
}
|
||||
// Check for new activities in the newlly scanned addresses
|
||||
var recentActive = actives.reduce(function(r, e, i) {
|
||||
return e ? scanIndex + i : r;
|
||||
}, lastActive);
|
||||
hasActivity = lastActive != recentActive;
|
||||
lastActive = recentActive;
|
||||
scanIndex += scanWindow;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function _while() {
|
||||
return hasActivity;
|
||||
},
|
||||
function _finnaly(err) {
|
||||
if (err) return cb(err);
|
||||
cb(null, lastActive);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -913,6 +1046,10 @@ Wallet.prototype.toggleAddressBookEntry = function(key) {
|
|||
this.store();
|
||||
};
|
||||
|
||||
Wallet.prototype.isShared = function() {
|
||||
return this.totalCopayers > 1;
|
||||
}
|
||||
|
||||
Wallet.prototype.isReady = function() {
|
||||
var ret = this.publicKeyRing.isComplete() && this.publicKeyRing.isFullyBackup();
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -102,10 +102,7 @@ WalletFactory.prototype.read = function(walletId) {
|
|||
|
||||
WalletFactory.prototype.create = function(opts) {
|
||||
opts = opts || {};
|
||||
this.log('### CREATING NEW WALLET.' +
|
||||
(opts.id ? ' USING ID: ' + opts.id : ' NEW ID') +
|
||||
(opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey')
|
||||
);
|
||||
this.log('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID') + (opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey'));
|
||||
|
||||
opts.privateKey = opts.privateKey || new PrivateKey({
|
||||
networkName: this.networkName
|
||||
|
|
@ -121,7 +118,8 @@ WalletFactory.prototype.create = function(opts) {
|
|||
});
|
||||
opts.publicKeyRing.addCopayer(
|
||||
opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(),
|
||||
opts.nickname);
|
||||
opts.nickname
|
||||
);
|
||||
this.log('\t### PublicKeyRing Initialized');
|
||||
|
||||
opts.txProposals = opts.txProposals || new TxProposals({
|
||||
|
|
@ -143,6 +141,7 @@ WalletFactory.prototype.create = function(opts) {
|
|||
opts.version = opts.version || this.version;
|
||||
var w = new Wallet(opts);
|
||||
w.store();
|
||||
this.storage.setLastOpened(w.id);
|
||||
return w;
|
||||
};
|
||||
|
||||
|
|
@ -156,9 +155,9 @@ WalletFactory.prototype._checkVersion = function(inVersion) {
|
|||
//We only check for major version differences
|
||||
if (thisV0 < inV0) {
|
||||
throw new Error('Major difference in software versions' +
|
||||
'. Received:' + inVersion +
|
||||
'. Current version:' + this.version +
|
||||
'. Aborting.');
|
||||
'. Received:' + inVersion +
|
||||
'. Current version:' + this.version +
|
||||
'. Aborting.');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -179,6 +178,8 @@ WalletFactory.prototype.open = function(walletId, opts) {
|
|||
if (w) {
|
||||
w.store();
|
||||
}
|
||||
|
||||
this.storage.setLastOpened(walletId);
|
||||
return w;
|
||||
};
|
||||
|
||||
|
|
@ -194,6 +195,7 @@ WalletFactory.prototype.delete = function(walletId, cb) {
|
|||
var s = this.storage;
|
||||
this.log('## DELETING WALLET ID:' + walletId); //TODO
|
||||
s.deleteWallet(walletId);
|
||||
s.setLastOpened(undefined);
|
||||
return cb();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -172,6 +172,13 @@ Storage.prototype.deleteWallet = function(walletId) {
|
|||
}
|
||||
};
|
||||
|
||||
Storage.prototype.setLastOpened = function(walletId) {
|
||||
this.setGlobal('lastOpened', walletId);
|
||||
}
|
||||
|
||||
Storage.prototype.getLastOpened = function() {
|
||||
return this.getGlobal('lastOpened');
|
||||
}
|
||||
|
||||
//obj contains keys to be set
|
||||
Storage.prototype.setFromObj = function(walletId, obj) {
|
||||
|
|
|
|||
|
|
@ -277,17 +277,28 @@ angular.module('copayApp.services')
|
|||
i.outs = outs;
|
||||
i.fee = i.builder.feeSat * satToUnit;
|
||||
i.missingSignatures = tx.countInputMissingSignatures(0);
|
||||
i.actionList = getActionList(i.peerActions);
|
||||
txs.push(i);
|
||||
}
|
||||
});
|
||||
|
||||
$rootScope.txs = txs; //.some(function(i) {return i.isPending; } );
|
||||
$rootScope.txs = txs;
|
||||
if ($rootScope.pendingTxCount < pendingForUs) {
|
||||
$rootScope.txAlertCount = pendingForUs;
|
||||
}
|
||||
$rootScope.pendingTxCount = pendingForUs;
|
||||
};
|
||||
|
||||
function getActionList(actions) {
|
||||
var peers = Object.keys(actions).map(function(i) {
|
||||
return {cId: i, actions: actions[i] }
|
||||
});
|
||||
|
||||
return peers.sort(function(a, b) {
|
||||
return !!b.actions.create - !!a.actions.create;
|
||||
});
|
||||
}
|
||||
|
||||
$rootScope.$watch('insightError', function(status) {
|
||||
if (status) {
|
||||
if (status === -1) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue