mirror of https://github.com/BTCPrivate/copay.git
commit
3784c7931a
|
@ -25,7 +25,7 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
$scope.isRateAvailable = false;
|
$scope.isRateAvailable = false;
|
||||||
$scope.rateService = rateService;
|
$scope.rateService = rateService;
|
||||||
$scope.showScanner = false;
|
$scope.showScanner = false;
|
||||||
|
$scope.myId = w.getMyCopayerId();
|
||||||
|
|
||||||
rateService.whenAvailable(function() {
|
rateService.whenAvailable(function() {
|
||||||
$scope.isRateAvailable = true;
|
$scope.isRateAvailable = true;
|
||||||
|
@ -369,6 +369,8 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
notification.success('Success', 'Transaction proposal created');
|
notification.success('Success', 'Transaction proposal created');
|
||||||
else if (status == copay.Wallet.TX_SIGNED)
|
else if (status == copay.Wallet.TX_SIGNED)
|
||||||
notification.success('Success', 'Transaction proposal was signed');
|
notification.success('Success', 'Transaction proposal was signed');
|
||||||
|
else if (status == copay.Wallet.TX_SIGNED_AND_BROADCASTED)
|
||||||
|
notification.success('Success', 'Transaction signed and broadcasted!');
|
||||||
else
|
else
|
||||||
notification.error('Error', 'Unknown error occured');
|
notification.error('Error', 'Unknown error occured');
|
||||||
};
|
};
|
||||||
|
@ -378,7 +380,7 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
$scope.error = $scope.success = null;
|
$scope.error = $scope.success = null;
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
$rootScope.txAlertCount = 0;
|
$rootScope.txAlertCount = 0;
|
||||||
w.broadcastTx(ntxid, function(err, txid, status) {
|
w.issueTx(ntxid, function(err, txid, status) {
|
||||||
$scope.notifyStatus(status);
|
$scope.notifyStatus(status);
|
||||||
if (cb) return cb();
|
if (cb) return cb();
|
||||||
else $scope.loadTxs();
|
else $scope.loadTxs();
|
||||||
|
|
|
@ -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';
|
'use strict';
|
||||||
|
|
||||||
var bitcore = require('bitcore');
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
var preconditions = require('preconditions').singleton();
|
||||||
|
|
||||||
|
var bitcore = require('bitcore');
|
||||||
var util = bitcore.util;
|
var util = bitcore.util;
|
||||||
var Transaction = bitcore.Transaction;
|
var Transaction = bitcore.Transaction;
|
||||||
var BuilderMockV0 = require('./BuilderMockV0');;
|
|
||||||
var TransactionBuilder = bitcore.TransactionBuilder;
|
var TransactionBuilder = bitcore.TransactionBuilder;
|
||||||
var Script = bitcore.Script;
|
var Script = bitcore.Script;
|
||||||
var Key = bitcore.Key;
|
var Key = bitcore.Key;
|
||||||
var buffertools = bitcore.buffertools;
|
|
||||||
var preconditions = require('preconditions').instance();
|
var log = require('../log');
|
||||||
|
|
||||||
var TX_MAX_SIZE_KB = 50;
|
var TX_MAX_SIZE_KB = 50;
|
||||||
var VERSION = 1;
|
var VERSION = 1;
|
||||||
|
@ -26,22 +27,24 @@ function TxProposal(opts) {
|
||||||
this.version = opts.version;
|
this.version = opts.version;
|
||||||
this.builder = opts.builder;
|
this.builder = opts.builder;
|
||||||
this.createdTs = opts.createdTs;
|
this.createdTs = opts.createdTs;
|
||||||
this._inputSigners = [];
|
|
||||||
|
|
||||||
// CopayerIds
|
// Copayer Actions ( copayerId: timeStamp )
|
||||||
this.creator = opts.creator;
|
|
||||||
this.signedBy = opts.signedBy || {};
|
this.signedBy = opts.signedBy || {};
|
||||||
this.seenBy = opts.seenBy || {};
|
this.seenBy = opts.seenBy || {};
|
||||||
this.rejectedBy = opts.rejectedBy || {};
|
this.rejectedBy = opts.rejectedBy || {};
|
||||||
|
|
||||||
this.sentTs = opts.sentTs || null;
|
this.sentTs = opts.sentTs || null;
|
||||||
this.sentTxid = opts.sentTxid || null;
|
this.sentTxid = opts.sentTxid || null;
|
||||||
this.comment = opts.comment || null;
|
this.comment = opts.comment || null;
|
||||||
this.readonly = opts.readonly || null;
|
this.readonly = opts.readonly || null;
|
||||||
this.merchant = opts.merchant || null;
|
this.merchant = opts.merchant || null;
|
||||||
this.paymentAckMemo = null;
|
this.paymentAckMemo = opts.paymentAckMemo || null;
|
||||||
this.paymentProtocolURL = opts.paymentProtocolURL || null;
|
this.paymentProtocolURL = opts.paymentProtocolURL || null;
|
||||||
|
|
||||||
if (opts.creator) {
|
this.resetCache();
|
||||||
|
|
||||||
|
// New Tx Proposal
|
||||||
|
if (_.isEmpty(this.seenBy) && opts.creator) {
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
var me = {};
|
var me = {};
|
||||||
me[opts.creator] = now;
|
me[opts.creator] = now;
|
||||||
|
@ -55,8 +58,6 @@ function TxProposal(opts) {
|
||||||
throw new Error('Could not sign generated tx');
|
throw new Error('Could not sign generated tx');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._sync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TxProposal.prototype._checkPayPro = function() {
|
TxProposal.prototype._checkPayPro = function() {
|
||||||
|
@ -94,13 +95,32 @@ TxProposal.prototype.isFullySigned = function() {
|
||||||
return this.builder && this.builder.isFullySigned();
|
return this.builder && this.builder.isFullySigned();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TxProposal.prototype.getMySignatures = function() {
|
||||||
|
preconditions.checkState(this._mySignatures, 'Still no signatures from us');
|
||||||
|
return _.clone(this._mySignatures);
|
||||||
|
};
|
||||||
|
|
||||||
|
TxProposal.prototype._setMySignatures = function(signaturesBefore) {
|
||||||
|
var mySigs = [];
|
||||||
|
_.each(this.getSignatures(), function(signatures, index) {
|
||||||
|
var diff = _.difference(signatures, signaturesBefore[index]);
|
||||||
|
preconditions.checkState(diff.length == 1, 'more that one signature added!');
|
||||||
|
mySigs.push(diff[0].toString('hex'));
|
||||||
|
})
|
||||||
|
this._mySignatures = mySigs;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
TxProposal.prototype.sign = function(keys, signerId) {
|
TxProposal.prototype.sign = function(keys, signerId) {
|
||||||
var before = this.countSignatures();
|
var before = this.countSignatures();
|
||||||
|
var signaturesBefore = this.getSignatures();
|
||||||
this.builder.sign(keys);
|
this.builder.sign(keys);
|
||||||
|
|
||||||
var signaturesAdded = this.countSignatures() > before;
|
var signaturesAdded = this.countSignatures() > before;
|
||||||
if (signaturesAdded){
|
if (signaturesAdded) {
|
||||||
this.signedBy[signerId] = Date.now();
|
this.signedBy[signerId] = Date.now();
|
||||||
|
this._setMySignatures(signaturesBefore);
|
||||||
}
|
}
|
||||||
return signaturesAdded;
|
return signaturesAdded;
|
||||||
};
|
};
|
||||||
|
@ -111,6 +131,7 @@ TxProposal.prototype._check = function() {
|
||||||
throw new Error('Invalid tx proposal');
|
throw new Error('Invalid tx proposal');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Should be able to build
|
||||||
var tx = this.builder.build();
|
var tx = this.builder.build();
|
||||||
|
|
||||||
var txSize = tx.getSize();
|
var txSize = tx.getSize();
|
||||||
|
@ -153,52 +174,207 @@ TxProposal.prototype.addMerchantData = function(merchantData) {
|
||||||
this._checkPayPro();
|
this._checkPayPro();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TxProposal.prototype.getSignatures = function() {
|
||||||
|
var ins = this.builder.build().ins;
|
||||||
|
var sigs = _.map(ins, function(value) {
|
||||||
|
var script = new bitcore.Script(value.s);
|
||||||
|
var nchunks = script.chunks.length;
|
||||||
|
return _.map(script.chunks.slice(1, nchunks - 1), function(buffer) {
|
||||||
|
return buffer.toString('hex');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return sigs;
|
||||||
|
};
|
||||||
|
|
||||||
TxProposal.prototype.rejectCount = function() {
|
TxProposal.prototype.rejectCount = function() {
|
||||||
return _.size(this.rejectedBy);
|
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 false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TxProposal.prototype._setSigned = function(copayerId) {
|
||||||
|
|
||||||
TxProposal.prototype._updateSignedBy = function() {
|
// Sign powns rejected
|
||||||
this._inputSigners = [];
|
if (this.rejectedBy[copayerId]) {
|
||||||
|
log.info("WARN: a previously rejected transaction was signed by:", copayerId);
|
||||||
|
delete this.rejectedBy[copayerId];
|
||||||
|
}
|
||||||
|
|
||||||
var tx = this.builder.build();
|
this.signedBy[copayerId] = Date.now();
|
||||||
for (var i in tx.ins) {
|
|
||||||
var scriptSig = new Script(tx.ins[i].s);
|
|
||||||
var signatureCount = scriptSig.countSignatures();
|
|
||||||
|
|
||||||
var info = TxProposal._infoFromRedeemScript(scriptSig);
|
return this;
|
||||||
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');
|
|
||||||
|
|
||||||
this._inputSigners[i] = signersPubKey;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @desc verify signatures of ONE copayer, using an array of signatures for each input
|
||||||
|
*
|
||||||
|
* @param {string[]} signatures, of the same copayer, one for each input
|
||||||
|
* @return {string[]} array for signing pubkeys for each input
|
||||||
|
*/
|
||||||
|
TxProposal.prototype._addSignatureAndVerify = function(signatures) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var ret = [];
|
||||||
|
var tx = self.builder.build();
|
||||||
|
|
||||||
|
var newScriptSigs = [];
|
||||||
|
_.each(tx.ins, function(input, index) {
|
||||||
|
var scriptSig = new Script(input.s);
|
||||||
|
|
||||||
|
var info = TxProposal.infoFromRedeemScript(scriptSig);
|
||||||
|
var txSigHash = tx.hashForSignature(info.script, parseInt(index), Transaction.SIGHASH_ALL);
|
||||||
|
var keys = TxProposal.formatKeys(info.keys);
|
||||||
|
var sig = new Buffer(signatures[index], 'hex');
|
||||||
|
|
||||||
|
var hashType = sig[sig.length - 1];
|
||||||
|
if (hashType !== Transaction.SIGHASH_ALL)
|
||||||
|
throw new Error('BADSIG: Invalid signature: Bad hash type');
|
||||||
|
|
||||||
|
var sigRaw = new Buffer(sig.slice(0, sig.length - 1));
|
||||||
|
var signingPubKeyHex = self._verifyOneSignature(keys, sigRaw, txSigHash);
|
||||||
|
if (!signingPubKeyHex)
|
||||||
|
throw new Error('BADSIG: Invalid signatures: invalid for input:' + index);
|
||||||
|
|
||||||
|
// now insert it
|
||||||
|
var keysHex = _.pluck(keys, 'keyHex');
|
||||||
|
var prio = _.indexOf(keysHex, signingPubKeyHex);
|
||||||
|
preconditions.checkState(prio >= 0);
|
||||||
|
|
||||||
|
var currentKeys = self.getSignersPubKeys()[index];
|
||||||
|
|
||||||
|
if (_.indexOf(currentKeys, signingPubKeyHex) >= 0)
|
||||||
|
throw new Error('BADSIG: Already have this signature');
|
||||||
|
|
||||||
|
var currentPrios = _.map(currentKeys, function(key) {
|
||||||
|
var prio = _.indexOf(keysHex, key);
|
||||||
|
preconditions.checkState(prio >= 0);
|
||||||
|
return prio;
|
||||||
|
});
|
||||||
|
|
||||||
|
var insertAt = 0;
|
||||||
|
while ( !_.isUndefined(currentPrios[insertAt]) && prio > currentPrios[insertAt] )
|
||||||
|
insertAt++;
|
||||||
|
|
||||||
|
// Insert it! (1 is OP_0!)
|
||||||
|
scriptSig.chunks.splice(1 + insertAt, 0, sig);
|
||||||
|
scriptSig.updateBuffer();
|
||||||
|
|
||||||
|
|
||||||
|
newScriptSigs.push(scriptSig.buffer);
|
||||||
|
});
|
||||||
|
preconditions.checkState(newScriptSigs.length === tx.ins.length);
|
||||||
|
|
||||||
|
// If we reach here, all signatures are OK, let's update the TX.
|
||||||
|
_.each(tx.ins, function(input, index) {
|
||||||
|
input.s = newScriptSigs[index];
|
||||||
|
|
||||||
|
// TODO just to keep TransactionBuilder
|
||||||
|
self.builder.inputsSigned++;
|
||||||
|
});
|
||||||
|
this.resetCache();
|
||||||
|
};
|
||||||
|
|
||||||
|
TxProposal.prototype.resetCache = function() {
|
||||||
|
this.cache = {
|
||||||
|
pubkeysForScript: {},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype._sync = function() {
|
/**
|
||||||
this._check();
|
* addSignature
|
||||||
this._updateSignedBy();
|
*
|
||||||
return this;
|
* @param {string[]} signatures from *ONE* copayer, one signature for each TX input.
|
||||||
}
|
* @return {boolean} true = signatures added
|
||||||
|
*/
|
||||||
|
TxProposal.prototype.addSignature = function(copayerId, signatures) {
|
||||||
|
preconditions.checkArgument(_.isArray(signatures));
|
||||||
|
|
||||||
|
if (this.isFullySigned())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var tx = this.builder.build();
|
||||||
|
preconditions.checkArgument(signatures.length === tx.ins.length, 'Wrong number of signatures given');
|
||||||
|
|
||||||
|
this._addSignatureAndVerify(signatures);
|
||||||
|
this._setSigned(copayerId);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* getSignersPubKey
|
||||||
|
* @desc get Pubkeys of signers, for each input. this is CPU intensive
|
||||||
|
*
|
||||||
|
* @return {string[][]} array of hashes for signing pubkeys for each input
|
||||||
|
*/
|
||||||
|
TxProposal.prototype.getSignersPubKeys = function(forceUpdate) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
|
||||||
|
var signersPubKey = [];
|
||||||
|
|
||||||
|
if (!self.cache.signersPubKey || forceUpdate) {
|
||||||
|
|
||||||
|
log.debug('PERFORMANCE WARN: Verifying *all* TX signatures:', self.getId());
|
||||||
|
|
||||||
|
var tx = self.builder.build();
|
||||||
|
_.each(tx.ins, function(input, index) {
|
||||||
|
|
||||||
|
if (!self.cache.pubkeysForScript[input.s]) {
|
||||||
|
var scriptSig = new Script(input.s);
|
||||||
|
var signatureCount = scriptSig.countSignatures();
|
||||||
|
|
||||||
|
var info = TxProposal.infoFromRedeemScript(scriptSig);
|
||||||
|
var txSigHash = tx.hashForSignature(info.script, parseInt(index), Transaction.SIGHASH_ALL);
|
||||||
|
var inputSignersPubKey = self.verifySignatures(info.keys, scriptSig, txSigHash);
|
||||||
|
|
||||||
|
// Does scriptSig has strings that are not signatures?
|
||||||
|
if (inputSignersPubKey.length !== signatureCount)
|
||||||
|
throw new Error('Invalid signature');
|
||||||
|
|
||||||
|
self.cache.pubkeysForScript[input.s] = inputSignersPubKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
signersPubKey[index] = self.cache.pubkeysForScript[input.s];
|
||||||
|
});
|
||||||
|
self.cache.signersPubKey = signersPubKey;
|
||||||
|
} else {
|
||||||
|
log.debug('Using signatures verification cache')
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.cache.signersPubKey;
|
||||||
|
};
|
||||||
|
|
||||||
TxProposal.prototype.getId = function() {
|
TxProposal.prototype.getId = function() {
|
||||||
preconditions.checkState(this.builder);
|
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() {
|
TxProposal.prototype.toObj = function() {
|
||||||
var o = JSON.parse(JSON.stringify(this));
|
var o = JSON.parse(JSON.stringify(this));
|
||||||
delete o['builder'];
|
delete o['builder'];
|
||||||
|
delete o['cache'];
|
||||||
o.builderObj = this.builder.toObj();
|
o.builderObj = this.builder.toObj();
|
||||||
return o;
|
return o;
|
||||||
};
|
};
|
||||||
|
@ -216,46 +392,43 @@ TxProposal.fromObj = function(o, forceOpts) {
|
||||||
preconditions.checkArgument(o.builderObj);
|
preconditions.checkArgument(o.builderObj);
|
||||||
delete o['builder'];
|
delete o['builder'];
|
||||||
forceOpts = forceOpts || {};
|
forceOpts = forceOpts || {};
|
||||||
|
o.builderObj.opts = o.builderObj.opts || {};
|
||||||
|
|
||||||
if (forceOpts) {
|
|
||||||
o.builderObj.opts = o.builderObj.opts || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// force opts is requested.
|
// force opts is requested.
|
||||||
for (var k in forceOpts) {
|
_.each(forceOpts, function(value, key) {
|
||||||
o.builderObj.opts[k] = forceOpts[k];
|
o.builderObj.opts[key] = value;
|
||||||
}
|
});
|
||||||
// Handle undef options
|
|
||||||
|
// Handle undef fee options
|
||||||
if (_.isUndefined(forceOpts.fee) && _.isUndefined(forceOpts.feeSat)) {
|
if (_.isUndefined(forceOpts.fee) && _.isUndefined(forceOpts.feeSat)) {
|
||||||
if (o.builderObj.opts) {
|
o.builderObj.opts.fee = undefined;
|
||||||
o.builderObj.opts.fee = undefined;
|
o.builderObj.opts.feeSat = undefined;
|
||||||
o.builderObj.opts.feeSat = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
o.builder = TransactionBuilder.fromObj(o.builderObj);
|
o.builder = TransactionBuilder.fromObj(o.builderObj);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
throw new Error(e);
|
||||||
// backwards (V0) compatatibility fix.
|
return null;
|
||||||
if (!o.version) {
|
|
||||||
o.builder = new BuilderMockV0(o.builderObj);
|
|
||||||
o.readonly = 1;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return new TxProposal(o);
|
return new TxProposal(o);
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.fromUntrustedObj = function(o, forceOpts) {
|
TxProposal.fromUntrustedObj = function(o, forceOpts) {
|
||||||
return TxProposal.fromObj(TxProposal._trim(o), forceOpts);
|
var trimmed = TxProposal._trim(o);
|
||||||
|
var txp = TxProposal.fromObj(trimmed, forceOpts);
|
||||||
|
if (!txp)
|
||||||
|
throw new Error('Invalid Transaction');
|
||||||
|
|
||||||
|
txp._check();
|
||||||
|
return txp;
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype.toObjTrim = function() {
|
TxProposal.prototype.toObjTrim = function() {
|
||||||
return TxProposal._trim(this.toObj());
|
return TxProposal._trim(this.toObj());
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal._formatKeys = function(keys) {
|
TxProposal.formatKeys = function(keys) {
|
||||||
var ret = [];
|
var ret = [];
|
||||||
for (var i in keys) {
|
for (var i in keys) {
|
||||||
if (!Buffer.isBuffer(keys[i]))
|
if (!Buffer.isBuffer(keys[i]))
|
||||||
|
@ -271,31 +444,66 @@ TxProposal._formatKeys = function(keys) {
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal._verifySignatures = function(inKeys, scriptSig, txSigHash) {
|
|
||||||
|
/**
|
||||||
|
* @desc Verify a single signature, for a given hash, tested against a given list of public keys.
|
||||||
|
* @param keys
|
||||||
|
* @param sigRaw
|
||||||
|
* @param txSigHash
|
||||||
|
* @return {string?} on valid signature, return the signing public key hex representation
|
||||||
|
*/
|
||||||
|
TxProposal.prototype._verifyOneSignature = function(keys, sigRaw, txSigHash) {
|
||||||
|
preconditions.checkArgument(Buffer.isBuffer(txSigHash));
|
||||||
|
preconditions.checkArgument(Buffer.isBuffer(sigRaw));
|
||||||
|
preconditions.checkArgument(_.isArray(keys));
|
||||||
|
preconditions.checkArgument(keys[0].keyObj);
|
||||||
|
|
||||||
|
var signingKey = _.find(keys, function(key) {
|
||||||
|
var ret = false;
|
||||||
|
try {
|
||||||
|
ret = key.keyObj.verifySignatureSync(txSigHash, sigRaw);
|
||||||
|
} catch (e) {};
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
|
||||||
|
return signingKey ? signingKey.keyHex : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc verify transaction signatures
|
||||||
|
*
|
||||||
|
* @param inKeys
|
||||||
|
* @param scriptSig
|
||||||
|
* @param txSigHash
|
||||||
|
* @return {string[]} signing pubkeys, in order of apperance
|
||||||
|
*/
|
||||||
|
TxProposal.prototype.verifySignatures = function(inKeys, scriptSig, txSigHash) {
|
||||||
preconditions.checkArgument(Buffer.isBuffer(txSigHash));
|
preconditions.checkArgument(Buffer.isBuffer(txSigHash));
|
||||||
preconditions.checkArgument(inKeys);
|
preconditions.checkArgument(inKeys);
|
||||||
preconditions.checkState(Buffer.isBuffer(inKeys[0]));
|
preconditions.checkState(Buffer.isBuffer(inKeys[0]));
|
||||||
|
var self = this;
|
||||||
|
|
||||||
if (scriptSig.chunks[0] !== 0)
|
if (scriptSig.chunks[0] !== 0)
|
||||||
throw new Error('Invalid scriptSig');
|
throw new Error('Invalid scriptSig');
|
||||||
|
|
||||||
var keys = TxProposal._formatKeys(inKeys);
|
var keys = TxProposal.formatKeys(inKeys);
|
||||||
var ret = [];
|
var ret = [];
|
||||||
for (var i = 1; i <= scriptSig.countSignatures(); i++) {
|
for (var i = 1; i <= scriptSig.countSignatures(); i++) {
|
||||||
var chunk = scriptSig.chunks[i];
|
var chunk = scriptSig.chunks[i];
|
||||||
|
log.debug('\t Verifying CHUNK:', i);
|
||||||
var sigRaw = new Buffer(chunk.slice(0, chunk.length - 1));
|
var sigRaw = new Buffer(chunk.slice(0, chunk.length - 1));
|
||||||
for (var j in keys) {
|
|
||||||
var k = keys[j];
|
var signingPubKeyHex = self._verifyOneSignature(keys, sigRaw, txSigHash);
|
||||||
if (k.keyObj.verifySignatureSync(txSigHash, sigRaw)) {
|
if (!signingPubKeyHex)
|
||||||
ret.push(k.keyHex);
|
throw new Error('Found a signature that is invalid');
|
||||||
break;
|
|
||||||
}
|
ret.push(signingPubKeyHex);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal._infoFromRedeemScript = function(s) {
|
TxProposal.infoFromRedeemScript = function(s) {
|
||||||
var redeemScript = new Script(s.chunks[s.chunks.length - 1]);
|
var redeemScript = new Script(s.chunks[s.chunks.length - 1]);
|
||||||
if (!redeemScript)
|
if (!redeemScript)
|
||||||
throw new Error('Bad scriptSig (no redeemscript)');
|
throw new Error('Bad scriptSig (no redeemscript)');
|
||||||
|
@ -340,17 +548,7 @@ TxProposal.prototype.getSent = function() {
|
||||||
return this.sentTs;
|
return this.sentTs;
|
||||||
}
|
}
|
||||||
|
|
||||||
TxProposal.prototype._allSignatures = function() {
|
TxProposal.prototype.setCopayers = function(pubkeyToCopayerMap) {
|
||||||
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) {
|
|
||||||
var newCopayer = {},
|
var newCopayer = {},
|
||||||
oldCopayers = {},
|
oldCopayers = {},
|
||||||
newSignedBy = {},
|
newSignedBy = {},
|
||||||
|
@ -375,9 +573,9 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var iSig = this._inputSigners[0];
|
var iSig = this.getSignersPubKeys();
|
||||||
for (var i in iSig) {
|
for (var i in iSig) {
|
||||||
var copayerId = keyMap[iSig[i]];
|
var copayerId = pubkeyToCopayerMap[iSig[i]];
|
||||||
|
|
||||||
if (!copayerId)
|
if (!copayerId)
|
||||||
throw new Error('Found unknown signature')
|
throw new Error('Found unknown signature')
|
||||||
|
@ -390,24 +588,19 @@ 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)
|
if (Object.keys(newCopayer).length > 1)
|
||||||
throw new Error('New TX must have only 1 new signature');
|
throw new Error('New TX must have only 1 new signature');
|
||||||
|
|
||||||
// Handler creator / createdTs.
|
// Handler creator / createdTs.
|
||||||
// from senderId, and must be signed by senderId
|
// from senderId, and must be signed by senderId * DISABLED*
|
||||||
|
//
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
this.creator = Object.keys(newCopayer)[0];
|
this.creator = Object.keys(newCopayer)[0];
|
||||||
this.seenBy[this.creator] = this.createdTs = Date.now();
|
this.seenBy[this.creator] = this.createdTs = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Ended. Update this.
|
//Ended. Update this
|
||||||
for (var i in newCopayer) {
|
_.extend(this.signedBy, newCopayer);
|
||||||
this.signedBy[i] = newCopayer[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// signedBy has preference over rejectedBy
|
// signedBy has preference over rejectedBy
|
||||||
for (var i in this.signedBy) {
|
for (var i in this.signedBy) {
|
||||||
|
@ -417,21 +610,6 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) {
|
||||||
return Object.keys(newCopayer);
|
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;
|
|
||||||
this.builder.merge(incoming.builder);
|
|
||||||
this._sync();
|
|
||||||
|
|
||||||
var after = this._inputSigners[0].length;
|
|
||||||
return after !== before;
|
|
||||||
};
|
|
||||||
|
|
||||||
//This should be on bitcore / Transaction
|
//This should be on bitcore / Transaction
|
||||||
TxProposal.prototype.countSignatures = function() {
|
TxProposal.prototype.countSignatures = function() {
|
||||||
var tx = this.builder.build();
|
var tx = this.builder.build();
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var BuilderMockV0 = require('./BuilderMockV0');;
|
var preconditions = require('preconditions').singleton();
|
||||||
|
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var util = bitcore.util;
|
var util = bitcore.util;
|
||||||
var Transaction = bitcore.Transaction;
|
var Transaction = bitcore.Transaction;
|
||||||
var BuilderMockV0 = require('./BuilderMockV0');;
|
|
||||||
var TxProposal = require('./TxProposal');;
|
|
||||||
var Script = bitcore.Script;
|
var Script = bitcore.Script;
|
||||||
var Key = bitcore.Key;
|
var Key = bitcore.Key;
|
||||||
var buffertools = bitcore.buffertools;
|
var buffertools = bitcore.buffertools;
|
||||||
var preconditions = require('preconditions').instance();
|
|
||||||
var log = require('../log');
|
var log = require('../log');
|
||||||
|
var TxProposal = require('./TxProposal');;
|
||||||
|
|
||||||
function TxProposals(opts) {
|
function TxProposals(opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
@ -51,9 +51,9 @@ TxProposals.prototype.getNtxidsSince = function(sinceTs) {
|
||||||
preconditions.checkArgument(sinceTs);
|
preconditions.checkArgument(sinceTs);
|
||||||
var ret = [];
|
var ret = [];
|
||||||
|
|
||||||
for(var ii in this.txps){
|
for (var ii in this.txps) {
|
||||||
var txp = this.txps[ii];
|
var txp = this.txps[ii];
|
||||||
if (txp.createdTs >= sinceTs)
|
if (txp.createdTs >= sinceTs)
|
||||||
ret.push(ii);
|
ret.push(ii);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -96,101 +96,41 @@ 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
|
// Add a LOCALLY CREATED (trusted) tx proposal
|
||||||
TxProposals.prototype.add = function(txp) {
|
TxProposals.prototype.add = function(txp) {
|
||||||
txp._sync();
|
|
||||||
var ntxid = txp.getId();
|
var ntxid = txp.getId();
|
||||||
this.txps[ntxid] = txp;
|
this.txps[ntxid] = txp;
|
||||||
return ntxid;
|
return ntxid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TxProposals.prototype.exist = function(ntxid) {
|
||||||
|
return this.txps[ntxid] ? true : false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
TxProposals.prototype.get = function(ntxid) {
|
TxProposals.prototype.get = function(ntxid) {
|
||||||
var ret = this.txps[ntxid];
|
var ret = this.txps[ntxid];
|
||||||
if (!ret)
|
if (!ret)
|
||||||
throw new Error('Unknown TXP: '+ntxid);
|
throw new Error('Unknown TXP: ' + ntxid);
|
||||||
|
|
||||||
return ret;
|
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
|
//returns the unspent txid-vout used in PENDING Txs
|
||||||
TxProposals.prototype.getUsedUnspent = function(maxRejectCount) {
|
TxProposals.prototype.getUsedUnspent = function(maxRejectCount) {
|
||||||
var ret = {};
|
var ret = {};
|
||||||
for (var i in this.txps) {
|
var self = this;
|
||||||
if (!this.txps[i].isPending(maxRejectCount))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var u = this.txps[i].builder.getSelectedUnspent();
|
_.each(this.txps, function(txp) {
|
||||||
var p = this.getTxProposal(i);
|
if (!txp.isPending(maxRejectCount))
|
||||||
|
return
|
||||||
|
|
||||||
for (var j in u) {
|
_.each(txp.builder.getSelectedUnspent(), function(u) {
|
||||||
ret[u[j].txid + ',' + u[j].vout] = 1;
|
ret[u.txid + ',' + u.vout] = 1;
|
||||||
}
|
});
|
||||||
}
|
});
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -122,6 +122,7 @@ inherits(Wallet, events.EventEmitter);
|
||||||
Wallet.TX_BROADCASTED = 'txBroadcasted';
|
Wallet.TX_BROADCASTED = 'txBroadcasted';
|
||||||
Wallet.TX_PROPOSAL_SENT = 'txProposalSent';
|
Wallet.TX_PROPOSAL_SENT = 'txProposalSent';
|
||||||
Wallet.TX_SIGNED = 'txSigned';
|
Wallet.TX_SIGNED = 'txSigned';
|
||||||
|
Wallet.TX_SIGNED_AND_BROADCASTED = 'txSignedAndBroadcasted';
|
||||||
|
|
||||||
Wallet.prototype.emitAndKeepAlive = function(args) {
|
Wallet.prototype.emitAndKeepAlive = function(args) {
|
||||||
var args = Array.prototype.slice.call(arguments);
|
var args = Array.prototype.slice.call(arguments);
|
||||||
|
@ -131,7 +132,7 @@ Wallet.prototype.emitAndKeepAlive = function(args) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc Fixed & Forced TransactionBuilder options, for genereration transactions.
|
* @desc Fixed & Forced TransactionBuilder options, for genererating transactions.
|
||||||
*
|
*
|
||||||
* @static
|
* @static
|
||||||
* @property lockTime null
|
* @property lockTime null
|
||||||
|
@ -338,76 +339,37 @@ Wallet.prototype._onPublicKeyRing = function(senderId, data) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc
|
* @desc
|
||||||
* Demultiplexes calls to update TxProposal updates
|
* Retrieves a keymap from a transaction proposal set extracts a maps from
|
||||||
*
|
|
||||||
* @param {string} senderId - the copayer that sent this event
|
|
||||||
* @param {Object} m - the data received
|
|
||||||
* @emits txProposalEvent
|
|
||||||
*/
|
|
||||||
Wallet.prototype._processProposalEvents = function(senderId, m) {
|
|
||||||
var ev;
|
|
||||||
if (m) {
|
|
||||||
if (m.new) {
|
|
||||||
ev = {
|
|
||||||
type: 'new',
|
|
||||||
cId: senderId
|
|
||||||
}
|
|
||||||
} else if (m.newCopayer && m.newCopayer.length) {
|
|
||||||
ev = {
|
|
||||||
type: 'signed',
|
|
||||||
cId: m.newCopayer[0]
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
log.error('unknown tx proposal event:', m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ev)
|
|
||||||
this.emitAndKeepAlive('txProposalEvent', ev);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/* OTDO
|
|
||||||
events.push({
|
|
||||||
type: 'signed',
|
|
||||||
cId: k,
|
|
||||||
txId: ntxid
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @desc
|
|
||||||
* Retrieves a keymap from from a transaction proposal set extracts a maps from
|
|
||||||
* public key to cosignerId for each signed input of the transaction proposal.
|
* public key to cosignerId for each signed input of the transaction proposal.
|
||||||
*
|
*
|
||||||
* @param {TxProposals} txp - the transaction proposals
|
* @param {TxProposals} txp - the transaction proposals
|
||||||
* @return {Object}
|
* @return {Object} [pubkey] -> copayerId
|
||||||
*/
|
*/
|
||||||
Wallet.prototype._getKeyMap = function(txp) {
|
Wallet.prototype._getPubkeyToCopayerMap = function(txp) {
|
||||||
preconditions.checkArgument(txp);
|
preconditions.checkArgument(txp);
|
||||||
var inSig0, keyMapAll = {};
|
var inSig0, keyMapAll = {},
|
||||||
|
self = this;
|
||||||
|
|
||||||
for (var i in txp._inputSigners) {
|
var signersPubKeys = txp.getSignersPubKeys();
|
||||||
var keyMap = this.publicKeyRing.copayersForPubkeys(txp._inputSigners[i], txp.inputChainPaths);
|
_.each(signersPubKeys, 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');
|
throw new Error('Signature does not match known copayers');
|
||||||
|
|
||||||
for (var j in keyMap) {
|
_.extend(keyMapAll, keyMap);
|
||||||
keyMapAll[j] = keyMap[j];
|
|
||||||
}
|
|
||||||
|
|
||||||
// From here -> only to check that all inputs have the same sigs
|
// From here -> only to check that all inputs have the same sigs
|
||||||
var inSigArr = [];
|
var inSigArr = _.values(keyMap);
|
||||||
_.each(keyMap, function(value, key) {
|
|
||||||
inSigArr.push(value);
|
|
||||||
});
|
|
||||||
var inSig = JSON.stringify(inSigArr.sort());
|
var inSig = JSON.stringify(inSigArr.sort());
|
||||||
if (i === '0') {
|
|
||||||
|
if (!inSig0) {
|
||||||
inSig0 = inSig;
|
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;
|
return keyMapAll;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -442,11 +404,10 @@ Wallet.prototype._checkIfTxIsSent = function(ntxid, cb) {
|
||||||
* and send `seen` messages to peers if aplicable.
|
* and send `seen` messages to peers if aplicable.
|
||||||
* @param ntxid
|
* @param ntxid
|
||||||
*/
|
*/
|
||||||
Wallet.prototype._setTxProposalSeen = function(ntxid) {
|
Wallet.prototype._setTxProposalSeen = function(txp) {
|
||||||
var txp = this.txProposals.get(ntxid);
|
|
||||||
if (!txp.getSeen(this.getMyCopayerId())) {
|
if (!txp.getSeen(this.getMyCopayerId())) {
|
||||||
txp.setSeen(this.getMyCopayerId());
|
txp.setSeen(this.getMyCopayerId());
|
||||||
this.sendSeen(ntxid);
|
this.sendSeen(txp.getId());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -458,17 +419,13 @@ Wallet.prototype._setTxProposalSeen = function(ntxid) {
|
||||||
* @param ntxid
|
* @param ntxid
|
||||||
* @param {transactionCallback} cb
|
* @param {transactionCallback} cb
|
||||||
*/
|
*/
|
||||||
Wallet.prototype._updateTxProposalSent = function(ntxid, cb) {
|
Wallet.prototype._updateTxProposalSent = function(txp, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var txp = this.txProposals.get(ntxid);
|
this._checkIfTxIsSent(txp.getId(), function(err, txid) {
|
||||||
|
|
||||||
this._checkIfTxIsSent(ntxid, function(err, txid) {
|
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
if (txid) {
|
if (txid) {
|
||||||
if (!txp.getSent()) {
|
txp.setSent(txid);
|
||||||
txp.setSent(txid);
|
|
||||||
}
|
|
||||||
self.emitAndKeepAlive('txProposalsUpdated');
|
self.emitAndKeepAlive('txProposalsUpdated');
|
||||||
}
|
}
|
||||||
if (cb)
|
if (cb)
|
||||||
|
@ -486,13 +443,10 @@ Wallet.prototype._updateTxProposalSent = function(ntxid, cb) {
|
||||||
* @param mergeInfo Proposals merge information, as returned by TxProposals.merge
|
* @param mergeInfo Proposals merge information, as returned by TxProposals.merge
|
||||||
* @return {fetchPaymentRequestCallback}
|
* @return {fetchPaymentRequestCallback}
|
||||||
*/
|
*/
|
||||||
Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) {
|
Wallet.prototype._processTxProposalPayPro = function(txp, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var txp = mergeInfo.txp;
|
|
||||||
var isNew = mergeInfo.new;
|
|
||||||
var ntxid = mergeInfo.ntxid;
|
|
||||||
|
|
||||||
if (!isNew || !txp.paymentProtocolURL)
|
if (!txp.paymentProtocolURL)
|
||||||
return cb();
|
return cb();
|
||||||
|
|
||||||
log.info('Received a Payment Protocol TX Proposal');
|
log.info('Received a Payment Protocol TX Proposal');
|
||||||
|
@ -513,35 +467,36 @@ Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* _processIncomingTxProposal
|
* @desc Process an NEW incoming transaction proposal. Runs safety and sanity checks on it.
|
||||||
*
|
|
||||||
* @desc Process an incoming transaction proposal. Runs safety and sanity checks on it.
|
|
||||||
*
|
*
|
||||||
* @param mergeInfo Proposals merge information, as returned by TxProposals.merge
|
* @param mergeInfo Proposals merge information, as returned by TxProposals.merge
|
||||||
* @return {errCallback}
|
* @return {errCallback}
|
||||||
*/
|
*/
|
||||||
Wallet.prototype._processIncomingTxProposal = function(mergeInfo, cb) {
|
Wallet.prototype._processIncomingNewTxProposal = function(txp, cb) {
|
||||||
if (!mergeInfo) return cb();
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self._processTxProposalPayPro(mergeInfo, function(err) {
|
var ntxid = txp.getId();
|
||||||
|
self._processTxProposalPayPro(txp, function(err) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
self._setTxProposalSeen(mergeInfo.ntxid);
|
self._setTxProposalSeen(txp);
|
||||||
|
|
||||||
var tx = mergeInfo.txp.builder.build();
|
var tx = txp.builder.build();
|
||||||
if (tx.isComplete())
|
if (tx.isComplete() && !txp.getSent())
|
||||||
self._updateTxProposalSent(mergeInfo.ntxid);
|
self._updateTxProposalSent(txp);
|
||||||
else {
|
|
||||||
self.emitAndKeepAlive('txProposalsUpdated');
|
|
||||||
}
|
|
||||||
return cb();
|
return cb();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* only for stubbing */
|
||||||
|
Wallet.prototype._txProposalFromUntrustedObj = function(data, opts) {
|
||||||
|
return TxProposal.fromUntrustedObj(data, opts);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc
|
* @desc
|
||||||
* Handles a 'TXPROPOSAL' network message
|
* Handles a NEW 'TXPROPOSAL' network message
|
||||||
*
|
*
|
||||||
* @param {string} senderId - the id of the sender
|
* @param {string} senderId - the id of the sender
|
||||||
* @param {Object} data - the data received
|
* @param {Object} data - the data received
|
||||||
|
@ -549,37 +504,56 @@ Wallet.prototype._processIncomingTxProposal = function(mergeInfo, cb) {
|
||||||
* @emits txProposalsUpdated
|
* @emits txProposalsUpdated
|
||||||
*/
|
*/
|
||||||
Wallet.prototype._onTxProposal = function(senderId, data) {
|
Wallet.prototype._onTxProposal = function(senderId, data) {
|
||||||
|
preconditions.checkArgument(data.txProposal);
|
||||||
var self = this;
|
var self = this;
|
||||||
var m;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
m = self.txProposals.merge(data.txProposal, Wallet.builderOpts);
|
var incomingTx = self._txProposalFromUntrustedObj(data.txProposal, Wallet.builderOpts);
|
||||||
var keyMap = self._getKeyMap(m.txp);
|
var incomingNtxid = incomingTx.getId();
|
||||||
m.newCopayer = m.txp.setCopayers(senderId, keyMap);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error('Corrupt TX proposal received from:', senderId, e.toString());
|
log.warn(e);
|
||||||
if (m && m.ntxid)
|
return;
|
||||||
self.txProposals.deleteOne(m.ntxid);
|
|
||||||
m = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m) {
|
if (this.txProposals.exist(incomingNtxid)) {
|
||||||
|
log.warn('Ignoring existing tx Proposal:' + incomingNtxid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self._processIncomingTxProposal(m, function(err) {
|
self._processIncomingNewTxProposal(incomingTx, function(err) {
|
||||||
|
if (err) {
|
||||||
|
log.warn('Corrupt TX proposal received from:', senderId, err.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
var pubkeyToCopayerMap = self._getPubkeyToCopayerMap(incomingTx);
|
||||||
|
incomingTx.setCopayers(pubkeyToCopayerMap);
|
||||||
|
|
||||||
|
self.txProposals.add(incomingTx);
|
||||||
|
self.emitAndKeepAlive('txProposalEvent', {
|
||||||
|
type: 'new',
|
||||||
|
cId: senderId,
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Wallet.prototype._onSignature = function(senderId, data) {
|
||||||
|
var self = this;
|
||||||
|
try {
|
||||||
|
var localTx = this.txProposals.get(data.ntxid);
|
||||||
|
} catch (e) {
|
||||||
|
log.info('Ignoring signature for unknown tx Proposal:' + data.ntxid);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
localTx.addSignature(senderId, data.signatures);
|
||||||
|
self.issueTxIfComplete(data.ntxid, function(err, txid) {
|
||||||
|
self.emitAndKeepAlive('txProposalEvent', {
|
||||||
|
type: txid ? 'signedAndBroadcasted' : 'signed',
|
||||||
|
cId: senderId,
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -753,6 +727,9 @@ Wallet.prototype._onData = function(senderId, data, ts) {
|
||||||
case 'txProposal':
|
case 'txProposal':
|
||||||
this._onTxProposal(senderId, data);
|
this._onTxProposal(senderId, data);
|
||||||
break;
|
break;
|
||||||
|
case 'signature':
|
||||||
|
this._onSignature(senderId, data);
|
||||||
|
break;
|
||||||
case 'indexes':
|
case 'indexes':
|
||||||
this._onIndexes(senderId, data);
|
this._onIndexes(senderId, data);
|
||||||
break;
|
break;
|
||||||
|
@ -1278,6 +1255,27 @@ 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);
|
||||||
|
var signatures = txp.getMySignatures();
|
||||||
|
preconditions.checkState(signatures && signatures.length);
|
||||||
|
|
||||||
|
this._sendToPeers(null, {
|
||||||
|
type: 'signature',
|
||||||
|
ntxid: ntxid,
|
||||||
|
signatures: signatures,
|
||||||
|
walletId: this.id,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc Notify other peers that a wallet has been backed up and it's ready to be used
|
* @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
|
* @param {string[]} [recipients] - the pubkeys of the recipients
|
||||||
|
@ -1396,45 +1394,28 @@ Wallet.prototype.generateAddress = function(isChange, cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc Retrieve all the Transaction proposals (see {@link TxProposals})
|
* TODO: get this out of here
|
||||||
* @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;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @desc get list of actions (see {@link getPendingTxProposals})
|
* @desc get list of actions (see {@link getPendingTxProposals})
|
||||||
*/
|
*/
|
||||||
Wallet.prototype._getActionList = function(actions) {
|
Wallet.prototype._getActionList = function(txp) {
|
||||||
if (!actions) return;
|
preconditions.checkArgument(txp);
|
||||||
var peers = Object.keys(actions).map(function(i) {
|
|
||||||
return {
|
|
||||||
cId: i,
|
|
||||||
actions: actions[i]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return peers.sort(function(a, b) {
|
var self = this;
|
||||||
return !!b.actions.create - !!a.actions.create;
|
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 +1427,22 @@ Wallet.prototype.getPendingTxProposals = function() {
|
||||||
var ret = [];
|
var ret = [];
|
||||||
ret.txs = [];
|
ret.txs = [];
|
||||||
var pendingForUs = 0;
|
var pendingForUs = 0;
|
||||||
var txps = this.getTxProposals();
|
var txps = this.txProposals.txps;
|
||||||
|
var maxRejectCount = this.maxRejectCount();
|
||||||
var satToUnit = 1 / this.settings.unitToSatoshi;
|
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++;
|
pendingForUs++;
|
||||||
var addresses = {};
|
var addresses = {};
|
||||||
var outs = JSON.parse(txp.builder.vanilla.outs);
|
var outs = JSON.parse(txp.builder.vanilla.outs);
|
||||||
outs.forEach(function(o) {
|
outs.forEach(function(o) {
|
||||||
if (!self.publicKeyRing.addressToPath[o.Straddress]) {
|
if (!self.addressIsOwn(o.address)) {
|
||||||
if (!addresses[o.address]) addresses[o.address] = 0;
|
if (!addresses[o.address]) addresses[o.address] = 0;
|
||||||
addresses[o.address] += (o.amountSatStr || Math.round(o.amount * bitcore.util.COIN));
|
addresses[o.address] += (o.amountSatStr || Math.round(o.amount * bitcore.util.COIN));
|
||||||
};
|
};
|
||||||
|
@ -1469,7 +1457,7 @@ Wallet.prototype.getPendingTxProposals = function() {
|
||||||
// extra fields
|
// extra fields
|
||||||
txp.fee = txp.builder.feeSat * satToUnit;
|
txp.fee = txp.builder.feeSat * satToUnit;
|
||||||
txp.missingSignatures = txp.builder.build().countInputMissingSignatures(0);
|
txp.missingSignatures = txp.builder.build().countInputMissingSignatures(0);
|
||||||
txp.actionList = self._getActionList(txp.peerActions);
|
txp.actionList = self._getActionList(txp);
|
||||||
ret.txs.push(txp);
|
ret.txs.push(txp);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1537,6 +1525,16 @@ Wallet.prototype.sign = function(ntxid) {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Wallet.prototype.issueTxIfComplete = function(ntxid, cb) {
|
||||||
|
var txp = this.txProposals.get(ntxid);
|
||||||
|
var tx = txp.builder.build();
|
||||||
|
if (tx.isComplete()) {
|
||||||
|
this.issueTx(ntxid, cb);
|
||||||
|
} else {
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -1551,13 +1549,13 @@ Wallet.prototype.sign = function(ntxid) {
|
||||||
*/
|
*/
|
||||||
Wallet.prototype.signAndSend = function(ntxid, cb) {
|
Wallet.prototype.signAndSend = function(ntxid, cb) {
|
||||||
if (this.sign(ntxid)) {
|
if (this.sign(ntxid)) {
|
||||||
var txp = this.txProposals.get(ntxid);
|
this.sendSignature(ntxid);
|
||||||
if (txp.isFullySigned()) {
|
this.issueTxIfComplete(ntxid, function(err, txid, status) {
|
||||||
return this.broadcastTx(ntxid, cb);
|
if (!txid)
|
||||||
} else {
|
return cb(null, ntxid, Wallet.TX_SIGNED);
|
||||||
this.sendTxProposal(ntxid);
|
else
|
||||||
return cb(null, ntxid, Wallet.TX_SIGNED);
|
return cb(null, ntxid, Wallet.TX_SIGNED_AND_BROADCASTED);
|
||||||
}
|
});
|
||||||
} else {
|
} else {
|
||||||
return cb(new Error('Could not sign the proposal'));
|
return cb(new Error('Could not sign the proposal'));
|
||||||
}
|
}
|
||||||
|
@ -1572,13 +1570,12 @@ Wallet.prototype.signAndSend = function(ntxid, cb) {
|
||||||
* @param cb
|
* @param cb
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
Wallet.prototype._doBroadcastTx = function(ntxid, cb) {
|
Wallet.prototype.broadcastToBitcoinNetwork = function(ntxid, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var txp = this.txProposals.get(ntxid);
|
var txp = this.txProposals.get(ntxid);
|
||||||
var tx = txp.builder.build();
|
|
||||||
|
|
||||||
if (!tx.isComplete())
|
var tx = txp.builder.build();
|
||||||
throw new Error('Tx is not complete. Can not broadcast');
|
preconditions.checkState(tx.isComplete(), 'tx is not complete');
|
||||||
|
|
||||||
var txHex = tx.serialize().toString('hex');
|
var txHex = tx.serialize().toString('hex');
|
||||||
|
|
||||||
|
@ -1595,7 +1592,7 @@ Wallet.prototype._doBroadcastTx = function(ntxid, cb) {
|
||||||
return cb(err, txid);
|
return cb(err, txid);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.info('Wallet:' + self.getName() + ' broadcasted a TX. BITCOIND txid:', txid);
|
log.info('Wallet:' + self.getName() + ' broadcasted a TX! TXID:', txid);
|
||||||
return cb(null, txid);
|
return cb(null, txid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1610,10 +1607,10 @@ Wallet.prototype._doBroadcastTx = function(ntxid, cb) {
|
||||||
* @param {string} txid - the transaction id on the blockchain
|
* @param {string} txid - the transaction id on the blockchain
|
||||||
* @param {signCallback} cb
|
* @param {signCallback} cb
|
||||||
*/
|
*/
|
||||||
Wallet.prototype.broadcastTx = function(ntxid, cb) {
|
Wallet.prototype.issueTx = function(ntxid, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self._doBroadcastTx(ntxid, function(err, txid) {
|
self.broadcastToBitcoinNetwork(ntxid, function(err, txid) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
preconditions.checkState(txid);
|
preconditions.checkState(txid);
|
||||||
|
|
||||||
|
@ -1630,8 +1627,6 @@ Wallet.prototype.broadcastTx = function(ntxid, cb) {
|
||||||
self.onPayProPaymentAck(ntxid, data);
|
self.onPayProPaymentAck(ntxid, data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sendTxProposal(ntxid);
|
|
||||||
self.emitAndKeepAlive('txProposalsUpdated');
|
self.emitAndKeepAlive('txProposalsUpdated');
|
||||||
return cb(null, txid, Wallet.TX_BROADCASTED);
|
return cb(null, txid, Wallet.TX_BROADCASTED);
|
||||||
});
|
});
|
||||||
|
@ -2133,58 +2128,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
|
* spend
|
||||||
*
|
*
|
||||||
|
@ -2252,11 +2195,10 @@ Wallet.prototype.spend = function(opts, cb) {
|
||||||
|
|
||||||
log.debug('TXP Added: ', ntxid);
|
log.debug('TXP Added: ', ntxid);
|
||||||
|
|
||||||
console.log('[Wallet.js.2233]'); //TODO
|
|
||||||
self.sendIndexes();
|
self.sendIndexes();
|
||||||
// Needs only one signature? Broadcast it!
|
// Needs only one signature? Broadcast it!
|
||||||
if (!self.requiresMultipleSignatures()) {
|
if (!self.requiresMultipleSignatures()) {
|
||||||
self.broadcastTx(ntxid, cb);
|
self.issueTx(ntxid, cb);
|
||||||
} else {
|
} else {
|
||||||
self.sendTxProposal(ntxid);
|
self.sendTxProposal(ntxid);
|
||||||
self.emitAndKeepAlive('txProposalsUpdated');
|
self.emitAndKeepAlive('txProposalsUpdated');
|
||||||
|
@ -2361,6 +2303,7 @@ Wallet.prototype._createTxProposal = function(toAddress, amountSat, comment, utx
|
||||||
signWith: keys,
|
signWith: keys,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('[Wallet.js.2303]'); //TODO
|
||||||
return txp;
|
return txp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2590,7 +2533,7 @@ Wallet.prototype.getTransactionHistory = function(opts, cb) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|
||||||
var addresses = self.getAddressesInfo();
|
var addresses = self.getAddressesInfo();
|
||||||
var proposals = self.getTxProposals();
|
var proposals = self.txProposals.txps;
|
||||||
var satToUnit = 1 / self.settings.unitToSatoshi;
|
var satToUnit = 1 / self.settings.unitToSatoshi;
|
||||||
|
|
||||||
var indexedProposals = _.indexBy(proposals, 'sentTxid');
|
var indexedProposals = _.indexBy(proposals, 'sentTxid');
|
||||||
|
@ -2689,18 +2632,11 @@ Wallet.prototype.getTransactionHistory = function(opts, cb) {
|
||||||
if (proposal) {
|
if (proposal) {
|
||||||
// TODO refactor
|
// TODO refactor
|
||||||
tx.comment = proposal.comment;
|
tx.comment = proposal.comment;
|
||||||
tx.sentTs = proposal.sentTs;
|
|
||||||
tx.merchant = proposal.merchant;
|
tx.merchant = proposal.merchant;
|
||||||
tx.peerActions = proposal.peerActions;
|
tx.peerActions = proposal.peerActions;
|
||||||
tx.finallyRejected = proposal.finallyRejected;
|
|
||||||
tx.merchant = proposal.merchant;
|
tx.merchant = proposal.merchant;
|
||||||
tx.paymentAckMemo = proposal.paymentAckMemo;
|
tx.paymentAckMemo = proposal.paymentAckMemo;
|
||||||
tx.peerActions = proposal.peerActions;
|
tx.actionList = self._getActionList(proposal);
|
||||||
tx.finallyRejected = proposal.finallyRejected;
|
|
||||||
|
|
||||||
if (tx.peerActions) {
|
|
||||||
tx.actionList = self._getActionList(tx.peerActions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -154,6 +154,10 @@ angular.module('copayApp.services')
|
||||||
notification.info('[' + name + '] Transaction Signed',
|
notification.info('[' + name + '] Transaction Signed',
|
||||||
$filter('translate')('A transaction was signed by') + ' ' + user);
|
$filter('translate')('A transaction was signed by') + ' ' + user);
|
||||||
break;
|
break;
|
||||||
|
case 'signedAndBroadcasted':
|
||||||
|
notification.info('[' + name + '] Transaction Approved',
|
||||||
|
$filter('translate')('A transaction was signed and broadcasted by') + ' ' + user);
|
||||||
|
break;
|
||||||
case 'rejected':
|
case 'rejected':
|
||||||
notification.info('[' + name + '] Transaction Rejected',
|
notification.info('[' + name + '] Transaction Rejected',
|
||||||
$filter('translate')('A transaction was rejected by') + ' ' + user);
|
$filter('translate')('A transaction was rejected by') + ' ' + user);
|
||||||
|
|
|
@ -129,7 +129,8 @@ describe('Identity model', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
should.exist(iden);
|
should.exist(iden);
|
||||||
should.exist(iden.wallets);
|
should.exist(iden.wallets);
|
||||||
Identity.prototype.store.calledOnce.should.be.true;
|
iden.store.calledOnce.should.be.true;
|
||||||
|
iden.store.restore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -169,10 +170,16 @@ describe('Identity model', function() {
|
||||||
args = createIdentity();
|
args = createIdentity();
|
||||||
args.params.noWallets = true;
|
args.params.noWallets = true;
|
||||||
var old = Identity.prototype.createWallet;
|
var old = Identity.prototype.createWallet;
|
||||||
|
sinon.stub(Identity.prototype, 'store').yields(null);
|
||||||
Identity.create(args.params, function(err, res) {
|
Identity.create(args.params, function(err, res) {
|
||||||
iden = res;
|
iden = res;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
iden.store.restore();
|
||||||
|
});
|
||||||
|
|
||||||
it('should be able to create wallets with given pk', function(done) {
|
it('should be able to create wallets with given pk', function(done) {
|
||||||
var priv = 'tprv8ZgxMBicQKsPdEqHcA7RjJTayxA3gSSqeRTttS1JjVbgmNDZdSk9EHZK5pc52GY5xFmwcakmUeKWUDzGoMLGAhrfr5b3MovMUZUTPqisL2m';
|
var priv = 'tprv8ZgxMBicQKsPdEqHcA7RjJTayxA3gSSqeRTttS1JjVbgmNDZdSk9EHZK5pc52GY5xFmwcakmUeKWUDzGoMLGAhrfr5b3MovMUZUTPqisL2m';
|
||||||
args.storage.setItem = sinon.stub();
|
args.storage.setItem = sinon.stub();
|
||||||
|
@ -220,7 +227,7 @@ describe('Identity model', function() {
|
||||||
args.storage.getItem.onFirstCall().callsArgWith(1, null, '{"wallet": "fakeData"}');
|
args.storage.getItem.onFirstCall().callsArgWith(1, null, '{"wallet": "fakeData"}');
|
||||||
var backup = Wallet.fromUntrustedObj;
|
var backup = Wallet.fromUntrustedObj;
|
||||||
args.params.noWallets = true;
|
args.params.noWallets = true;
|
||||||
|
sinon.stub(Identity.prototype, 'store').yields(null);
|
||||||
sinon.stub().returns(args.wallet);
|
sinon.stub().returns(args.wallet);
|
||||||
|
|
||||||
var opts = {
|
var opts = {
|
||||||
|
@ -232,6 +239,7 @@ describe('Identity model', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
opts.importWallet.calledOnce.should.equal(true);
|
opts.importWallet.calledOnce.should.equal(true);
|
||||||
should.exist(wallet);
|
should.exist(wallet);
|
||||||
|
iden.store.restore();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -246,6 +254,7 @@ describe('Identity model', function() {
|
||||||
var backup = Wallet.fromUntrustedObj;
|
var backup = Wallet.fromUntrustedObj;
|
||||||
args.params.noWallets = true;
|
args.params.noWallets = true;
|
||||||
sinon.stub().returns(args.wallet);
|
sinon.stub().returns(args.wallet);
|
||||||
|
sinon.stub(Identity.prototype, 'store').yields(null);
|
||||||
|
|
||||||
var fakeCrypto = {
|
var fakeCrypto = {
|
||||||
kdf: sinon.stub().returns('passphrase'),
|
kdf: sinon.stub().returns('passphrase'),
|
||||||
|
@ -263,6 +272,7 @@ describe('Identity model', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
fakeCrypto.decrypt.getCall(0).args[0].should.equal('password');
|
fakeCrypto.decrypt.getCall(0).args[0].should.equal('password');
|
||||||
fakeCrypto.decrypt.getCall(0).args[1].should.equal(123);
|
fakeCrypto.decrypt.getCall(0).args[1].should.equal(123);
|
||||||
|
iden.store.restore();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -311,12 +321,14 @@ describe('Identity model', function() {
|
||||||
args = createIdentity();
|
args = createIdentity();
|
||||||
args.params.Async = net = sinon.stub();
|
args.params.Async = net = sinon.stub();
|
||||||
|
|
||||||
|
sinon.stub(Identity.prototype, 'store').yields(null);
|
||||||
net.cleanUp = sinon.spy();
|
net.cleanUp = sinon.spy();
|
||||||
net.on = sinon.stub();
|
net.on = sinon.stub();
|
||||||
net.start = sinon.spy();
|
net.start = sinon.spy();
|
||||||
var old = Identity.prototype.createWallet;
|
var old = Identity.prototype.createWallet;
|
||||||
Identity.create(args.params, function(err, res) {
|
Identity.create(args.params, function(err, res) {
|
||||||
iden = res;
|
iden = res;
|
||||||
|
iden.store.restore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,19 +8,89 @@ var Script = bitcore.Script;
|
||||||
var TransactionBuilder = bitcore.TransactionBuilder;
|
var TransactionBuilder = bitcore.TransactionBuilder;
|
||||||
var util = bitcore.util;
|
var util = bitcore.util;
|
||||||
var networks = bitcore.networks;
|
var networks = bitcore.networks;
|
||||||
var FakeBuilder = requireMock('FakeBuilder');
|
|
||||||
var TxProposal = copay.TxProposal;
|
|
||||||
var Buffer = bitcore.Buffer;
|
var Buffer = bitcore.Buffer;
|
||||||
|
|
||||||
var someKeys = ["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5", "022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"];
|
var TxProposal = copay.TxProposal;
|
||||||
|
|
||||||
|
|
||||||
describe('TxProposal', function() {
|
describe('TxProposal', function() {
|
||||||
|
|
||||||
function dummyProposal() {
|
|
||||||
|
// These 2 signed the scripts below
|
||||||
|
var PUBKEYS = ['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3'];
|
||||||
|
|
||||||
|
|
||||||
|
// Signatures of the scripts below
|
||||||
|
var SIG0 = '304502200708a381dde585ef7fdfaeaeb5da9b451d3e22b01eac8a5e3d03b959e24a7478022100c90e76e423523a54a9e9c43858337ebcef1a539a7fc685c2698dd8648fcf1b9101';
|
||||||
|
var SIG1 = '3044022030a77c9613d6ee010717c1abc494668d877e3fa0ae4c520f65cc3b308754c98c02205219d387bcb291bd44805b9468439e4168b02a6a180cdbcc24d84d71d696c1ae01';
|
||||||
|
|
||||||
|
/* decoded redeemscript
|
||||||
|
*
|
||||||
|
"asm" : "3 03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d 0380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127 0392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed03 03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3 03e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e4 5 OP_CHECKMULTISIG",
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 1,2 signatures 3-5!
|
||||||
|
var SCRIPTSIG = _.map([
|
||||||
|
'0048304502207d8e832bd576c93300e53ab6cbd68641961bec60690c358fd42d8e42b7d7d687022100a1daa89923efdb4c9b615d065058d9e1644f67000694a7d0806759afa7bef19b014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae',
|
||||||
|
'0048304502200708a381dde585ef7fdfaeaeb5da9b451d3e22b01eac8a5e3d03b959e24a7478022100c90e76e423523a54a9e9c43858337ebcef1a539a7fc685c2698dd8648fcf1b9101473044022030a77c9613d6ee010717c1abc494668d877e3fa0ae4c520f65cc3b308754c98c02205219d387bcb291bd44805b9468439e4168b02a6a180cdbcc24d84d71d696c1ae014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae'
|
||||||
|
], function(hex) {
|
||||||
|
return new Buffer(hex, 'hex');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var someKeys = ["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5", "022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"];
|
||||||
|
|
||||||
|
|
||||||
|
function dummyBuilder(opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
|
||||||
|
var index = opts.nsig ? opts.nsig - 1 : 1;
|
||||||
|
var script = SCRIPTSIG[index];
|
||||||
|
|
||||||
|
var aIn = {
|
||||||
|
s: script
|
||||||
|
};
|
||||||
|
|
||||||
|
var tx = {};
|
||||||
|
tx.ins = opts.noins ? [] : [opts.nosigs ? {} : aIn];
|
||||||
|
|
||||||
|
tx.serialize = sinon.stub().returns(new Buffer('1234', 'hex'));
|
||||||
|
tx.getSize = sinon.stub().returns(1);
|
||||||
|
tx.getHashType = sinon.stub().returns(opts.hashtype || 1);
|
||||||
|
tx.getNormalizedHash = sinon.stub().returns('123456');
|
||||||
|
tx.hashForSignature = sinon.stub().returns(
|
||||||
|
new Buffer('31103626e162f1cbfab6b95b08c9f6e78aae128523261cb37f8dfd4783cb09a7', 'hex'));
|
||||||
|
|
||||||
|
|
||||||
|
var builder = {};
|
||||||
|
|
||||||
|
builder.opts = opts.opts || {};
|
||||||
|
builder.build = sinon.stub().returns(tx)
|
||||||
|
builder.toObj = sinon.stub().returns({
|
||||||
|
iAmBuilderObj: true,
|
||||||
|
version: 1,
|
||||||
|
opts: builder.opts,
|
||||||
|
});
|
||||||
|
builder.isFullySigned = sinon.stub().returns(false);
|
||||||
|
|
||||||
|
builder.vanilla = {
|
||||||
|
scriptSig: [SCRIPTSIG[1]],
|
||||||
|
outs: JSON.stringify([{
|
||||||
|
address: '2NDJbzwzsmRgD2o5HHXPhuq5g6tkKTjYkd6',
|
||||||
|
amountSatStr: '123',
|
||||||
|
}]),
|
||||||
|
};
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
};
|
||||||
|
|
||||||
|
function dummyProposal(opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
|
||||||
return new TxProposal({
|
return new TxProposal({
|
||||||
creator: 'creator',
|
creator: 'creator',
|
||||||
createdTs: 1,
|
createdTs: 1,
|
||||||
builder: new FakeBuilder(),
|
builder: dummyBuilder(opts),
|
||||||
inputChainPaths: ['m/1'],
|
inputChainPaths: ['m/1'],
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
@ -47,7 +117,7 @@ describe('TxProposal', function() {
|
||||||
var txp = new TxProposal({
|
var txp = new TxProposal({
|
||||||
creator: 1,
|
creator: 1,
|
||||||
createdTs: 1,
|
createdTs: 1,
|
||||||
builder: new FakeBuilder(),
|
builder: dummyBuilder(),
|
||||||
inputChainPaths: 'm/1',
|
inputChainPaths: 'm/1',
|
||||||
});
|
});
|
||||||
should.exist(txp);
|
should.exist(txp);
|
||||||
|
@ -59,8 +129,7 @@ describe('TxProposal', function() {
|
||||||
});
|
});
|
||||||
describe('#getId', function() {
|
describe('#getId', function() {
|
||||||
it('should return id', function() {
|
it('should return id', function() {
|
||||||
var b = new FakeBuilder();
|
var b = new dummyBuilder();
|
||||||
var spy = sinon.spy(b.tx, 'getNormalizedHash');
|
|
||||||
var txp = new TxProposal({
|
var txp = new TxProposal({
|
||||||
creator: 1,
|
creator: 1,
|
||||||
createdTs: 1,
|
createdTs: 1,
|
||||||
|
@ -68,12 +137,12 @@ describe('TxProposal', function() {
|
||||||
inputChainPaths: 'm/1',
|
inputChainPaths: 'm/1',
|
||||||
});
|
});
|
||||||
txp.getId().should.equal('123456');;
|
txp.getId().should.equal('123456');;
|
||||||
sinon.assert.callCount(spy, 1);
|
sinon.assert.callCount(b.build().getNormalizedHash, 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('#toObj', function() {
|
describe('#toObj', function() {
|
||||||
it('should return an object and remove builder', function() {
|
it('should return an object and remove builder', function() {
|
||||||
var b = new FakeBuilder();
|
var b = new dummyBuilder();
|
||||||
var txp = new TxProposal({
|
var txp = new TxProposal({
|
||||||
creator: 1,
|
creator: 1,
|
||||||
createdTs: 1,
|
createdTs: 1,
|
||||||
|
@ -87,7 +156,7 @@ describe('TxProposal', function() {
|
||||||
should.exist(o.builderObj);
|
should.exist(o.builderObj);
|
||||||
});
|
});
|
||||||
it('toObjTrim', function() {
|
it('toObjTrim', function() {
|
||||||
var b = new FakeBuilder();
|
var b = new dummyBuilder();
|
||||||
var txp = new TxProposal({
|
var txp = new TxProposal({
|
||||||
creator: 1,
|
creator: 1,
|
||||||
createdTs: 1,
|
createdTs: 1,
|
||||||
|
@ -104,54 +173,66 @@ describe('TxProposal', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
describe('#fromObj', function() {
|
describe('#fromUntrustedObj', function() {
|
||||||
it('should fail to create from wrong object', function() {
|
it('should fail to create from wrong object', function() {
|
||||||
var b = new FakeBuilder();
|
var b = new dummyBuilder();
|
||||||
(function() {
|
(function() {
|
||||||
var txp = TxProposal.fromObj({
|
var txp = TxProposal.fromUntrustedObj({
|
||||||
creator: 1,
|
creator: 1,
|
||||||
createdTs: 1,
|
createdTs: 1,
|
||||||
builderObj: b.toObj(),
|
builderObj: b.toObj(),
|
||||||
inputChainPaths: ['m/1'],
|
inputChainPaths: ['m/1'],
|
||||||
});
|
});
|
||||||
}).should.throw('Invalid');
|
}).should.throw('tx is not defined');
|
||||||
});
|
});
|
||||||
it('sets force opts', function() {
|
it('sets force opts', function() {
|
||||||
var b = new FakeBuilder();
|
|
||||||
b.opts = {
|
// Create an incomming TX proposal, with certain options...
|
||||||
juan: 1,
|
var b = new dummyBuilder({
|
||||||
pepe: 1,
|
opts: {
|
||||||
fee: 1000
|
juan: 1,
|
||||||
};
|
pepe: 1,
|
||||||
var txp;
|
fee: 1000
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var o = {
|
var o = {
|
||||||
creator: 1,
|
creator: 1,
|
||||||
createdTs: 1,
|
createdTs: 1,
|
||||||
builderObj: b.toObj(),
|
builderObj: b.toObj(),
|
||||||
inputChainPaths: ['m/1'],
|
inputChainPaths: ['m/1'],
|
||||||
};
|
};
|
||||||
(function() {
|
sinon.stub(TxProposal.prototype, '_check').returns(true);
|
||||||
txp = TxProposal.fromObj(o, {
|
|
||||||
pepe: 100
|
//Force other options
|
||||||
});
|
var txp = TxProposal.fromUntrustedObj(o, {
|
||||||
}).should.throw('Invalid tx proposal: no ins');
|
pepe: 100
|
||||||
|
});
|
||||||
|
|
||||||
o.builderObj.opts.should.deep.equal({
|
o.builderObj.opts.should.deep.equal({
|
||||||
juan: 1,
|
juan: 1,
|
||||||
pepe: 100,
|
pepe: 100,
|
||||||
feeSat: undefined,
|
feeSat: undefined,
|
||||||
fee: undefined
|
fee: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
|
TxProposal.prototype._check.restore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('#fromObj', function() {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('#setSent', function() {
|
describe('#setSent', function() {
|
||||||
it('should set txid and timestamp', function() {
|
it('should set txid and timestamp', function() {
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
var txp = new TxProposal({
|
var txp = new TxProposal({
|
||||||
creator: 1,
|
creator: 1,
|
||||||
createdTs: 1,
|
createdTs: 1,
|
||||||
builder: new FakeBuilder(),
|
builder: new dummyBuilder(),
|
||||||
inputChainPaths: ['m/1'],
|
inputChainPaths: ['m/1'],
|
||||||
});
|
});
|
||||||
txp.setSent('3a42');
|
txp.setSent('3a42');
|
||||||
|
@ -166,7 +247,7 @@ describe('TxProposal', function() {
|
||||||
var txp = new TxProposal({
|
var txp = new TxProposal({
|
||||||
creator: 1,
|
creator: 1,
|
||||||
createdTs: 1,
|
createdTs: 1,
|
||||||
builder: new FakeBuilder(),
|
builder: new dummyBuilder(),
|
||||||
inputChainPaths: ['m/1'],
|
inputChainPaths: ['m/1'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -180,7 +261,8 @@ describe('TxProposal', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Signature verification', function() {
|
describe('Signature verification', function() {
|
||||||
var validScriptSig = new bitcore.Script(FakeBuilder.VALID_SCRIPTSIG_BUF);
|
var validScriptSig1Sig = new bitcore.Script(SCRIPTSIG[0]);
|
||||||
|
var validScriptSig = new bitcore.Script(SCRIPTSIG[1]);
|
||||||
|
|
||||||
var pubkeys = [
|
var pubkeys = [
|
||||||
'03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d',
|
'03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d',
|
||||||
|
@ -194,33 +276,43 @@ describe('TxProposal', function() {
|
||||||
var keyBuf = someKeys.map(function(hex) {
|
var keyBuf = someKeys.map(function(hex) {
|
||||||
return new Buffer(hex, 'hex');
|
return new Buffer(hex, 'hex');
|
||||||
});
|
});
|
||||||
it('#_formatKeys', function() {
|
it('#formatKeys', function() {
|
||||||
(function() {
|
(function() {
|
||||||
TxProposal._formatKeys(someKeys);
|
TxProposal.formatKeys(someKeys);
|
||||||
}).should.throw('buffers');
|
}).should.throw('buffers');
|
||||||
var res = TxProposal._formatKeys(keyBuf);
|
var res = TxProposal.formatKeys(keyBuf);
|
||||||
});
|
});
|
||||||
it('#_verifyScriptSig arg checks', function() {
|
it('#_verifyScriptSig arg checks', function() {
|
||||||
|
var txp = dummyProposal();
|
||||||
(function() {
|
(function() {
|
||||||
TxProposal._verifySignatures(
|
txp.verifySignatures(
|
||||||
keyBuf,
|
keyBuf,
|
||||||
new bitcore.Script(new Buffer('112233', 'hex')),
|
new bitcore.Script(new Buffer('112233', 'hex')),
|
||||||
new Buffer('1a', 'hex'));
|
new Buffer('1a', 'hex'));
|
||||||
}).should.throw('script');
|
}).should.throw('script');
|
||||||
});
|
});
|
||||||
it('#_verifyScriptSig, no signatures', function() {
|
it('#_verifyScriptSig, no signatures', function() {
|
||||||
var ret = TxProposal._verifySignatures(keyBuf, validScriptSig, new Buffer(32));
|
var txp = dummyProposal();
|
||||||
ret.length.should.equal(0);
|
(function() {
|
||||||
|
txp.verifySignatures(keyBuf, validScriptSig, new Buffer(32));
|
||||||
|
}).should.throw('invalid');
|
||||||
|
});
|
||||||
|
it('#_verifyScriptSig, one signature', function() {
|
||||||
|
// Data taken from bitcore's TransactionBuilder test
|
||||||
|
var txp = dummyProposal();
|
||||||
|
var tx = dummyProposal().builder.build();
|
||||||
|
var ret = txp.verifySignatures(pubkeys, validScriptSig1Sig, tx.hashForSignature());
|
||||||
|
ret.should.deep.equal(['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d']);
|
||||||
});
|
});
|
||||||
it('#_verifyScriptSig, two signatures', function() {
|
it('#_verifyScriptSig, two signatures', function() {
|
||||||
// Data taken from bitcore's TransactionBuilder test
|
// Data taken from bitcore's TransactionBuilder test
|
||||||
var txp = dummyProposal();
|
var txp = dummyProposal();
|
||||||
var tx = dummyProposal().builder.build();
|
var tx = dummyProposal().builder.build();
|
||||||
var ret = TxProposal._verifySignatures(pubkeys, validScriptSig, tx.hashForSignature());
|
var ret = txp.verifySignatures(pubkeys, validScriptSig, tx.hashForSignature());
|
||||||
ret.should.deep.equal(['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3']);
|
ret.should.deep.equal(['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3']);
|
||||||
});
|
});
|
||||||
it('#_infoFromRedeemScript', function() {
|
it('#infoFromRedeemScript', function() {
|
||||||
var info = TxProposal._infoFromRedeemScript(validScriptSig);
|
var info = TxProposal.infoFromRedeemScript(validScriptSig);
|
||||||
var keys = info.keys;
|
var keys = info.keys;
|
||||||
keys.length.should.equal(5);
|
keys.length.should.equal(5);
|
||||||
for (var i in keys) {
|
for (var i in keys) {
|
||||||
|
@ -228,54 +320,107 @@ describe('TxProposal', function() {
|
||||||
}
|
}
|
||||||
Buffer.isBuffer(info.script.getBuffer()).should.equal(true);
|
Buffer.isBuffer(info.script.getBuffer()).should.equal(true);
|
||||||
});
|
});
|
||||||
it('#_updateSignedBy', function() {
|
it('#getSignersPubKeys', function() {
|
||||||
var txp = dummyProposal();
|
var txp = dummyProposal();
|
||||||
txp._inputSigners.should.deep.equal([
|
var pubkeys = txp.getSignersPubKeys();
|
||||||
['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3']
|
pubkeys.should.deep.equal([PUBKEYS]);
|
||||||
]);
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
describe('#getSignatures', function() {
|
||||||
|
it('should get signatures', function() {
|
||||||
|
var txp = dummyProposal();
|
||||||
|
var sigs = txp.getSignatures();
|
||||||
|
sigs.length.should.equal(1);
|
||||||
|
sigs[0].length.should.equal(2);
|
||||||
|
sigs[0][0].should.equal(SIG0);
|
||||||
|
sigs[0][1].should.equal(SIG1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#addSignature', function() {
|
||||||
|
it('should add signatures maintaing pubkeys order', function() {
|
||||||
|
var txp = dummyProposal({
|
||||||
|
nsig:1
|
||||||
|
});
|
||||||
|
txp.getSignersPubKeys()[0].length.should.equal(1);
|
||||||
|
|
||||||
|
txp.addSignature('pepe', [SIG1]);
|
||||||
|
txp.getSignersPubKeys()[0].length.should.equal(2);
|
||||||
|
|
||||||
|
var keys = txp.getSignersPubKeys()[0];
|
||||||
|
var keysSorted = _.clone(keys).sort();
|
||||||
|
keysSorted.should.deep.equal(keys);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
it('should fail with invalid signatures', function() {
|
||||||
|
var txp = dummyProposal({
|
||||||
|
nsig:1
|
||||||
|
});
|
||||||
|
txp.getSignersPubKeys()[0].length.should.equal(1);
|
||||||
|
|
||||||
|
(function(){
|
||||||
|
txp.addSignature('pepe', ['002030a77c9613d6ee010717c1abc494668d877e3fa0ae4c520f65cc3b308754c98c02205219d387bcb291bd44805b9468439e4168b02a6a180cdbcc24d84d71d696c1ae01']);
|
||||||
|
}).should.throw('BADSIG');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail adding the same signature twice', function() {
|
||||||
|
var txp = dummyProposal({
|
||||||
|
nsig:1
|
||||||
|
});
|
||||||
|
txp.getSignersPubKeys()[0].length.should.equal(1);
|
||||||
|
|
||||||
|
txp.addSignature('pepe', [SIG1]);
|
||||||
|
(function(){
|
||||||
|
txp.addSignature('pepe', [SIG1]);
|
||||||
|
}).should.throw('BADSIG');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('#_check', function() {
|
describe('#_check', function() {
|
||||||
var txp = dummyProposal();
|
|
||||||
var backup = txp.builder.tx.ins;
|
|
||||||
|
|
||||||
it('OK', function() {
|
it('OK', function() {
|
||||||
txp._check();
|
dummyProposal({})._check();
|
||||||
});
|
});
|
||||||
it('FAIL ins', function() {
|
it('FAIL ins', function() {
|
||||||
txp.builder.tx.ins = [];
|
|
||||||
(function() {
|
(function() {
|
||||||
txp._check();
|
dummyProposal({
|
||||||
|
noins: true,
|
||||||
|
})._check();
|
||||||
}).should.throw('no ins');
|
}).should.throw('no ins');
|
||||||
txp.builder.tx.ins = backup;
|
|
||||||
});
|
});
|
||||||
it('FAIL signhash SINGLE', function() {
|
it('FAIL signhash SINGLE', function() {
|
||||||
sinon.stub(txp.builder.tx, 'getHashType').returns(Transaction.SIGHASH_SINGLE);
|
var txp = dummyProposal({
|
||||||
|
hashtype: Transaction.SIGHASH_SINGLE
|
||||||
|
});
|
||||||
(function() {
|
(function() {
|
||||||
txp._check();
|
txp._check();
|
||||||
}).should.throw('signatures');
|
}).should.throw('signatures');
|
||||||
txp.builder.tx.getHashType.restore();
|
|
||||||
});
|
});
|
||||||
it('FAIL signhash NONE', function() {
|
it('FAIL signhash NONE', function() {
|
||||||
sinon.stub(txp.builder.tx, 'getHashType').returns(Transaction.SIGHASH_NONE);
|
var txp = dummyProposal({
|
||||||
|
hashtype: Transaction.SIGHASH_NONE,
|
||||||
|
});
|
||||||
(function() {
|
(function() {
|
||||||
txp._check();
|
txp._check();
|
||||||
}).should.throw('signatures');
|
}).should.throw('signatures');
|
||||||
txp.builder.tx.getHashType.restore();
|
|
||||||
});
|
});
|
||||||
it('FAIL signhash ANYONECANPAY', function() {
|
it('FAIL signhash ANYONECANPAY', function() {
|
||||||
sinon.stub(txp.builder.tx, 'getHashType').returns(Transaction.SIGHASH_ANYONECANPAY);
|
var txp = dummyProposal({
|
||||||
|
hashtype: Transaction.SIGHASH_ANYONECANPAY,
|
||||||
|
});
|
||||||
(function() {
|
(function() {
|
||||||
txp._check();
|
txp._check();
|
||||||
}).should.throw('signatures');
|
}).should.throw('signatures');
|
||||||
txp.builder.tx.getHashType.restore();
|
|
||||||
});
|
});
|
||||||
it('FAIL no signatures', function() {
|
it('FAIL no signatures', function() {
|
||||||
var backup = txp.builder.tx.ins[0].s;
|
var txp = dummyProposal({
|
||||||
txp.builder.tx.ins[0].s = undefined;
|
nosigs: true,
|
||||||
|
});
|
||||||
(function() {
|
(function() {
|
||||||
txp._check();
|
txp._check();
|
||||||
}).should.throw('no signatures');
|
}).should.throw('no signatures');
|
||||||
txp.builder.tx.ins[0].s = backup;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -366,21 +511,19 @@ describe('TxProposal', function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#merge', function() {
|
describe.skip('#merge', function() {
|
||||||
var txp = dummyProposal();
|
|
||||||
var backup = txp.builder.tx.ins;
|
|
||||||
it('with self', function() {
|
it('with self', function() {
|
||||||
|
var txp = dummyProposal();
|
||||||
var hasChanged = txp.merge(txp);
|
var hasChanged = txp.merge(txp);
|
||||||
hasChanged.should.equal(false);
|
hasChanged.should.equal(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('with less signatures', function() {
|
it('with less signatures', function() {
|
||||||
|
var txp = dummyProposal();
|
||||||
|
var txp1Sig = dummyProposal({
|
||||||
|
nsig:1
|
||||||
|
});
|
||||||
var backup = txp.builder.vanilla.scriptSig[0];
|
var backup = txp.builder.vanilla.scriptSig[0];
|
||||||
txp.builder.merge = function() {
|
|
||||||
// Only one signatures.
|
|
||||||
this.vanilla.scriptSig = ['0048304502207d8e832bd576c93300e53ab6cbd68641961bec60690c358fd42d8e42b7d7d687022100a1daa89923efdb4c9b615d065058d9e1644f67000694a7d0806759afa7bef19b014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae'];
|
|
||||||
this.tx.ins[0].s = new Buffer(this.vanilla.scriptSig[0], 'hex');
|
|
||||||
};
|
|
||||||
var hasChanged = txp.merge(txp);
|
var hasChanged = txp.merge(txp);
|
||||||
hasChanged.should.equal(true);
|
hasChanged.should.equal(true);
|
||||||
|
|
||||||
|
@ -388,7 +531,6 @@ describe('TxProposal', function() {
|
||||||
txp.builder.tx.ins[0].s = new Buffer(backup, 'hex');
|
txp.builder.tx.ins[0].s = new Buffer(backup, 'hex');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('with more signatures', function() {
|
it('with more signatures', function() {
|
||||||
txp.builder.merge = function() {
|
txp.builder.merge = function() {
|
||||||
// 3 signatures.
|
// 3 signatures.
|
||||||
|
@ -407,7 +549,7 @@ describe('TxProposal', function() {
|
||||||
};
|
};
|
||||||
delete txp['creator'];
|
delete txp['creator'];
|
||||||
(function() {
|
(function() {
|
||||||
txp.setCopayers('juan', {
|
txp.setCopayers({
|
||||||
pk1: 'pepe'
|
pk1: 'pepe'
|
||||||
})
|
})
|
||||||
}).should.throw('no creator');
|
}).should.throw('no creator');
|
||||||
|
@ -422,7 +564,7 @@ describe('TxProposal', function() {
|
||||||
['pkX']
|
['pkX']
|
||||||
];
|
];
|
||||||
(function() {
|
(function() {
|
||||||
txp.setCopayers('juan', {
|
txp.setCopayers({
|
||||||
pk1: 'pepe'
|
pk1: 'pepe'
|
||||||
})
|
})
|
||||||
}).should.throw('creator');
|
}).should.throw('creator');
|
||||||
|
@ -439,7 +581,7 @@ describe('TxProposal', function() {
|
||||||
['pk0', 'pkX']
|
['pk0', 'pkX']
|
||||||
];
|
];
|
||||||
(function() {
|
(function() {
|
||||||
txp.setCopayers('juan', {
|
txp.setCopayers({
|
||||||
pk1: 'pepe'
|
pk1: 'pepe'
|
||||||
})
|
})
|
||||||
}).should.throw('unknown sig');
|
}).should.throw('unknown sig');
|
||||||
|
@ -456,7 +598,7 @@ describe('TxProposal', function() {
|
||||||
'creator': Date.now()
|
'creator': Date.now()
|
||||||
};
|
};
|
||||||
(function() {
|
(function() {
|
||||||
txp.setCopayers('juan', {
|
txp.setCopayers({
|
||||||
pk0: 'creator',
|
pk0: 'creator',
|
||||||
pk1: 'pepe',
|
pk1: 'pepe',
|
||||||
pk2: 'john'
|
pk2: 'john'
|
||||||
|
@ -468,13 +610,12 @@ describe('TxProposal', function() {
|
||||||
it("should set signedBy (trivial case)", function() {
|
it("should set signedBy (trivial case)", function() {
|
||||||
var txp = dummyProposal();
|
var txp = dummyProposal();
|
||||||
var ts = Date.now();
|
var ts = Date.now();
|
||||||
txp._inputSigners = [
|
|
||||||
['pk1', 'pk0']
|
sinon.stub(txp, 'getSignersPubKeys').returns(['pk1', 'pk0']);
|
||||||
];
|
|
||||||
txp.signedBy = {
|
txp.signedBy = {
|
||||||
'creator': Date.now()
|
'creator': Date.now()
|
||||||
};
|
};
|
||||||
txp.setCopayers('pepe', {
|
txp.setCopayers({
|
||||||
pk0: 'creator',
|
pk0: 'creator',
|
||||||
pk1: 'pepe',
|
pk1: 'pepe',
|
||||||
pk2: 'john'
|
pk2: 'john'
|
||||||
|
@ -486,13 +627,11 @@ describe('TxProposal', function() {
|
||||||
it("should assign creator", function() {
|
it("should assign creator", function() {
|
||||||
var txp = dummyProposal();
|
var txp = dummyProposal();
|
||||||
var ts = Date.now();
|
var ts = Date.now();
|
||||||
txp._inputSigners = [
|
sinon.stub(txp, 'getSignersPubKeys').returns(['pk0']);
|
||||||
['pk0']
|
|
||||||
];
|
|
||||||
txp.signedBy = {};
|
txp.signedBy = {};
|
||||||
delete txp['creator'];
|
delete txp['creator'];
|
||||||
delete txp['creatorTs'];
|
delete txp['creatorTs'];
|
||||||
txp.setCopayers('creator', {
|
txp.setCopayers({
|
||||||
pk0: 'creator',
|
pk0: 'creator',
|
||||||
pk1: 'pepe',
|
pk1: 'pepe',
|
||||||
pk2: 'john'
|
pk2: 'john'
|
||||||
|
@ -508,33 +647,27 @@ describe('TxProposal', function() {
|
||||||
txp.signedBy = {};
|
txp.signedBy = {};
|
||||||
delete txp['creator'];
|
delete txp['creator'];
|
||||||
delete txp['creatorTs'];
|
delete txp['creatorTs'];
|
||||||
txp._inputSigners = [
|
sinon.stub(txp, 'getSignersPubKeys').returns(['pk0', 'pk1']);
|
||||||
['pk0', 'pk1']
|
|
||||||
];
|
|
||||||
(function() {
|
(function() {
|
||||||
txp.setCopayers(
|
txp.setCopayers({
|
||||||
'creator', {
|
pk0: 'creator',
|
||||||
pk0: 'creator',
|
pk1: 'pepe',
|
||||||
pk1: 'pepe',
|
pk2: 'john'
|
||||||
pk2: 'john'
|
}, {
|
||||||
}, {
|
'creator2': 1
|
||||||
'creator2': 1
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
}).should.throw('only 1');
|
}).should.throw('only 1');
|
||||||
})
|
})
|
||||||
|
|
||||||
it("if signed, should not change ts", function() {
|
it("if signed, should not change ts", function() {
|
||||||
var txp = dummyProposal();
|
var txp = dummyProposal();
|
||||||
var ts = Date.now();
|
var ts = Date.now();
|
||||||
txp._inputSigners = [
|
sinon.stub(txp, 'getSignersPubKeys').returns(['pk0', 'pk1']);
|
||||||
['pk0', 'pk1']
|
|
||||||
];
|
|
||||||
txp.creator = 'creator';
|
txp.creator = 'creator';
|
||||||
txp.signedBy = {
|
txp.signedBy = {
|
||||||
'creator': 1
|
'creator': 1
|
||||||
};
|
};
|
||||||
txp.setCopayers('pepe', {
|
txp.setCopayers({
|
||||||
pk0: 'creator',
|
pk0: 'creator',
|
||||||
pk1: 'pepe',
|
pk1: 'pepe',
|
||||||
pk2: 'john'
|
pk2: 'john'
|
||||||
|
|
|
@ -9,14 +9,15 @@ var TransactionBuilder = bitcore.TransactionBuilder;
|
||||||
var util = bitcore.util;
|
var util = bitcore.util;
|
||||||
var networks = bitcore.networks;
|
var networks = bitcore.networks;
|
||||||
|
|
||||||
var FakeBuilder = requireMock('FakeBuilder');
|
|
||||||
var TxProposal = copay.TxProposal;
|
var TxProposal = copay.TxProposal;
|
||||||
var TxProposals = copay.TxProposals;
|
var TxProposals = copay.TxProposals;
|
||||||
|
|
||||||
var dummyProposal = new TxProposal({
|
var dummyProposal = new TxProposal({
|
||||||
creator: 1,
|
creator: 1,
|
||||||
createdTs: 1,
|
createdTs: 1,
|
||||||
builder: new FakeBuilder(),
|
builder: {
|
||||||
|
toObj: sinon.stub().returns({}),
|
||||||
|
},
|
||||||
inputChainPaths: ['m/1'],
|
inputChainPaths: ['m/1'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
549
test/Wallet.js
549
test/Wallet.js
|
@ -3,7 +3,6 @@ var Wallet = copay.Wallet;
|
||||||
var PrivateKey = copay.PrivateKey;
|
var PrivateKey = copay.PrivateKey;
|
||||||
var Network = requireMock('FakeNetwork');
|
var Network = requireMock('FakeNetwork');
|
||||||
var Blockchain = requireMock('FakeBlockchain');
|
var Blockchain = requireMock('FakeBlockchain');
|
||||||
var Builder = requireMock('FakeBuilder');
|
|
||||||
var TransactionBuilder = bitcore.TransactionBuilder;
|
var TransactionBuilder = bitcore.TransactionBuilder;
|
||||||
var Transaction = bitcore.Transaction;
|
var Transaction = bitcore.Transaction;
|
||||||
var Address = bitcore.Address;
|
var Address = bitcore.Address;
|
||||||
|
@ -234,7 +233,7 @@ describe('Wallet model', function() {
|
||||||
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true);
|
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true);
|
||||||
|
|
||||||
var f = function() {
|
var f = function() {
|
||||||
var ntxid = w._createTxProposal(
|
w._createTxProposal(
|
||||||
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||||
'123456789',
|
'123456789',
|
||||||
null,
|
null,
|
||||||
|
@ -276,7 +275,7 @@ describe('Wallet model', function() {
|
||||||
unspentTest
|
unspentTest
|
||||||
);
|
);
|
||||||
|
|
||||||
Object.keys(txp._inputSigners).length.should.equal(1);
|
Object.keys(txp.getSignersPubKeys()).length.should.equal(1);
|
||||||
var tx = txp.builder.build();
|
var tx = txp.builder.build();
|
||||||
should.exist(tx);
|
should.exist(tx);
|
||||||
chai.expect(txp.comment).to.be.null;
|
chai.expect(txp.comment).to.be.null;
|
||||||
|
@ -587,47 +586,6 @@ describe('Wallet model', function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handle network txProposals correctly', function() {
|
|
||||||
var w = createW();
|
|
||||||
var txp = {
|
|
||||||
'txProposal': {
|
|
||||||
inputChainPaths: ['m/1'],
|
|
||||||
builderObj: {
|
|
||||||
version: 1,
|
|
||||||
outs: [{
|
|
||||||
address: '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
|
||||||
amountSatStr: '123456789'
|
|
||||||
}],
|
|
||||||
utxos: [{
|
|
||||||
address: '2N6fdPg2QL7V36XKe7a8wkkA5HCy7fNYmZF',
|
|
||||||
scriptPubKey: 'a91493372782bab70f4eefdefefea8ece0df44f9596887',
|
|
||||||
txid: '2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1',
|
|
||||||
vout: 1,
|
|
||||||
amount: 10,
|
|
||||||
confirmations: 7
|
|
||||||
}],
|
|
||||||
opts: {
|
|
||||||
remainderOut: {
|
|
||||||
address: '2N7BLvdrxJ4YzDtb3hfgt6CMY5rrw5kNT1H'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scriptSig: ['00493046022100b8249a4fc326c4c33882e9d5468a1c6faa01e8c6cef0a24970122e804abdd860022100dbf6ee3b07d3aad8f73997e62ad20654a08aa63a7609792d02f3d5d088e69ad9014cad5321027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d5092102ab32ba51402a139873aeb919c738f5a945f3956f8f8c6ba296677bd29e85d7e821036f119b72e09f76c11ebe2cf754d64eac2cb42c9e623455d54aaa89d70c11f9c82103bcbd3f8ab2c849ea9eae434733cee8b75120d26233def56011b3682ca12081d72103f37f81dc534163b9f73ecf36b91e6c3fb8ae370c24618f91bb1d972e86ceeee255ae'],
|
|
||||||
hashToScriptMap: {
|
|
||||||
'2N6fdPg2QL7V36XKe7a8wkkA5HCy7fNYmZF': '5321027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d5092102ab32ba51402a139873aeb919c738f5a945f3956f8f8c6ba296677bd29e85d7e821036f119b72e09f76c11ebe2cf754d64eac2cb42c9e623455d54aaa89d70c11f9c82103bcbd3f8ab2c849ea9eae434733cee8b75120d26233def56011b3682ca12081d72103f37f81dc534163b9f73ecf36b91e6c3fb8ae370c24618f91bb1d972e86ceeee255ae'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys').returns({
|
|
||||||
'027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d509': 'pepe'
|
|
||||||
});
|
|
||||||
w._onTxProposal('senderID', txp, true);
|
|
||||||
Object.keys(w.txProposals.txps).length.should.equal(1);
|
|
||||||
w.getTxProposals().length.should.equal(1);
|
|
||||||
//stub.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
var newId = '00bacacafe';
|
var newId = '00bacacafe';
|
||||||
it('handle new connections', function(done) {
|
it('handle new connections', function(done) {
|
||||||
var w = createW();
|
var w = createW();
|
||||||
|
@ -800,13 +758,16 @@ describe('Wallet model', function() {
|
||||||
var w = createW2([k2]);
|
var w = createW2([k2]);
|
||||||
var utxo = createUTXO(w);
|
var utxo = createUTXO(w);
|
||||||
w.blockchain.fixUnspent(utxo);
|
w.blockchain.fixUnspent(utxo);
|
||||||
|
var now = Date.now();
|
||||||
w.spend({
|
w.spend({
|
||||||
toAddress: toAddress,
|
toAddress: toAddress,
|
||||||
amountSat: amountSatStr,
|
amountSat: amountSatStr,
|
||||||
}, function(err, ntxid) {
|
}, function(err, ntxid) {
|
||||||
w.on('txProposalsUpdated', function() {
|
w.on('txProposalsUpdated', function() {
|
||||||
w.getTxProposals()[0].signedByUs.should.equal(true);
|
var txp = w.txProposals.txps[ntxid];
|
||||||
w.getTxProposals()[0].rejectedByUs.should.equal(false);
|
var myId = w.getMyCopayerId();
|
||||||
|
txp.signedBy[myId].should.be.above(now - 1);
|
||||||
|
should.not.exist(txp.rejectedBy[myId]);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
w.privateKey = k2;
|
w.privateKey = k2;
|
||||||
|
@ -870,8 +831,9 @@ describe('Wallet model', function() {
|
||||||
w.blockchain.fixUnspent(utxo);
|
w.blockchain.fixUnspent(utxo);
|
||||||
sinon.spy(w, 'sendIndexes');
|
sinon.spy(w, 'sendIndexes');
|
||||||
sinon.spy(w, 'sendTxProposal');
|
sinon.spy(w, 'sendTxProposal');
|
||||||
sinon.spy(w, 'broadcastTx');
|
sinon.spy(w, 'issueTx');
|
||||||
sinon.stub(w, 'requiresMultipleSignatures').returns(false);
|
sinon.stub(w, 'requiresMultipleSignatures').returns(false);
|
||||||
|
sinon.stub(w.blockchain, 'broadcast').yields(null, 1234);
|
||||||
w.spend({
|
w.spend({
|
||||||
toAddress: toAddress,
|
toAddress: toAddress,
|
||||||
amountSat: amountSatStr,
|
amountSat: amountSatStr,
|
||||||
|
@ -879,9 +841,8 @@ describe('Wallet model', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
should.exist(id);
|
should.exist(id);
|
||||||
status.should.equal(Wallet.TX_BROADCASTED);
|
status.should.equal(Wallet.TX_BROADCASTED);
|
||||||
w.sendTxProposal.calledOnce.should.equal(true);
|
w.blockchain.broadcast.calledOnce.should.equal(true);
|
||||||
w.sendIndexes.calledOnce.should.equal(true);
|
w.issueTx.calledOnce.should.equal(true);
|
||||||
w.broadcastTx.calledOnce.should.equal(true);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -893,7 +854,7 @@ describe('Wallet model', function() {
|
||||||
sinon.stub(w, 'requiresMultipleSignatures').returns(false);
|
sinon.stub(w, 'requiresMultipleSignatures').returns(false);
|
||||||
sinon.spy(w, 'sendIndexes');
|
sinon.spy(w, 'sendIndexes');
|
||||||
sinon.spy(w, 'sendTxProposal');
|
sinon.spy(w, 'sendTxProposal');
|
||||||
sinon.stub(w, '_doBroadcastTx').yields('error');
|
sinon.stub(w, 'broadcastToBitcoinNetwork').yields('error');
|
||||||
w.spend({
|
w.spend({
|
||||||
toAddress: toAddress,
|
toAddress: toAddress,
|
||||||
amountSat: amountSatStr,
|
amountSat: amountSatStr,
|
||||||
|
@ -938,26 +899,7 @@ describe('Wallet model', function() {
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
describe('#broadcastTx', function() {
|
describe('#issueTx', function() {
|
||||||
it('should fail to send incomplete transaction', function(done) {
|
|
||||||
var w = createW2(null, 1);
|
|
||||||
var utxo = createUTXO(w);
|
|
||||||
var txp = w._createTxProposal(toAddress, amountSatStr + 0, 'hola', utxo);
|
|
||||||
var ntxid = w.txProposals.add(txp);
|
|
||||||
|
|
||||||
// Assign fake builder
|
|
||||||
txp.builder = new Builder();
|
|
||||||
sinon.stub(txp.builder, 'build').returns({
|
|
||||||
isComplete: function() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
(function() {
|
|
||||||
w.broadcastTx(ntxid);
|
|
||||||
}).should.throw('Tx is not complete. Can not broadcast');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should broadcast a TX', function(done) {
|
it('should broadcast a TX', function(done) {
|
||||||
var w = createW2(null, 1);
|
var w = createW2(null, 1);
|
||||||
var utxo = createUTXO(w);
|
var utxo = createUTXO(w);
|
||||||
|
@ -965,7 +907,7 @@ describe('Wallet model', function() {
|
||||||
var ntxid = w.txProposals.add(txp);
|
var ntxid = w.txProposals.add(txp);
|
||||||
sinon.stub(w.blockchain, 'broadcast').yields(null, 1234);
|
sinon.stub(w.blockchain, 'broadcast').yields(null, 1234);
|
||||||
|
|
||||||
w.broadcastTx(ntxid, function(err, txid, status) {
|
w.issueTx(ntxid, function(err, txid, status) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
txid.should.equal(1234);
|
txid.should.equal(1234);
|
||||||
status.should.equal(Wallet.TX_BROADCASTED);
|
status.should.equal(Wallet.TX_BROADCASTED);
|
||||||
|
@ -973,7 +915,6 @@ describe('Wallet model', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should send Payment Messages on a PayPro payment', function(done) {
|
it('should send Payment Messages on a PayPro payment', function(done) {
|
||||||
var w = createW2(null, 1);
|
var w = createW2(null, 1);
|
||||||
var utxo = createUTXO(w);
|
var utxo = createUTXO(w);
|
||||||
|
@ -992,7 +933,7 @@ describe('Wallet model', function() {
|
||||||
sinon.stub(w, 'onPayProPaymentAck');
|
sinon.stub(w, 'onPayProPaymentAck');
|
||||||
|
|
||||||
|
|
||||||
w.broadcastTx(ntxid, function(err, txid, status) {
|
w.issueTx(ntxid, function(err, txid, status) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
txid.should.equal(1234);
|
txid.should.equal(1234);
|
||||||
status.should.equal(Wallet.TX_BROADCASTED);
|
status.should.equal(Wallet.TX_BROADCASTED);
|
||||||
|
@ -1004,6 +945,26 @@ describe('Wallet model', function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fail to send incomplete transaction', function(done) {
|
||||||
|
var w = createW2(null, 1);
|
||||||
|
var utxo = createUTXO(w);
|
||||||
|
var txp = w._createTxProposal(toAddress, amountSatStr + 0, 'hola', utxo);
|
||||||
|
var ntxid = w.txProposals.add(txp);
|
||||||
|
|
||||||
|
// Assign fake builder
|
||||||
|
sinon.stub(txp.builder, 'build').returns({
|
||||||
|
serialize: sinon.stub().returns('xxx'),
|
||||||
|
isComplete: sinon.stub().returns(false),
|
||||||
|
});
|
||||||
|
(function() {
|
||||||
|
w.issueTx(ntxid);
|
||||||
|
}).should.throw('tx is not complete');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -1128,7 +1089,7 @@ describe('Wallet model', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('removeTxWithSpentInputs', function() {
|
describe.skip('removeTxWithSpentInputs', function() {
|
||||||
var w;
|
var w;
|
||||||
var utxos;
|
var utxos;
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
@ -1508,22 +1469,21 @@ describe('Wallet model', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('_getKeymap', function() {
|
describe('_getPubkeyToCopayerMap', function() {
|
||||||
var w = cachedCreateW();
|
var w = cachedCreateW();
|
||||||
|
|
||||||
it('should set keymap', function() {
|
it('should set keymap', function() {
|
||||||
var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys', function() {
|
var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys').returns({
|
||||||
return {
|
'123': 'juan'
|
||||||
'123': 'juan'
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
var txp = {
|
var txp = {
|
||||||
_inputSigners: [
|
getSignersPubKeys: sinon.stub().returns([
|
||||||
['123']
|
['123']
|
||||||
],
|
]),
|
||||||
inputChainPaths: ['/m/1'],
|
inputChainPaths: ['/m/1'],
|
||||||
};
|
};
|
||||||
var map = w._getKeyMap(txp);
|
var map = w._getPubkeyToCopayerMap(txp);
|
||||||
|
console.log('[Wallet.js.1526:map:]', map); //TODO
|
||||||
Object.keys(map).length.should.equal(1);
|
Object.keys(map).length.should.equal(1);
|
||||||
map['123'].should.equal('juan');
|
map['123'].should.equal('juan');
|
||||||
stub.restore();
|
stub.restore();
|
||||||
|
@ -1534,13 +1494,13 @@ describe('Wallet model', function() {
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
var txp = {
|
var txp = {
|
||||||
_inputSigners: [
|
getSignersPubKeys: sinon.stub().returns([
|
||||||
['234']
|
['123']
|
||||||
],
|
]),
|
||||||
inputChainPaths: ['/m/1'],
|
inputChainPaths: ['/m/1'],
|
||||||
};
|
};
|
||||||
(function() {
|
(function() {
|
||||||
w._getKeyMap(txp);
|
w._getPubkeyToCopayerMap(txp);
|
||||||
}).should.throw('does not match known copayers');
|
}).should.throw('does not match known copayers');
|
||||||
stub.restore();
|
stub.restore();
|
||||||
});
|
});
|
||||||
|
@ -1550,14 +1510,14 @@ describe('Wallet model', function() {
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
var txp = {
|
var txp = {
|
||||||
_inputSigners: [
|
getSignersPubKeys: sinon.stub().returns([
|
||||||
['234', '321'],
|
['234', '321'],
|
||||||
['234', '322']
|
['234', '322']
|
||||||
],
|
]),
|
||||||
inputChainPaths: ['/m/1'],
|
inputChainPaths: ['/m/1'],
|
||||||
};
|
};
|
||||||
(function() {
|
(function() {
|
||||||
w._getKeyMap(txp);
|
w._getPubkeyToCopayerMap(txp);
|
||||||
}).should.throw('does not match known copayers');
|
}).should.throw('does not match known copayers');
|
||||||
stub.restore();
|
stub.restore();
|
||||||
});
|
});
|
||||||
|
@ -1570,12 +1530,12 @@ describe('Wallet model', function() {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
var txp = {
|
var txp = {
|
||||||
_inputSigners: [
|
getSignersPubKeys: sinon.stub().returns([
|
||||||
['234', '123']
|
['234', '321'],
|
||||||
],
|
]),
|
||||||
inputChainPaths: ['/m/1'],
|
inputChainPaths: ['/m/1'],
|
||||||
};
|
};
|
||||||
var map = w._getKeyMap(txp);
|
var map = w._getPubkeyToCopayerMap(txp);
|
||||||
Object.keys(map).length.should.equal(2);
|
Object.keys(map).length.should.equal(2);
|
||||||
map['123'].should.equal('juan');
|
map['123'].should.equal('juan');
|
||||||
map['234'].should.equal('pepe');
|
map['234'].should.equal('pepe');
|
||||||
|
@ -1593,14 +1553,16 @@ describe('Wallet model', function() {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
var txp = {
|
var txp = {
|
||||||
_inputSigners: [
|
getSignersPubKeys: sinon.stub().returns([
|
||||||
['234', '123'],
|
['234', '123'],
|
||||||
['555']
|
['555']
|
||||||
],
|
]),
|
||||||
|
|
||||||
|
_inputSigners: [],
|
||||||
inputChainPaths: ['/m/1'],
|
inputChainPaths: ['/m/1'],
|
||||||
};
|
};
|
||||||
(function() {
|
(function() {
|
||||||
w._getKeyMap(txp);
|
w._getPubkeyToCopayerMap(txp);
|
||||||
}).should.throw('different sig');
|
}).should.throw('different sig');
|
||||||
stub.restore();
|
stub.restore();
|
||||||
});
|
});
|
||||||
|
@ -1618,14 +1580,14 @@ describe('Wallet model', function() {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
var txp = {
|
var txp = {
|
||||||
_inputSigners: [
|
getSignersPubKeys: sinon.stub().returns([
|
||||||
['234', '123'],
|
['234', '123'],
|
||||||
['555', '666']
|
['555', '666'],
|
||||||
],
|
]),
|
||||||
inputChainPaths: ['/m/1'],
|
inputChainPaths: ['/m/1'],
|
||||||
};
|
};
|
||||||
(function() {
|
(function() {
|
||||||
w._getKeyMap(txp);
|
w._getPubkeyToCopayerMap(txp);
|
||||||
}).should.throw('different sig');
|
}).should.throw('different sig');
|
||||||
stub.restore();
|
stub.restore();
|
||||||
});
|
});
|
||||||
|
@ -1643,13 +1605,13 @@ describe('Wallet model', function() {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
var txp = {
|
var txp = {
|
||||||
_inputSigners: [
|
getSignersPubKeys: sinon.stub().returns([
|
||||||
['234', '123'],
|
['234', '123'],
|
||||||
['555', '666']
|
['555', '666']
|
||||||
],
|
]),
|
||||||
inputChainPaths: ['/m/1'],
|
inputChainPaths: ['/m/1'],
|
||||||
};
|
};
|
||||||
var gk = w._getKeyMap(txp);
|
var gk = w._getPubkeyToCopayerMap(txp);
|
||||||
gk.should.deep.equal({
|
gk.should.deep.equal({
|
||||||
'123': 'pedro',
|
'123': 'pedro',
|
||||||
'234': 'pepe',
|
'234': 'pepe',
|
||||||
|
@ -1662,72 +1624,18 @@ describe('Wallet model', function() {
|
||||||
|
|
||||||
describe('_onTxProposal', function() {
|
describe('_onTxProposal', function() {
|
||||||
var w, data, txp;
|
var w, data, txp;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
w = cachedCreateW();
|
w = cachedCreateW();
|
||||||
data = {
|
data = {
|
||||||
txProposal: {
|
txProposal: {
|
||||||
dummy: 1,
|
dummy: 1,
|
||||||
},
|
builderObj: {
|
||||||
};
|
dummy: 1,
|
||||||
sinon.stub(w.txProposals, 'deleteOne');
|
},
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should handle corrupt tx', function() {
|
|
||||||
w.txProposals.merge = sinon.stub().throws(new Error('test error'));
|
|
||||||
|
|
||||||
sinon.stub(w, 'on');
|
|
||||||
w._onTxProposal('senderID', data);
|
|
||||||
w.on.called.should.equal(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call _processIncomingTxProposal', function(done) {
|
|
||||||
var args = {
|
|
||||||
xxx: 'yyy',
|
|
||||||
new: true,
|
|
||||||
txp: {
|
|
||||||
setCopayers: sinon.stub(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
sinon.stub(w.txProposals, 'merge').returns(args);
|
|
||||||
sinon.stub(w, '_processIncomingTxProposal').yields(null);
|
|
||||||
sinon.stub(w, '_getKeyMap').returns(null);
|
|
||||||
|
|
||||||
w.on('txProposalEvent', function(e) {
|
|
||||||
e.type.should.equal('new');
|
|
||||||
w._processIncomingTxProposal.getCall(0).args[0].should.deep.equal(args);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
w._onTxProposal('senderID', data);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle corrupt tx, case2', function() {
|
|
||||||
sinon.stub(w.txProposals, 'merge').returns({
|
|
||||||
ntxid: '1'
|
|
||||||
});
|
|
||||||
sinon.stub(w, 'on');
|
|
||||||
sinon.stub(w, '_getKeyMap').throws(new Error('test error'));
|
|
||||||
w._onTxProposal('senderID', data);
|
|
||||||
w.on.called.should.equal(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe.skip('_onTxProposal', function() {
|
|
||||||
var w, data, txp;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
w = cachedCreateW();
|
|
||||||
w._getKeyMap = sinon.stub();
|
|
||||||
w.sendSeen = sinon.spy();
|
|
||||||
w.sendTxProposal = sinon.spy();
|
|
||||||
data = {
|
|
||||||
txProposal: {
|
|
||||||
dummy: 1,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
txp = {
|
txp = {
|
||||||
|
getId: sinon.stub().returns('ntxid'),
|
||||||
getSeen: sinon.stub().returns(false),
|
getSeen: sinon.stub().returns(false),
|
||||||
setSeen: sinon.spy(),
|
setSeen: sinon.spy(),
|
||||||
setCopayers: sinon.spy(),
|
setCopayers: sinon.spy(),
|
||||||
|
@ -1738,159 +1646,132 @@ describe('Wallet model', function() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
w.txProposals.get = sinon.stub().returns(txp);
|
sinon.stub(w, '_processIncomingNewTxProposal').yields(null);
|
||||||
|
sinon.stub(w.txProposals, 'get').returns(null);
|
||||||
w.txProposals.merge = sinon.stub().returns({
|
sinon.stub(w.txProposals, 'deleteOne');
|
||||||
ntxid: 1,
|
sinon.stub(w, '_txProposalFromUntrustedObj').returns(txp);
|
||||||
txp: txp,
|
sinon.stub(w, '_getPubkeyToCopayerMap');
|
||||||
new: true,
|
|
||||||
hasChanged: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
it('should handle new 1', function(done) {
|
|
||||||
|
|
||||||
var spy1 = sinon.spy();
|
afterEach(function() {});
|
||||||
var spy2 = sinon.spy();
|
|
||||||
w.on('txProposalEvent', spy1);
|
it('should handle corrupt message', function() {
|
||||||
w.on('txProposalsUpdated', spy2);
|
w._txProposalFromUntrustedObj.throws('error');
|
||||||
|
w._onTxProposal('senderID', data);
|
||||||
|
w._processIncomingNewTxProposal.called.should.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore localTx', function() {
|
||||||
|
w.txProposals.get = sinon.stub().returns(txp);
|
||||||
|
w._txProposalFromUntrustedObj.throws('error');
|
||||||
|
w._onTxProposal('senderID', data);
|
||||||
|
w._processIncomingNewTxProposal.called.should.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept a new valid TXP', function(done) {
|
||||||
|
w.txProposals.get = sinon.stub().returns(null);
|
||||||
w.on('txProposalEvent', function(e) {
|
w.on('txProposalEvent', function(e) {
|
||||||
e.type.should.equal('new');
|
e.type.should.equal('new');
|
||||||
spy1.called.should.be.true;
|
w._processIncomingNewTxProposal.called.should.equal(true);
|
||||||
spy2.called.should.be.true;
|
w._getPubkeyToCopayerMap.called.should.equal(true);
|
||||||
txp.setSeen.calledOnce.should.be.true;
|
|
||||||
w.sendSeen.calledOnce.should.equal(true);
|
|
||||||
w.sendTxProposal.calledOnce.should.equal(true);
|
|
||||||
done();
|
done();
|
||||||
});
|
})
|
||||||
|
|
||||||
w._onTxProposal('senderID', data);
|
w._onTxProposal('senderID', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle signed', function(done) {
|
|
||||||
var data = {
|
it('should ignore is a TXP arrived 2 times', function(done) {
|
||||||
txProposal: {
|
w.txProposals.get = sinon.stub().returns(null);
|
||||||
dummy: 1,
|
var secondCall = false;
|
||||||
},
|
w.on('txProposalEvent', function(e) {
|
||||||
};
|
e.type.should.equal('new');
|
||||||
|
w._processIncomingNewTxProposal.calledOnce.should.equal(true);
|
||||||
|
w._getPubkeyToCopayerMap.called.should.equal(true);
|
||||||
|
w._onTxProposal('senderID', data);
|
||||||
|
w._processIncomingNewTxProposal.calledOnce.should.equal(true);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
w._onTxProposal('senderID', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
it('should handle a real txp correctly', function(done) {
|
||||||
|
w._txProposalFromUntrustedObj.restore();
|
||||||
|
w._getPubkeyToCopayerMap.restore();
|
||||||
var txp = {
|
var txp = {
|
||||||
getSeen: sinon.stub().returns(true),
|
'txProposal': {
|
||||||
setSeen: sinon.spy(),
|
inputChainPaths: ['m/1'],
|
||||||
setCopayers: sinon.stub().returns(['new copayer']),
|
builderObj: {
|
||||||
builder: {
|
version: 1,
|
||||||
build: sinon.stub().returns({
|
outs: [{
|
||||||
isComplete: sinon.stub().returns(false),
|
address: '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||||
}),
|
amountSatStr: '123456789'
|
||||||
},
|
}],
|
||||||
|
utxos: [{
|
||||||
|
address: '2N6fdPg2QL7V36XKe7a8wkkA5HCy7fNYmZF',
|
||||||
|
scriptPubKey: 'a91493372782bab70f4eefdefefea8ece0df44f9596887',
|
||||||
|
txid: '2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1',
|
||||||
|
vout: 1,
|
||||||
|
amount: 10,
|
||||||
|
confirmations: 7
|
||||||
|
}],
|
||||||
|
opts: {
|
||||||
|
remainderOut: {
|
||||||
|
address: '2N7BLvdrxJ4YzDtb3hfgt6CMY5rrw5kNT1H'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scriptSig: ['00493046022100b8249a4fc326c4c33882e9d5468a1c6faa01e8c6cef0a24970122e804abdd860022100dbf6ee3b07d3aad8f73997e62ad20654a08aa63a7609792d02f3d5d088e69ad9014cad5321027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d5092102ab32ba51402a139873aeb919c738f5a945f3956f8f8c6ba296677bd29e85d7e821036f119b72e09f76c11ebe2cf754d64eac2cb42c9e623455d54aaa89d70c11f9c82103bcbd3f8ab2c849ea9eae434733cee8b75120d26233def56011b3682ca12081d72103f37f81dc534163b9f73ecf36b91e6c3fb8ae370c24618f91bb1d972e86ceeee255ae'],
|
||||||
|
hashToScriptMap: {
|
||||||
|
'2N6fdPg2QL7V36XKe7a8wkkA5HCy7fNYmZF': '5321027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d5092102ab32ba51402a139873aeb919c738f5a945f3956f8f8c6ba296677bd29e85d7e821036f119b72e09f76c11ebe2cf754d64eac2cb42c9e623455d54aaa89d70c11f9c82103bcbd3f8ab2c849ea9eae434733cee8b75120d26233def56011b3682ca12081d72103f37f81dc534163b9f73ecf36b91e6c3fb8ae370c24618f91bb1d972e86ceeee255ae'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
w.txProposals.get = sinon.stub().returns(txp);
|
var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys').returns({
|
||||||
w.txProposals.merge = sinon.stub().returns({
|
'027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d509': 'pepe'
|
||||||
ntxid: 1,
|
|
||||||
txp: txp,
|
|
||||||
new: false,
|
|
||||||
hasChanged: true,
|
|
||||||
});
|
});
|
||||||
|
w.on('txProposalEvent', function(e) {
|
||||||
|
Object.keys(w.txProposals.txps).length.should.equal(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
w._onTxProposal('senderID', txp, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('_onSignature', function() {
|
||||||
|
var w, data, txp;
|
||||||
|
beforeEach(function() {
|
||||||
|
w = cachedCreateW2();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {});
|
||||||
|
|
||||||
|
it('should handle corrupt message', function() {
|
||||||
|
w._onSignature('senderID', 'sigs');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sign a txp', function(done) {
|
||||||
|
var utxo = createUTXO(w);
|
||||||
|
var txp = w._createTxProposal(PP.outs[0].address, PP.outs[0].amountSatStr, 'hola', utxo);
|
||||||
|
var ntxid = w.txProposals.add(txp);
|
||||||
|
sinon.stub(w.blockchain, 'broadcast').yields(null, 1234);
|
||||||
|
data = {
|
||||||
|
ntxid: ntxid,
|
||||||
|
signatures: [1],
|
||||||
|
}
|
||||||
|
sinon.stub(w.txProposals, 'get').returns(txp);
|
||||||
|
sinon.stub(txp, '_addSignatureAndVerify').returns();
|
||||||
|
|
||||||
var spy1 = sinon.spy();
|
|
||||||
var spy2 = sinon.spy();
|
|
||||||
w.on('txProposalEvent', spy1);
|
|
||||||
w.on('txProposalsUpdated', spy2);
|
|
||||||
w.on('txProposalEvent', function(e) {
|
w.on('txProposalEvent', function(e) {
|
||||||
e.type.should.equal('signed');
|
e.type.should.equal('signed');
|
||||||
spy1.called.should.be.true;
|
|
||||||
spy2.called.should.be.true;
|
|
||||||
txp.setSeen.calledOnce.should.be.false;
|
|
||||||
w.sendSeen.calledOnce.should.be.false;
|
|
||||||
w.sendTxProposal.calledOnce.should.be.true;
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
})
|
||||||
|
w._onSignature('senderID', data);
|
||||||
w._onTxProposal('senderID', data);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should only mark as broadcast if found in the blockchain', function(done) {
|
|
||||||
w.txProposals.get = sinon.stub().returns(txp);
|
|
||||||
w.txProposals.merge = sinon.stub().returns({
|
|
||||||
ntxid: 1,
|
|
||||||
txp: txp,
|
|
||||||
new: false,
|
|
||||||
hasChanged: false,
|
|
||||||
});
|
|
||||||
w._checkSentTx = sinon.stub().yields(false);
|
|
||||||
w.on('txProposalEvent', function(e) {
|
|
||||||
txp.setSent.called.should.equal(false);
|
|
||||||
txp.setSent.calledWith(1).should.equal(false);
|
|
||||||
w.sendTxProposal.called.should.equal(false);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
w._onTxProposal('senderID', data);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not overwrite sent info', function(done) {
|
|
||||||
var data = {
|
|
||||||
txProposal: {
|
|
||||||
dummy: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
var txp = {
|
|
||||||
getSeen: sinon.stub().returns(true),
|
|
||||||
setCopayers: sinon.stub().returns(['new copayer']),
|
|
||||||
getSent: sinon.stub().returns(true),
|
|
||||||
setSent: sinon.spy(),
|
|
||||||
builder: {
|
|
||||||
build: sinon.stub().returns({
|
|
||||||
isComplete: sinon.stub().returns(true),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
w.txProposals.get = sinon.stub().returns(txp);
|
|
||||||
w.txProposals.merge = sinon.stub().returns({
|
|
||||||
ntxid: 1,
|
|
||||||
txp: txp,
|
|
||||||
new: false,
|
|
||||||
hasChanged: false,
|
|
||||||
});
|
|
||||||
w._checkSentTx = sinon.stub().yields(true);
|
|
||||||
|
|
||||||
w._onTxProposal('senderID', data);
|
|
||||||
txp.setSent.called.should.be.false;
|
|
||||||
w.sendTxProposal.called.should.be.false;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should resend when not complete only if changed', function(done) {
|
|
||||||
var data = {
|
|
||||||
txProposal: {
|
|
||||||
dummy: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
var txp = {
|
|
||||||
getSeen: sinon.stub().returns(true),
|
|
||||||
setCopayers: sinon.stub().returns(['new copayer']),
|
|
||||||
builder: {
|
|
||||||
build: sinon.stub().returns({
|
|
||||||
isComplete: sinon.stub().returns(false),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
w.txProposals.get = sinon.stub().returns(txp);
|
|
||||||
w.txProposals.merge = sinon.stub().returns({
|
|
||||||
ntxid: 1,
|
|
||||||
txp: txp,
|
|
||||||
new: false,
|
|
||||||
hasChanged: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
w._onTxProposal('senderID', data);
|
|
||||||
w.sendTxProposal.called.should.be.false;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -1993,6 +1874,70 @@ describe('Wallet model', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('sendMesages', function() {
|
||||||
|
var w, txp;
|
||||||
|
beforeEach(function() {
|
||||||
|
w = createW2(null, 1);
|
||||||
|
var utxo = createUTXO(w);
|
||||||
|
txp = w._createTxProposal(
|
||||||
|
'2MtP8WyiwG7ZdVWM96CVsk2M1N8zyfiVQsY',
|
||||||
|
'123456789',
|
||||||
|
null,
|
||||||
|
utxo
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to sendReject', function() {
|
||||||
|
w.sendReject(txp.getId());
|
||||||
|
w.network.send.calledOnce.should.equal(true);
|
||||||
|
var payload = w.network.send.getCall(0).args[1];
|
||||||
|
payload.type.should.equal('reject');
|
||||||
|
payload.walletId.should.equal(w.id);
|
||||||
|
payload.ntxid.should.equal(txp.getId());
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should be able to sendSend', function() {
|
||||||
|
w.sendSeen(txp.getId());
|
||||||
|
w.network.send.calledOnce.should.equal(true);
|
||||||
|
var payload = w.network.send.getCall(0).args[1];
|
||||||
|
payload.type.should.equal('seen');
|
||||||
|
payload.walletId.should.equal(w.id);
|
||||||
|
payload.ntxid.should.equal(txp.getId());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to sendTxProposal', function() {
|
||||||
|
w.txProposals.add(txp);
|
||||||
|
w.sendTxProposal(txp.getId());
|
||||||
|
w.network.send.calledOnce.should.equal(true);
|
||||||
|
var payload = w.network.send.getCall(0).args[1];
|
||||||
|
payload.type.should.equal('txProposal');
|
||||||
|
payload.walletId.should.equal(w.id);
|
||||||
|
payload.txProposal.should.deep.equal(txp.toObjTrim());
|
||||||
|
});
|
||||||
|
it('should be able to sendAllTxProposals', function() {
|
||||||
|
w.txProposals.add(txp);
|
||||||
|
w.sendAllTxProposals();
|
||||||
|
w.network.send.calledOnce.should.equal(true);
|
||||||
|
var payload = w.network.send.getCall(0).args[1];
|
||||||
|
payload.type.should.equal('txProposal');
|
||||||
|
payload.walletId.should.equal(w.id);
|
||||||
|
payload.txProposal.should.deep.equal(txp.toObjTrim());
|
||||||
|
});
|
||||||
|
it('should be able to sendSignature', function() {
|
||||||
|
w.txProposals.add(txp);
|
||||||
|
w.sendSignature(txp.getId());
|
||||||
|
w.network.send.calledOnce.should.equal(true);
|
||||||
|
var payload = w.network.send.getCall(0).args[1];
|
||||||
|
payload.type.should.equal('signature');
|
||||||
|
payload.walletId.should.equal(w.id);
|
||||||
|
payload.signatures.length.should.equal(1);
|
||||||
|
var sig = new Buffer(payload.signatures[0], 'hex');
|
||||||
|
sig.length.should.be.above(70);
|
||||||
|
sig.length.should.be.below(74);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#obtainNetworkName', function() {
|
describe('#obtainNetworkName', function() {
|
||||||
it('should return the networkname', function() {
|
it('should return the networkname', function() {
|
||||||
Wallet.obtainNetworkName({
|
Wallet.obtainNetworkName({
|
||||||
|
@ -2438,13 +2383,19 @@ describe('Wallet model', function() {
|
||||||
isChange: true,
|
isChange: true,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
w.getTxProposals = sinon.stub().returns([{
|
w.txProposals.txps = [{
|
||||||
sentTxid: 'id0',
|
sentTxid: 'id0',
|
||||||
comment: 'My comment',
|
comment: 'My comment',
|
||||||
|
rejectedBy: {},
|
||||||
|
signedBy: {},
|
||||||
|
seenBy: {},
|
||||||
}, {
|
}, {
|
||||||
sentTxid: 'id1',
|
sentTxid: 'id1',
|
||||||
comment: 'Another comment',
|
comment: 'Another comment',
|
||||||
}]);
|
rejectedBy: {},
|
||||||
|
signedBy: {},
|
||||||
|
seenBy: {},
|
||||||
|
}];
|
||||||
w.getTransactionHistory(function(err, res) {
|
w.getTransactionHistory(function(err, res) {
|
||||||
res.should.exist;
|
res.should.exist;
|
||||||
res.items.should.exist;
|
res.items.should.exist;
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
'use scrict';
|
|
||||||
var bitcore = bitcore || require('bitcore');
|
|
||||||
var Script = bitcore.Script;
|
|
||||||
|
|
||||||
var VALID_SCRIPTSIG_BUF = new Buffer('0048304502200708a381dde585ef7fdfaeaeb5da9b451d3e22b01eac8a5e3d03b959e24a7478022100c90e76e423523a54a9e9c43858337ebcef1a539a7fc685c2698dd8648fcf1b9101473044022030a77c9613d6ee010717c1abc494668d877e3fa0ae4c520f65cc3b308754c98c02205219d387bcb291bd44805b9468439e4168b02a6a180cdbcc24d84d71d696c1ae014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae', 'hex');
|
|
||||||
|
|
||||||
function Tx() {
|
|
||||||
this.ins = [{
|
|
||||||
s: VALID_SCRIPTSIG_BUF
|
|
||||||
}];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Tx.prototype.serialize = function() {
|
|
||||||
return new Buffer('1234','hex');
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Tx.prototype.getSize = function() {
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
Tx.prototype.getHashType = function() {
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
Tx.prototype.getNormalizedHash = function() {
|
|
||||||
return '123456';
|
|
||||||
};
|
|
||||||
Tx.prototype.hashForSignature = function() {
|
|
||||||
return new Buffer('31103626e162f1cbfab6b95b08c9f6e78aae128523261cb37f8dfd4783cb09a7', 'hex');
|
|
||||||
};
|
|
||||||
|
|
||||||
function FakeBuilder() {
|
|
||||||
this.test = 1;
|
|
||||||
this.tx = new Tx();
|
|
||||||
this.signhash = 1;
|
|
||||||
this.inputMap = [{
|
|
||||||
address: '2NDJbzwzsmRgD2o5HHXPhuq5g6tkKTjYkd6',
|
|
||||||
scriptPubKey: new Script(new Buffer('a914dc0623476aefb049066b09b0147a022e6eb8429187', 'hex')),
|
|
||||||
scriptType: 4,
|
|
||||||
i: 0
|
|
||||||
}];
|
|
||||||
|
|
||||||
this.vanilla = {
|
|
||||||
scriptSig: [VALID_SCRIPTSIG_BUF],
|
|
||||||
outs: JSON.stringify([{
|
|
||||||
address: '2NDJbzwzsmRgD2o5HHXPhuq5g6tkKTjYkd6',
|
|
||||||
amountSatStr: '123',
|
|
||||||
}]),
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
FakeBuilder.prototype.merge = function() {};
|
|
||||||
|
|
||||||
FakeBuilder.prototype.build = function() {
|
|
||||||
return this.tx;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
FakeBuilder.prototype.toObj = function() {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
FakeBuilder.VALID_SCRIPTSIG_BUF = VALID_SCRIPTSIG_BUF;
|
|
||||||
module.exports = FakeBuilder;
|
|
|
@ -128,9 +128,6 @@ var createBundle = function(opts) {
|
||||||
b.require('./test/mocks/FakeNetwork', {
|
b.require('./test/mocks/FakeNetwork', {
|
||||||
expose: './mocks/FakeNetwork'
|
expose: './mocks/FakeNetwork'
|
||||||
});
|
});
|
||||||
b.require('./test/mocks/FakeBuilder', {
|
|
||||||
expose: './mocks/FakeBuilder'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!opts.debug) {
|
if (!opts.debug) {
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
<div class="last-transactions-footer">
|
<div class="last-transactions-footer">
|
||||||
<div class="row collapse">
|
<div class="row collapse">
|
||||||
<div class="large-5 medium-7 small-12 columns" ng-show="!tx.sentTs">
|
<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">
|
<div class="hide-for-small-only">
|
||||||
<button class="primary tiny m0 m15r" ng-click="sign(tx.ntxid)" ng-disabled="loading">
|
<button class="primary tiny m0 m15r" ng-click="sign(tx.ntxid)" ng-disabled="loading">
|
||||||
<i class="fi-check"></i> <span translate>Sign</span>
|
<i class="fi-check"></i> <span translate>Sign</span>
|
||||||
|
|
Loading…
Reference in New Issue