mirror of https://github.com/BTCPrivate/copay.git
WIP: sign message
This commit is contained in:
parent
82bfa7d855
commit
05325b2d92
|
@ -25,7 +25,7 @@ angular.module('copayApp.controllers').controller('SendController',
|
|||
$scope.isRateAvailable = false;
|
||||
$scope.rateService = rateService;
|
||||
$scope.showScanner = false;
|
||||
|
||||
$scope.myId = w.getMyCopayerId();
|
||||
|
||||
rateService.whenAvailable(function() {
|
||||
$scope.isRateAvailable = true;
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var Transaction = bitcore.Transaction;
|
||||
|
||||
function BuilderMockV0 (data) {
|
||||
this.vanilla = data;
|
||||
this.tx = new Transaction();
|
||||
this.tx.parse(new Buffer(data.tx, 'hex'));
|
||||
};
|
||||
|
||||
BuilderMockV0.prototype.build = function() {
|
||||
return this.tx;
|
||||
};
|
||||
|
||||
|
||||
BuilderMockV0.prototype.getSelectedUnspent = function() {
|
||||
return [];
|
||||
};
|
||||
|
||||
BuilderMockV0.prototype.toObj = function() {
|
||||
return this.vanilla;
|
||||
};
|
||||
|
||||
module.exports = BuilderMockV0;
|
|
@ -1,15 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var _ = require('lodash');
|
||||
var preconditions = require('preconditions').singleton();
|
||||
|
||||
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 log = require('../log');
|
||||
|
||||
var TX_MAX_SIZE_KB = 50;
|
||||
var VERSION = 1;
|
||||
|
@ -26,13 +27,12 @@ function TxProposal(opts) {
|
|||
this.version = opts.version;
|
||||
this.builder = opts.builder;
|
||||
this.createdTs = opts.createdTs;
|
||||
this._inputSigners = [];
|
||||
|
||||
// CopayerIds
|
||||
this.creator = opts.creator;
|
||||
// Copayer Actions ( copayerId: timeStamp )
|
||||
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;
|
||||
|
@ -41,7 +41,8 @@ function TxProposal(opts) {
|
|||
this.paymentAckMemo = null;
|
||||
this.paymentProtocolURL = opts.paymentProtocolURL || null;
|
||||
|
||||
if (opts.creator) {
|
||||
// New Tx Proposal
|
||||
if (_.isEmpty(this.seenBy) && opts.creator) {
|
||||
var now = Date.now();
|
||||
var me = {};
|
||||
me[opts.creator] = now;
|
||||
|
@ -55,8 +56,6 @@ function TxProposal(opts) {
|
|||
throw new Error('Could not sign generated tx');
|
||||
}
|
||||
}
|
||||
|
||||
this._sync();
|
||||
}
|
||||
|
||||
TxProposal.prototype._checkPayPro = function() {
|
||||
|
@ -99,7 +98,7 @@ TxProposal.prototype.sign = function(keys, signerId) {
|
|||
this.builder.sign(keys);
|
||||
|
||||
var signaturesAdded = this.countSignatures() > before;
|
||||
if (signaturesAdded){
|
||||
if (signaturesAdded) {
|
||||
this.signedBy[signerId] = Date.now();
|
||||
}
|
||||
return signaturesAdded;
|
||||
|
@ -117,6 +116,7 @@ TxProposal.prototype._check = function() {
|
|||
if (txSize / 1024 > TX_MAX_SIZE_KB)
|
||||
throw new Error('BIG: Invalid TX proposal. Too big: ' + txSize + ' bytes');
|
||||
|
||||
console.log('[TxProposal.js.118]'); //TODO
|
||||
if (!tx.ins.length)
|
||||
throw new Error('Invalid tx proposal: no ins');
|
||||
|
||||
|
@ -153,47 +153,80 @@ TxProposal.prototype.addMerchantData = function(merchantData) {
|
|||
this._checkPayPro();
|
||||
};
|
||||
|
||||
TxProposal.prototype.getScriptSigs = function() {
|
||||
var tx = this.builder.build();
|
||||
var sigs = _.map(tx.ins, function(value) {
|
||||
value.s.toString('hex');
|
||||
});
|
||||
console.log('[TxProposal.js.161:sigs:]',sigs); //TODO
|
||||
|
||||
return sigs;
|
||||
};
|
||||
|
||||
TxProposal.prototype.rejectCount = function() {
|
||||
return _.size(this.rejectedBy);
|
||||
};
|
||||
|
||||
TxProposal.prototype.isPending = function(maxRejectCount) {
|
||||
preconditions.checkArgument(typeof maxRejectCount != 'undefined');
|
||||
|
||||
if (this.rejectCount() > maxRejectCount || this.sentTxid)
|
||||
TxProposal.prototype.isFinallyRejected = function(maxRejectCount) {
|
||||
return this.rejectCount() > maxRejectCount;
|
||||
};
|
||||
|
||||
TxProposal.prototype.isPending = function(maxRejectCount) {
|
||||
preconditions.checkArgument(_.isNumber(maxRejectCount));
|
||||
|
||||
if (this.isFinallyRejected(maxRejectCount) || this.sentTxid)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
TxProposal.prototype._updateSignedBy = function() {
|
||||
this._inputSigners = [];
|
||||
/**
|
||||
* getSignersPubKey
|
||||
* @desc get Pubkeys of signers, for each input
|
||||
*
|
||||
* @return {string[][]} array of arrays for pubkeys for each input
|
||||
*/
|
||||
TxProposal.prototype.getSignersPubKey = function(forceUpdate) {
|
||||
|
||||
var tx = this.builder.build();
|
||||
for (var i in tx.ins) {
|
||||
var scriptSig = new Script(tx.ins[i].s);
|
||||
var signatureCount = scriptSig.countSignatures();
|
||||
var signersPubKey = [];
|
||||
|
||||
var info = TxProposal._infoFromRedeemScript(scriptSig);
|
||||
var txSigHash = tx.hashForSignature(info.script, parseInt(i), Transaction.SIGHASH_ALL);
|
||||
var signersPubKey = TxProposal._verifySignatures(info.keys, scriptSig, txSigHash);
|
||||
if (signersPubKey.length !== signatureCount)
|
||||
throw new Error('Invalid signature');
|
||||
if (!this._signersPubKey || forceUpdate) {
|
||||
|
||||
this._inputSigners[i] = signersPubKey;
|
||||
};
|
||||
log.debug('Verifing signatures...');
|
||||
|
||||
var tx = this.builder.build();
|
||||
_.each(tx.ins, function(input, index) {
|
||||
|
||||
var scriptSig = new Script(input.s);
|
||||
var signatureCount = scriptSig.countSignatures();
|
||||
console.log('[TxProposal.js.191:signatureCount:]', signatureCount); //TODO
|
||||
|
||||
var info = TxProposal._infoFromRedeemScript(scriptSig);
|
||||
var txSigHash = tx.hashForSignature(info.script, parseInt(index), Transaction.SIGHASH_ALL);
|
||||
var inputSignersPubKey = TxProposal._verifySignatures(info.keys, scriptSig, txSigHash);
|
||||
console.log('[TxProposal.js.197:inputSignersPubKey:]', inputSignersPubKey); //TODO
|
||||
|
||||
// Does scriptSig has strings that are not signatures?
|
||||
if (inputSignersPubKey.length !== signatureCount)
|
||||
throw new Error('Invalid signature');
|
||||
|
||||
signersPubKey[index] = inputSignersPubKey;
|
||||
});
|
||||
this._signersPubKey = signersPubKey;
|
||||
}
|
||||
|
||||
return this._signersPubKey;
|
||||
};
|
||||
|
||||
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');
|
||||
|
||||
if (!this.ntxid) {
|
||||
this.ntxid = this.builder.build().getNormalizedHash().toString('hex');
|
||||
}
|
||||
return this.ntxid;
|
||||
};
|
||||
|
||||
TxProposal.prototype.toObj = function() {
|
||||
|
@ -237,18 +270,19 @@ TxProposal.fromObj = function(o, forceOpts) {
|
|||
try {
|
||||
o.builder = TransactionBuilder.fromObj(o.builderObj);
|
||||
} catch (e) {
|
||||
|
||||
// backwards (V0) compatatibility fix.
|
||||
if (!o.version) {
|
||||
o.builder = new BuilderMockV0(o.builderObj);
|
||||
o.readonly = 1;
|
||||
};
|
||||
log.info('Ignoring TXP:', e);
|
||||
return null;
|
||||
}
|
||||
return new TxProposal(o);
|
||||
};
|
||||
|
||||
TxProposal.fromUntrustedObj = function(o, forceOpts) {
|
||||
return TxProposal.fromObj(TxProposal._trim(o), forceOpts);
|
||||
var txp = TxProposal.fromObj(TxProposal._trim(o), forceOpts);
|
||||
if (!txp)
|
||||
throw new Error('Invalid Transaction');
|
||||
|
||||
txp._check();
|
||||
return txp;
|
||||
};
|
||||
|
||||
TxProposal.prototype.toObjTrim = function() {
|
||||
|
@ -340,17 +374,7 @@ TxProposal.prototype.getSent = function() {
|
|||
return this.sentTs;
|
||||
}
|
||||
|
||||
TxProposal.prototype._allSignatures = function() {
|
||||
var ret = {};
|
||||
for (var i in this._inputSigners)
|
||||
for (var j in this._inputSigners[i])
|
||||
ret[this._inputSigners[i][j]] = true;
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) {
|
||||
TxProposal.prototype.setCopayers = function(senderId, keyMap) {
|
||||
var newCopayer = {},
|
||||
oldCopayers = {},
|
||||
newSignedBy = {},
|
||||
|
@ -375,7 +399,8 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) {
|
|||
}
|
||||
|
||||
|
||||
var iSig = this._inputSigners[0];
|
||||
var iSig = this.getSignersPubKey();
|
||||
console.log('[TxProposal.js.374:iSig:]', iSig, keyMap); //TODO
|
||||
for (var i in iSig) {
|
||||
var copayerId = keyMap[iSig[i]];
|
||||
|
||||
|
@ -390,10 +415,6 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) {
|
|||
}
|
||||
}
|
||||
|
||||
// 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');
|
||||
|
||||
|
@ -414,21 +435,20 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) {
|
|||
delete this.rejectedBy[i];
|
||||
}
|
||||
|
||||
console.log('[TxProposal.js.410:newCopayer:]', newCopayer); //TODO
|
||||
return Object.keys(newCopayer);
|
||||
};
|
||||
|
||||
// merge will not merge any metadata.
|
||||
TxProposal.prototype.merge = function(incoming) {
|
||||
preconditions.checkArgument(_.isFunction(incoming._sync));
|
||||
incoming._sync();
|
||||
|
||||
// Note that all inputs must have the same number of signatures, so checking
|
||||
// one (0) is OK.
|
||||
var before = this._inputSigners[0].length;
|
||||
var before = this._inputSigners[0] ? this._inputSigners[0].length : 0;
|
||||
this.builder.merge(incoming.builder);
|
||||
this._sync();
|
||||
|
||||
var after = this._inputSigners[0].length;
|
||||
console.log('[TxProposal.js.442:after:]', before, after); //TODO
|
||||
return after !== before;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
var BuilderMockV0 = require('./BuilderMockV0');;
|
||||
var bitcore = require('bitcore');
|
||||
var util = bitcore.util;
|
||||
var Transaction = bitcore.Transaction;
|
||||
var BuilderMockV0 = require('./BuilderMockV0');;
|
||||
var TxProposal = require('./TxProposal');;
|
||||
var Script = bitcore.Script;
|
||||
var Key = bitcore.Key;
|
||||
|
@ -51,9 +49,9 @@ TxProposals.prototype.getNtxidsSince = function(sinceTs) {
|
|||
preconditions.checkArgument(sinceTs);
|
||||
var ret = [];
|
||||
|
||||
for(var ii in this.txps){
|
||||
for (var ii in this.txps) {
|
||||
var txp = this.txps[ii];
|
||||
if (txp.createdTs >= sinceTs)
|
||||
if (txp.createdTs >= sinceTs)
|
||||
ret.push(ii);
|
||||
}
|
||||
return ret;
|
||||
|
@ -96,34 +94,9 @@ TxProposals.prototype.toObj = function() {
|
|||
};
|
||||
|
||||
|
||||
TxProposals.prototype.merge = function(inObj, builderOpts) {
|
||||
var incomingTx = TxProposal.fromUntrustedObj(inObj, builderOpts);
|
||||
|
||||
var myTxps = this.txps;
|
||||
var ntxid = incomingTx.getId();
|
||||
var ret = {
|
||||
ntxid: ntxid
|
||||
};
|
||||
|
||||
if (myTxps[ntxid]) {
|
||||
|
||||
// Merge an existing txProposal
|
||||
ret.hasChanged = myTxps[ntxid].merge(incomingTx);
|
||||
|
||||
|
||||
} else {
|
||||
// Create a new one
|
||||
ret.new = ret.hasChanged = 1;
|
||||
this.txps[ntxid] = incomingTx;
|
||||
}
|
||||
|
||||
ret.txp = this.txps[ntxid];
|
||||
return ret;
|
||||
};
|
||||
|
||||
// Add a LOCALLY CREATED (trusted) tx proposal
|
||||
TxProposals.prototype.add = function(txp) {
|
||||
txp._sync();
|
||||
var ntxid = txp.getId();
|
||||
this.txps[ntxid] = txp;
|
||||
return ntxid;
|
||||
|
@ -133,64 +106,24 @@ TxProposals.prototype.add = function(txp) {
|
|||
TxProposals.prototype.get = function(ntxid) {
|
||||
var ret = this.txps[ntxid];
|
||||
if (!ret)
|
||||
throw new Error('Unknown TXP: '+ntxid);
|
||||
throw new Error('Unknown TXP: ' + ntxid);
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
TxProposals.prototype.getTxProposal = function(ntxid, copayers) {
|
||||
var txp = this.get(ntxid);
|
||||
|
||||
var i = JSON.parse(JSON.stringify(txp));
|
||||
i.builder = txp.builder;
|
||||
i.ntxid = ntxid;
|
||||
i.peerActions = {};
|
||||
|
||||
if (copayers) {
|
||||
for (var j = 0; j < copayers.length; j++) {
|
||||
var p = copayers[j];
|
||||
i.peerActions[p] = {};
|
||||
}
|
||||
}
|
||||
|
||||
for (var p in txp.seenBy) {
|
||||
i.peerActions[p] = {
|
||||
seen: txp.seenBy[p]
|
||||
};
|
||||
}
|
||||
for (var p in txp.signedBy) {
|
||||
i.peerActions[p] = i.peerActions[p] || {};
|
||||
i.peerActions[p].sign = txp.signedBy[p];
|
||||
}
|
||||
var r = 0;
|
||||
for (var p in txp.rejectedBy) {
|
||||
i.peerActions[p] = i.peerActions[p] || {};
|
||||
i.peerActions[p].rejected = txp.rejectedBy[p];
|
||||
r++;
|
||||
}
|
||||
i.rejectCount = r;
|
||||
|
||||
var c = txp.creator;
|
||||
i.peerActions[c] = i.peerActions[c] || {};
|
||||
i.peerActions[c].create = txp.createdTs;
|
||||
return i;
|
||||
};
|
||||
|
||||
|
||||
//returns the unspent txid-vout used in PENDING Txs
|
||||
TxProposals.prototype.getUsedUnspent = function(maxRejectCount) {
|
||||
var ret = {};
|
||||
for (var i in this.txps) {
|
||||
if (!this.txps[i].isPending(maxRejectCount))
|
||||
continue;
|
||||
var self = this;
|
||||
|
||||
var u = this.txps[i].builder.getSelectedUnspent();
|
||||
var p = this.getTxProposal(i);
|
||||
_.each(this.txps, function(txp) {
|
||||
if (!txp.isPending(maxRejectCount))
|
||||
return
|
||||
|
||||
for (var j in u) {
|
||||
ret[u[j].txid + ',' + u[j].vout] = 1;
|
||||
}
|
||||
}
|
||||
_.each(txp.builder.getSelectedUnspent(), function(u) {
|
||||
ret[u.txid + ',' + u.vout] = 1;
|
||||
});
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
|
|
@ -357,8 +357,6 @@ Wallet.prototype._processProposalEvents = function(senderId, m) {
|
|||
type: 'signed',
|
||||
cId: m.newCopayer[0]
|
||||
};
|
||||
} else {
|
||||
log.error('unknown tx proposal event:', m)
|
||||
}
|
||||
}
|
||||
if (ev)
|
||||
|
@ -366,48 +364,38 @@ Wallet.prototype._processProposalEvents = function(senderId, m) {
|
|||
};
|
||||
|
||||
|
||||
/* OTDO
|
||||
events.push({
|
||||
type: 'signed',
|
||||
cId: k,
|
||||
txId: ntxid
|
||||
});
|
||||
*/
|
||||
/**
|
||||
* @desc
|
||||
* Retrieves a keymap from from a transaction proposal set extracts a maps from
|
||||
* Retrieves a keymap from a transaction proposal set extracts a maps from
|
||||
* public key to cosignerId for each signed input of the transaction proposal.
|
||||
*
|
||||
* @param {TxProposals} txp - the transaction proposals
|
||||
* @return {Object}
|
||||
* @return {Object} [pubkey] -> copayerId
|
||||
*/
|
||||
Wallet.prototype._getKeyMap = function(txp) {
|
||||
preconditions.checkArgument(txp);
|
||||
var inSig0, keyMapAll = {};
|
||||
var inSig0, keyMapAll = {},
|
||||
self = this;
|
||||
|
||||
for (var i in txp._inputSigners) {
|
||||
var keyMap = this.publicKeyRing.copayersForPubkeys(txp._inputSigners[i], txp.inputChainPaths);
|
||||
_.each(txp.getSignersPubKey(), function(inputSignersPubKey, i) {
|
||||
var keyMap = self.publicKeyRing.copayersForPubkeys(inputSignersPubKey, txp.inputChainPaths);
|
||||
|
||||
if (_.size(keyMap) !== _.size(txp._inputSigners[i]))
|
||||
if (_.size(keyMap) !== _.size(inputSignersPubKey))
|
||||
throw new Error('Signature does not match known copayers');
|
||||
|
||||
for (var j in keyMap) {
|
||||
keyMapAll[j] = keyMap[j];
|
||||
}
|
||||
keyMapAll = _.extend(keyMap, keyMapAll);
|
||||
|
||||
// From here -> only to check that all inputs have the same sigs
|
||||
var inSigArr = [];
|
||||
_.each(keyMap, function(value, key) {
|
||||
inSigArr.push(value);
|
||||
});
|
||||
var inSigArr = _.values(keyMap);
|
||||
var inSig = JSON.stringify(inSigArr.sort());
|
||||
if (i === '0') {
|
||||
|
||||
if (!inSig0) {
|
||||
inSig0 = inSig;
|
||||
continue;
|
||||
} else {
|
||||
if (inSig !== inSig0)
|
||||
throw new Error('found inputs with different signatures');
|
||||
}
|
||||
if (inSig !== inSig0)
|
||||
throw new Error('found inputs with different signatures');
|
||||
}
|
||||
});
|
||||
return keyMapAll;
|
||||
};
|
||||
|
||||
|
@ -442,11 +430,10 @@ Wallet.prototype._checkIfTxIsSent = function(ntxid, cb) {
|
|||
* and send `seen` messages to peers if aplicable.
|
||||
* @param ntxid
|
||||
*/
|
||||
Wallet.prototype._setTxProposalSeen = function(ntxid) {
|
||||
var txp = this.txProposals.get(ntxid);
|
||||
Wallet.prototype._setTxProposalSeen = function(txp) {
|
||||
if (!txp.getSeen(this.getMyCopayerId())) {
|
||||
txp.setSeen(this.getMyCopayerId());
|
||||
this.sendSeen(ntxid);
|
||||
this.sendSeen(txp.getId());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -458,17 +445,13 @@ Wallet.prototype._setTxProposalSeen = function(ntxid) {
|
|||
* @param ntxid
|
||||
* @param {transactionCallback} cb
|
||||
*/
|
||||
Wallet.prototype._updateTxProposalSent = function(ntxid, cb) {
|
||||
Wallet.prototype._updateTxProposalSent = function(txp, cb) {
|
||||
var self = this;
|
||||
var txp = this.txProposals.get(ntxid);
|
||||
|
||||
this._checkIfTxIsSent(ntxid, function(err, txid) {
|
||||
this._checkIfTxIsSent(txp.getId(), function(err, txid) {
|
||||
if (err) return cb(err);
|
||||
|
||||
if (txid) {
|
||||
if (!txp.getSent()) {
|
||||
txp.setSent(txid);
|
||||
}
|
||||
txp.setSent(txid);
|
||||
self.emitAndKeepAlive('txProposalsUpdated');
|
||||
}
|
||||
if (cb)
|
||||
|
@ -486,13 +469,10 @@ Wallet.prototype._updateTxProposalSent = function(ntxid, cb) {
|
|||
* @param mergeInfo Proposals merge information, as returned by TxProposals.merge
|
||||
* @return {fetchPaymentRequestCallback}
|
||||
*/
|
||||
Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) {
|
||||
Wallet.prototype._processTxProposalPayPro = function(txp, cb) {
|
||||
var self = this;
|
||||
var txp = mergeInfo.txp;
|
||||
var isNew = mergeInfo.new;
|
||||
var ntxid = mergeInfo.ntxid;
|
||||
|
||||
if (!isNew || !txp.paymentProtocolURL)
|
||||
if (!txp.paymentProtocolURL)
|
||||
return cb();
|
||||
|
||||
log.info('Received a Payment Protocol TX Proposal');
|
||||
|
@ -515,26 +495,23 @@ Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) {
|
|||
/**
|
||||
* _processIncomingTxProposal
|
||||
*
|
||||
* @desc Process an incoming transaction proposal. Runs safety and sanity checks on it.
|
||||
* @desc Process an NEW incoming transaction proposal. Runs safety and sanity checks on it.
|
||||
*
|
||||
* @param mergeInfo Proposals merge information, as returned by TxProposals.merge
|
||||
* @return {errCallback}
|
||||
*/
|
||||
Wallet.prototype._processIncomingTxProposal = function(mergeInfo, cb) {
|
||||
if (!mergeInfo) return cb();
|
||||
Wallet.prototype._processIncomingNewTxProposal = function(txp, cb) {
|
||||
var self = this;
|
||||
|
||||
self._processTxProposalPayPro(mergeInfo, function(err) {
|
||||
var ntxid = txp.getId();
|
||||
self._processTxProposalPayPro(txp, function(err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
self._setTxProposalSeen(mergeInfo.ntxid);
|
||||
self._setTxProposalSeen(txp);
|
||||
|
||||
var tx = mergeInfo.txp.builder.build();
|
||||
if (tx.isComplete())
|
||||
self._updateTxProposalSent(mergeInfo.ntxid);
|
||||
else {
|
||||
self.emitAndKeepAlive('txProposalsUpdated');
|
||||
}
|
||||
var tx = txp.builder.build();
|
||||
if (tx.isComplete() && !txp.getSent())
|
||||
self._updateTxProposalSent(txp);
|
||||
return cb();
|
||||
});
|
||||
};
|
||||
|
@ -550,36 +527,34 @@ Wallet.prototype._processIncomingTxProposal = function(mergeInfo, cb) {
|
|||
*/
|
||||
Wallet.prototype._onTxProposal = function(senderId, data) {
|
||||
var self = this;
|
||||
var m;
|
||||
var incomingTx = TxProposal.fromUntrustedObj(data.txProposal, Wallet.builderOpts);
|
||||
var incomingNtxid = incomingTx.getId();
|
||||
|
||||
try {
|
||||
m = self.txProposals.merge(data.txProposal, Wallet.builderOpts);
|
||||
var keyMap = self._getKeyMap(m.txp);
|
||||
m.newCopayer = m.txp.setCopayers(senderId, keyMap);
|
||||
} catch (e) {
|
||||
log.error('Corrupt TX proposal received from:', senderId, e.toString());
|
||||
if (m && m.ntxid)
|
||||
self.txProposals.deleteOne(m.ntxid);
|
||||
m = null;
|
||||
var localTx = this.txProposals.get(incomingNtxid);
|
||||
} catch (e) {};
|
||||
|
||||
if (localTx) {
|
||||
log.debug('Ignoring existing tx Proposal:' + incomingNtxid);
|
||||
console.log('')
|
||||
return;
|
||||
}
|
||||
|
||||
if (m) {
|
||||
self._processIncomingNewTxProposal(incomingTx, function(err) {
|
||||
if (err) {
|
||||
log.info('Corrupt TX proposal received from:', senderId, err.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
self._processIncomingTxProposal(m, function(err) {
|
||||
var keyMap = self._getKeyMap(incomingTx);
|
||||
incomingTx.setCopayers(senderId, keyMap);
|
||||
|
||||
if (err) {
|
||||
log.error('Corrupt TX proposal received from:', senderId, err.toString());
|
||||
if (m && m.ntxid)
|
||||
self.txProposals.deleteOne(m.ntxid);
|
||||
m = null;
|
||||
} else {
|
||||
if (m && m.hasChanged)
|
||||
self.sendTxProposal(m.ntxid);
|
||||
}
|
||||
|
||||
self._processProposalEvents(senderId, m);
|
||||
self.txProposals.add(incomingTx);
|
||||
self.emitAndKeepAlive('txProposalEvent', {
|
||||
type: 'new',
|
||||
cId: senderId,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1278,6 +1253,24 @@ Wallet.prototype.sendReject = function(ntxid) {
|
|||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @desc Send a signature for a TX Proposal
|
||||
* @param {string} ntxid
|
||||
*/
|
||||
Wallet.prototype.sendSignature = function(ntxid) {
|
||||
preconditions.checkArgument(ntxid);
|
||||
|
||||
var txp = this.txProposals.get(ntxid);
|
||||
|
||||
this._sendToPeers(null, {
|
||||
type: 'sign',
|
||||
signatures: txp.getScriptSigs(),
|
||||
walletId: this.id,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @desc Notify other peers that a wallet has been backed up and it's ready to be used
|
||||
* @param {string[]} [recipients] - the pubkeys of the recipients
|
||||
|
@ -1396,45 +1389,28 @@ Wallet.prototype.generateAddress = function(isChange, cb) {
|
|||
};
|
||||
|
||||
/**
|
||||
* @desc Retrieve all the Transaction proposals (see {@link TxProposals})
|
||||
* @return {Object[]} each object returned represents a transaction proposal, with two additional
|
||||
* booleans: <tt>signedByUs</tt> and <tt>rejectedByUs</tt>. An optional third boolean signals
|
||||
* whether the transaction was finally rejected (<tt>finallyRejected</tt> set to true).
|
||||
*/
|
||||
Wallet.prototype.getTxProposals = function() {
|
||||
var ret = [];
|
||||
var self = this;
|
||||
var copayers = self.getRegisteredCopayerIds();
|
||||
var myId = self.getMyCopayerId();
|
||||
|
||||
_.each(self.txProposals.txps, function(txp, ntxid){
|
||||
txp.signedByUs = txp.signedBy[myId] ? true : false;
|
||||
txp.rejectedByUs = txp.rejectedBy[self.getMyCopayerId()] ? true : false;
|
||||
txp.finallyRejected = self.totalCopayers - txp.rejectCount < self.requiredCopayers;
|
||||
txp.isPending = !txp.finallyRejected && !txp.sentTxid;
|
||||
|
||||
if (!txp.readonly || txp.finallyRejected || txp.sentTs) {
|
||||
ret.push(txp);
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: get this out of here
|
||||
* @desc get list of actions (see {@link getPendingTxProposals})
|
||||
*/
|
||||
Wallet.prototype._getActionList = function(actions) {
|
||||
if (!actions) return;
|
||||
var peers = Object.keys(actions).map(function(i) {
|
||||
return {
|
||||
cId: i,
|
||||
actions: actions[i]
|
||||
}
|
||||
});
|
||||
Wallet.prototype._getActionList = function(txp) {
|
||||
preconditions.checkArgument(txp);
|
||||
|
||||
return peers.sort(function(a, b) {
|
||||
return !!b.actions.create - !!a.actions.create;
|
||||
var self = this;
|
||||
var peers = [];
|
||||
|
||||
_.each(self.getRegisteredCopayerIds(), function(copayerId) {
|
||||
var actions = {
|
||||
rejected: txp.rejectedBy[copayerId],
|
||||
sign: txp.signedBy[copayerId],
|
||||
seen: txp.seenBy[copayerId],
|
||||
create: (txp.creator === copayerId) ? txp.createdTs : null,
|
||||
};
|
||||
peers.push({
|
||||
cId: copayerId,
|
||||
actions: actions,
|
||||
});
|
||||
});
|
||||
return peers;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1446,15 +1422,22 @@ Wallet.prototype.getPendingTxProposals = function() {
|
|||
var ret = [];
|
||||
ret.txs = [];
|
||||
var pendingForUs = 0;
|
||||
var txps = this.getTxProposals();
|
||||
var txps = this.txProposals.txps;
|
||||
var maxRejectCount = this.maxRejectCount();
|
||||
var satToUnit = 1 / this.settings.unitToSatoshi;
|
||||
|
||||
_.each(_.where(txps, 'isPending'), function(txp) {
|
||||
_.each(txps, function(inTxp, ntxid) {
|
||||
if (!inTxp.isPending(maxRejectCount))
|
||||
return;
|
||||
|
||||
var txp = _.clone(inTxp);
|
||||
txp.ntxid = ntxid;
|
||||
|
||||
pendingForUs++;
|
||||
var addresses = {};
|
||||
var outs = JSON.parse(txp.builder.vanilla.outs);
|
||||
outs.forEach(function(o) {
|
||||
if (!self.publicKeyRing.addressToPath[o.Straddress]) {
|
||||
if (!self.addressIsOwn(o.address)) {
|
||||
if (!addresses[o.address]) addresses[o.address] = 0;
|
||||
addresses[o.address] += (o.amountSatStr || Math.round(o.amount * bitcore.util.COIN));
|
||||
};
|
||||
|
@ -1469,7 +1452,7 @@ Wallet.prototype.getPendingTxProposals = function() {
|
|||
// extra fields
|
||||
txp.fee = txp.builder.feeSat * satToUnit;
|
||||
txp.missingSignatures = txp.builder.build().countInputMissingSignatures(0);
|
||||
txp.actionList = self._getActionList(txp.peerActions);
|
||||
txp.actionList = self._getActionList(txp);
|
||||
ret.txs.push(txp);
|
||||
});
|
||||
|
||||
|
@ -1552,10 +1535,10 @@ Wallet.prototype.sign = function(ntxid) {
|
|||
Wallet.prototype.signAndSend = function(ntxid, cb) {
|
||||
if (this.sign(ntxid)) {
|
||||
var txp = this.txProposals.get(ntxid);
|
||||
this.sendSignature(ntxid);
|
||||
if (txp.isFullySigned()) {
|
||||
return this.broadcastTx(ntxid, cb);
|
||||
} else {
|
||||
this.sendTxProposal(ntxid);
|
||||
return cb(null, ntxid, Wallet.TX_SIGNED);
|
||||
}
|
||||
} else {
|
||||
|
@ -2133,58 +2116,6 @@ Wallet.prototype.getUnspent = function(cb) {
|
|||
});
|
||||
};
|
||||
|
||||
// TODO. not used.
|
||||
Wallet.prototype.removeTxWithSpentInputs = function(cb) {
|
||||
var self = this;
|
||||
|
||||
cb = cb || function() {};
|
||||
|
||||
if (!_.some(self.getTxProposals(), {
|
||||
isPending: true
|
||||
}))
|
||||
return cb();
|
||||
|
||||
var proposalsChanged = false;
|
||||
this.blockchain.getUnspent(this.getAddressesStr(), function(err, unspentList) {
|
||||
if (err) return cb(err);
|
||||
|
||||
var txps = _.where(self.getTxProposals(), {
|
||||
isPending: true
|
||||
});
|
||||
if (txps.length === 0) return cb();
|
||||
|
||||
var inputs = _.flatten(_.map(txps, function(txp) {
|
||||
return _.map(txp.builder.utxos, function(utxo) {
|
||||
return {
|
||||
ntxid: txp.ntxid,
|
||||
txid: utxo.txid,
|
||||
vout: utxo.vout,
|
||||
};
|
||||
});
|
||||
}));
|
||||
|
||||
_.each(unspentList, function(unspent) {
|
||||
_.each(inputs, function(input) {
|
||||
input.unspent = input.unspent || (input.txid === unspent.txid && input.vout === unspent.vout);
|
||||
});
|
||||
});
|
||||
|
||||
_.each(inputs, function(input) {
|
||||
if (!input.unspent) {
|
||||
proposalsChanged = true;
|
||||
self.txProposals.deleteOne(input.ntxid);
|
||||
}
|
||||
});
|
||||
|
||||
if (proposalsChanged) {
|
||||
self.emitAndKeepAlive('txProposalsUpdated');
|
||||
}
|
||||
|
||||
return cb();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* spend
|
||||
*
|
||||
|
@ -2252,7 +2183,6 @@ Wallet.prototype.spend = function(opts, cb) {
|
|||
|
||||
log.debug('TXP Added: ', ntxid);
|
||||
|
||||
console.log('[Wallet.js.2233]'); //TODO
|
||||
self.sendIndexes();
|
||||
// Needs only one signature? Broadcast it!
|
||||
if (!self.requiresMultipleSignatures()) {
|
||||
|
@ -2361,6 +2291,7 @@ Wallet.prototype._createTxProposal = function(toAddress, amountSat, comment, utx
|
|||
signWith: keys,
|
||||
});
|
||||
|
||||
console.log('[Wallet.js.2303]'); //TODO
|
||||
return txp;
|
||||
};
|
||||
|
||||
|
@ -2590,7 +2521,7 @@ Wallet.prototype.getTransactionHistory = function(opts, cb) {
|
|||
opts = opts || {};
|
||||
|
||||
var addresses = self.getAddressesInfo();
|
||||
var proposals = self.getTxProposals();
|
||||
var proposals = self.txProposals.txps;
|
||||
var satToUnit = 1 / self.settings.unitToSatoshi;
|
||||
|
||||
var indexedProposals = _.indexBy(proposals, 'sentTxid');
|
||||
|
@ -2689,18 +2620,11 @@ Wallet.prototype.getTransactionHistory = function(opts, cb) {
|
|||
if (proposal) {
|
||||
// TODO refactor
|
||||
tx.comment = proposal.comment;
|
||||
tx.sentTs = proposal.sentTs;
|
||||
tx.merchant = proposal.merchant;
|
||||
tx.peerActions = proposal.peerActions;
|
||||
tx.finallyRejected = proposal.finallyRejected;
|
||||
tx.merchant = proposal.merchant;
|
||||
tx.paymentAckMemo = proposal.paymentAckMemo;
|
||||
tx.peerActions = proposal.peerActions;
|
||||
tx.finallyRejected = proposal.finallyRejected;
|
||||
|
||||
if (tx.peerActions) {
|
||||
tx.actionList = self._getActionList(tx.peerActions);
|
||||
}
|
||||
tx.actionList = self._getActionList(proposal);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -104,11 +104,11 @@ describe('TxProposal', function() {
|
|||
});
|
||||
|
||||
});
|
||||
describe('#fromObj', function() {
|
||||
describe('#fromUntrustedObj', function() {
|
||||
it('should fail to create from wrong object', function() {
|
||||
var b = new FakeBuilder();
|
||||
(function() {
|
||||
var txp = TxProposal.fromObj({
|
||||
var txp = TxProposal.fromUntrustedObj({
|
||||
creator: 1,
|
||||
createdTs: 1,
|
||||
builderObj: b.toObj(),
|
||||
|
@ -131,7 +131,7 @@ describe('TxProposal', function() {
|
|||
inputChainPaths: ['m/1'],
|
||||
};
|
||||
(function() {
|
||||
txp = TxProposal.fromObj(o, {
|
||||
txp = TxProposal.fromUntrustedObj(o, {
|
||||
pepe: 100
|
||||
});
|
||||
}).should.throw('Invalid tx proposal: no ins');
|
||||
|
@ -143,6 +143,9 @@ describe('TxProposal', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
describe('#fromObj', function() {
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('#setSent', function() {
|
||||
|
|
|
@ -35,6 +35,7 @@ function FakeBuilder() {
|
|||
this.test = 1;
|
||||
this.tx = new Tx();
|
||||
this.signhash = 1;
|
||||
this.version =1;
|
||||
this.inputMap = [{
|
||||
address: '2NDJbzwzsmRgD2o5HHXPhuq5g6tkKTjYkd6',
|
||||
scriptPubKey: new Script(new Buffer('a914dc0623476aefb049066b09b0147a022e6eb8429187', 'hex')),
|
||||
|
@ -48,7 +49,6 @@ function FakeBuilder() {
|
|||
address: '2NDJbzwzsmRgD2o5HHXPhuq5g6tkKTjYkd6',
|
||||
amountSatStr: '123',
|
||||
}]),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
<div class="last-transactions-footer">
|
||||
<div class="row collapse">
|
||||
<div class="large-5 medium-7 small-12 columns" ng-show="!tx.sentTs">
|
||||
<div ng-show="!tx.signedByUs && !tx.rejectedByUs && !tx.finallyRejected && tx.missingSignatures">
|
||||
<div ng-show="!tx.signedBy[myId] && !tx.rejectedBy[myId]">
|
||||
<div class="hide-for-small-only">
|
||||
<button class="primary tiny m0 m15r" ng-click="sign(tx.ntxid)" ng-disabled="loading">
|
||||
<i class="fi-check"></i> <span translate>Sign</span>
|
||||
|
|
Loading…
Reference in New Issue