mirror of https://github.com/BTCPrivate/copay.git
signMessage on message
This commit is contained in:
parent
cdc80e94eb
commit
8339cd7298
|
@ -369,6 +369,8 @@ angular.module('copayApp.controllers').controller('SendController',
|
|||
notification.success('Success', 'Transaction proposal created');
|
||||
else if (status == copay.Wallet.TX_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
|
||||
notification.error('Error', 'Unknown error occured');
|
||||
};
|
||||
|
@ -378,7 +380,7 @@ angular.module('copayApp.controllers').controller('SendController',
|
|||
$scope.error = $scope.success = null;
|
||||
$scope.loading = true;
|
||||
$rootScope.txAlertCount = 0;
|
||||
w.broadcastTx(ntxid, function(err, txid, status) {
|
||||
w.issueTx(ntxid, function(err, txid, status) {
|
||||
$scope.notifyStatus(status);
|
||||
if (cb) return cb();
|
||||
else $scope.loadTxs();
|
||||
|
|
|
@ -41,8 +41,7 @@ function TxProposal(opts) {
|
|||
this.paymentAckMemo = opts.paymentAckMemo || null;
|
||||
this.paymentProtocolURL = opts.paymentProtocolURL || null;
|
||||
|
||||
// not from obj
|
||||
this._pubkeysForScriptCache = {};
|
||||
this.resetCache();
|
||||
|
||||
// New Tx Proposal
|
||||
if (_.isEmpty(this.seenBy) && opts.creator) {
|
||||
|
@ -206,10 +205,122 @@ TxProposal.prototype.isPending = function(maxRejectCount) {
|
|||
return true;
|
||||
};
|
||||
|
||||
TxProposal.prototype._setSigned = function(copayerId) {
|
||||
|
||||
// Sign powns rejected
|
||||
if (this.rejectedBy[copayerId]) {
|
||||
log.info("WARN: a previously rejected transaction was signed by:", copayerId);
|
||||
delete this.rejectedBy[copayerId];
|
||||
}
|
||||
|
||||
this.signedBy[copayerId] = Date.now();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @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: {},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* addSignature
|
||||
*
|
||||
* @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
|
||||
* @desc get Pubkeys of signers, for each input. this is CPU intensive
|
||||
*
|
||||
* @return {string[][]} array of hashes for signing pubkeys for each input
|
||||
*/
|
||||
|
@ -219,34 +330,36 @@ TxProposal.prototype.getSignersPubKeys = function(forceUpdate) {
|
|||
|
||||
var signersPubKey = [];
|
||||
|
||||
if (!self._signersPubKey || forceUpdate) {
|
||||
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._pubkeysForScriptCache[input.s]) {
|
||||
if (!self.cache.pubkeysForScript[input.s]) {
|
||||
var scriptSig = new Script(input.s);
|
||||
var signatureCount = scriptSig.countSignatures();
|
||||
|
||||
var info = TxProposal._infoFromRedeemScript(scriptSig);
|
||||
var info = TxProposal.infoFromRedeemScript(scriptSig);
|
||||
var txSigHash = tx.hashForSignature(info.script, parseInt(index), Transaction.SIGHASH_ALL);
|
||||
var inputSignersPubKey = TxProposal._verifySignatures(info.keys, scriptSig, txSigHash);
|
||||
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._pubkeysForScriptCache[input.s] = inputSignersPubKey;
|
||||
self.cache.pubkeysForScript[input.s] = inputSignersPubKey;
|
||||
}
|
||||
|
||||
signersPubKey[index] = self._pubkeysForScriptCache[input.s];
|
||||
signersPubKey[index] = self.cache.pubkeysForScript[input.s];
|
||||
});
|
||||
self._signersPubKey = signersPubKey;
|
||||
self.cache.signersPubKey = signersPubKey;
|
||||
} else {
|
||||
log.debug('Using signatures verification cache')
|
||||
}
|
||||
|
||||
return self._signersPubKey;
|
||||
return self.cache.signersPubKey;
|
||||
};
|
||||
|
||||
TxProposal.prototype.getId = function() {
|
||||
|
@ -261,7 +374,7 @@ TxProposal.prototype.getId = function() {
|
|||
TxProposal.prototype.toObj = function() {
|
||||
var o = JSON.parse(JSON.stringify(this));
|
||||
delete o['builder'];
|
||||
delete o['_pubkeysForScriptCache'];
|
||||
delete o['cache'];
|
||||
o.builderObj = this.builder.toObj();
|
||||
return o;
|
||||
};
|
||||
|
@ -279,8 +392,6 @@ TxProposal.fromObj = function(o, forceOpts) {
|
|||
preconditions.checkArgument(o.builderObj);
|
||||
delete o['builder'];
|
||||
forceOpts = forceOpts || {};
|
||||
var builderClass = forceOpts.transactionBuilderClass || TransactionBuilder;
|
||||
|
||||
o.builderObj.opts = o.builderObj.opts || {};
|
||||
|
||||
// force opts is requested.
|
||||
|
@ -295,7 +406,7 @@ TxProposal.fromObj = function(o, forceOpts) {
|
|||
}
|
||||
|
||||
try {
|
||||
o.builder = builderClass.fromObj(o.builderObj);
|
||||
o.builder = TransactionBuilder.fromObj(o.builderObj);
|
||||
} catch (e) {
|
||||
throw new Error(e);
|
||||
return null;
|
||||
|
@ -317,7 +428,7 @@ TxProposal.prototype.toObjTrim = function() {
|
|||
return TxProposal._trim(this.toObj());
|
||||
};
|
||||
|
||||
TxProposal._formatKeys = function(keys) {
|
||||
TxProposal.formatKeys = function(keys) {
|
||||
var ret = [];
|
||||
for (var i in keys) {
|
||||
if (!Buffer.isBuffer(keys[i]))
|
||||
|
@ -333,33 +444,66 @@ TxProposal._formatKeys = function(keys) {
|
|||
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(inKeys);
|
||||
preconditions.checkState(Buffer.isBuffer(inKeys[0]));
|
||||
var self = this;
|
||||
|
||||
if (scriptSig.chunks[0] !== 0)
|
||||
throw new Error('Invalid scriptSig');
|
||||
|
||||
var keys = TxProposal._formatKeys(inKeys);
|
||||
var keys = TxProposal.formatKeys(inKeys);
|
||||
var ret = [];
|
||||
for (var i = 1; i <= scriptSig.countSignatures(); i++) {
|
||||
var chunk = scriptSig.chunks[i];
|
||||
log.debug('\t Verifying CHUNK:', i);
|
||||
var sigRaw = new Buffer(chunk.slice(0, chunk.length - 1));
|
||||
|
||||
log.debug('\t Verifying CHUNK:', i);
|
||||
for (var j in keys) {
|
||||
var k = keys[j];
|
||||
if (k.keyObj.verifySignatureSync(txSigHash, sigRaw)) {
|
||||
ret.push(k.keyHex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
var signingPubKeyHex = self._verifyOneSignature(keys, sigRaw, txSigHash);
|
||||
if (!signingPubKeyHex)
|
||||
throw new Error('Found a signature that is invalid');
|
||||
|
||||
ret.push(signingPubKeyHex);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
TxProposal._infoFromRedeemScript = function(s) {
|
||||
TxProposal.infoFromRedeemScript = function(s) {
|
||||
var redeemScript = new Script(s.chunks[s.chunks.length - 1]);
|
||||
if (!redeemScript)
|
||||
throw new Error('Bad scriptSig (no redeemscript)');
|
||||
|
@ -455,10 +599,8 @@ TxProposal.prototype.setCopayers = function(pubkeyToCopayerMap) {
|
|||
this.seenBy[this.creator] = this.createdTs = Date.now();
|
||||
}
|
||||
|
||||
//Ended. Update this.
|
||||
for (var i in newCopayer) {
|
||||
this.signedBy[i] = newCopayer[i];
|
||||
}
|
||||
//Ended. Update this
|
||||
_.extend(this.signedBy, newCopayer);
|
||||
|
||||
// signedBy has preference over rejectedBy
|
||||
for (var i in this.signedBy) {
|
||||
|
|
|
@ -105,6 +105,11 @@ TxProposals.prototype.add = function(txp) {
|
|||
};
|
||||
|
||||
|
||||
TxProposals.prototype.exist = function(ntxid) {
|
||||
return this.txps[ntxid] ? true : false;
|
||||
};
|
||||
|
||||
|
||||
TxProposals.prototype.get = function(ntxid) {
|
||||
var ret = this.txps[ntxid];
|
||||
if (!ret)
|
||||
|
|
|
@ -122,6 +122,7 @@ inherits(Wallet, events.EventEmitter);
|
|||
Wallet.TX_BROADCASTED = 'txBroadcasted';
|
||||
Wallet.TX_PROPOSAL_SENT = 'txProposalSent';
|
||||
Wallet.TX_SIGNED = 'txSigned';
|
||||
Wallet.TX_SIGNED_AND_BROADCASTED = 'txSignedAndBroadcasted';
|
||||
|
||||
Wallet.prototype.emitAndKeepAlive = function(args) {
|
||||
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
|
||||
* @property lockTime null
|
||||
|
@ -144,7 +145,6 @@ Wallet.builderOpts = {
|
|||
signhash: bitcore.Transaction.SIGHASH_ALL,
|
||||
fee: undefined,
|
||||
feeSat: undefined,
|
||||
builderClass: undefined,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -467,8 +467,6 @@ Wallet.prototype._processTxProposalPayPro = function(txp, cb) {
|
|||
};
|
||||
|
||||
/**
|
||||
* _processIncomingTxProposal
|
||||
*
|
||||
* @desc Process an NEW incoming transaction proposal. Runs safety and sanity checks on it.
|
||||
*
|
||||
* @param mergeInfo Proposals merge information, as returned by TxProposals.merge
|
||||
|
@ -490,6 +488,12 @@ Wallet.prototype._processIncomingNewTxProposal = function(txp, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
|
||||
/* only for stubbing */
|
||||
Wallet.prototype._txProposalFromUntrustedObj = function(data, opts) {
|
||||
return TxProposal.fromUntrustedObj(data, opts);
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Handles a NEW 'TXPROPOSAL' network message
|
||||
|
@ -500,27 +504,31 @@ Wallet.prototype._processIncomingNewTxProposal = function(txp, cb) {
|
|||
* @emits txProposalsUpdated
|
||||
*/
|
||||
Wallet.prototype._onTxProposal = function(senderId, data) {
|
||||
preconditions.checkArgument(data.txProposal);
|
||||
var self = this;
|
||||
var incomingTx = TxProposal.fromUntrustedObj(data.txProposal, Wallet.builderOpts);
|
||||
var incomingNtxid = incomingTx.getId();
|
||||
|
||||
try {
|
||||
var localTx = this.txProposals.get(incomingNtxid);
|
||||
} catch (e) {};
|
||||
var incomingTx = self._txProposalFromUntrustedObj(data.txProposal, Wallet.builderOpts);
|
||||
var incomingNtxid = incomingTx.getId();
|
||||
} catch (e) {
|
||||
log.warn(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (localTx) {
|
||||
log.debug('Ignoring existing tx Proposal:' + incomingNtxid);
|
||||
if (this.txProposals.exist(incomingNtxid)) {
|
||||
log.warn('Ignoring existing tx Proposal:' + incomingNtxid);
|
||||
return;
|
||||
}
|
||||
|
||||
self._processIncomingNewTxProposal(incomingTx, function(err) {
|
||||
if (err) {
|
||||
log.info('Corrupt TX proposal received from:', senderId, err.toString());
|
||||
log.warn('Corrupt TX proposal received from:', senderId, err.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
var keyMap = self._getPubkeyToCopayerMap(incomingTx);
|
||||
incomingTx.setCopayers(keyMap);
|
||||
|
||||
var pubkeyToCopayerMap = self._getPubkeyToCopayerMap(incomingTx);
|
||||
incomingTx.setCopayers(pubkeyToCopayerMap);
|
||||
|
||||
self.txProposals.add(incomingTx);
|
||||
self.emitAndKeepAlive('txProposalEvent', {
|
||||
|
@ -531,7 +539,7 @@ Wallet.prototype._onTxProposal = function(senderId, data) {
|
|||
};
|
||||
|
||||
|
||||
Wallet.prototype._onSignatures = function(senderId, data) {
|
||||
Wallet.prototype._onSignature = function(senderId, data) {
|
||||
var self = this;
|
||||
try {
|
||||
var localTx = this.txProposals.get(data.ntxid);
|
||||
|
@ -539,16 +547,13 @@ Wallet.prototype._onSignatures = function(senderId, data) {
|
|||
log.info('Ignoring signature for unknown tx Proposal:' + data.ntxid);
|
||||
return;
|
||||
};
|
||||
|
||||
var keyMap = self._getPubkeyToCopayerMap(locaTx);
|
||||
|
||||
// TODO look senderIdin keyMap
|
||||
localTx.addSignature(pubkeys, data.signature, keyMap);
|
||||
|
||||
this.emitAndKeepAlive('txProposalEvent', {
|
||||
type: 'signed',
|
||||
localTx.addSignature(senderId, data.signatures);
|
||||
self.issueTxIfComplete(data.ntxid, function(err, txid) {
|
||||
self.emitAndKeepAlive('txProposalEvent', {
|
||||
type: txid ? 'signedAndBroadcasted' : 'signed',
|
||||
cId: senderId,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1259,10 +1264,13 @@ 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',
|
||||
signatures: txp.getMySignatures(),
|
||||
ntxid: ntxid,
|
||||
signatures: signatures,
|
||||
walletId: this.id,
|
||||
});
|
||||
};
|
||||
|
@ -1517,6 +1525,16 @@ Wallet.prototype.sign = function(ntxid) {
|
|||
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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -1531,13 +1549,13 @@ Wallet.prototype.sign = function(ntxid) {
|
|||
*/
|
||||
Wallet.prototype.signAndSend = function(ntxid, cb) {
|
||||
if (this.sign(ntxid)) {
|
||||
var txp = this.txProposals.get(ntxid);
|
||||
this.sendSignature(ntxid);
|
||||
if (txp.isFullySigned()) {
|
||||
return this.broadcastTx(ntxid, cb);
|
||||
} else {
|
||||
this.issueTxIfComplete(ntxid, function(err, txid, status) {
|
||||
if (!txid)
|
||||
return cb(null, ntxid, Wallet.TX_SIGNED);
|
||||
}
|
||||
else
|
||||
return cb(null, ntxid, Wallet.TX_SIGNED_AND_BROADCASTED);
|
||||
});
|
||||
} else {
|
||||
return cb(new Error('Could not sign the proposal'));
|
||||
}
|
||||
|
@ -1552,13 +1570,12 @@ Wallet.prototype.signAndSend = function(ntxid, cb) {
|
|||
* @param cb
|
||||
* @return {undefined}
|
||||
*/
|
||||
Wallet.prototype._doBroadcastTx = function(ntxid, cb) {
|
||||
Wallet.prototype.broadcastToBitcoinNetwork = function(ntxid, cb) {
|
||||
var self = this;
|
||||
var txp = this.txProposals.get(ntxid);
|
||||
var tx = txp.builder.build();
|
||||
|
||||
if (!tx.isComplete())
|
||||
throw new Error('Tx is not complete. Can not broadcast');
|
||||
var tx = txp.builder.build();
|
||||
preconditions.checkState(tx.isComplete(), 'tx is not complete');
|
||||
|
||||
var txHex = tx.serialize().toString('hex');
|
||||
|
||||
|
@ -1575,7 +1592,7 @@ Wallet.prototype._doBroadcastTx = function(ntxid, cb) {
|
|||
return cb(err, txid);
|
||||
});
|
||||
} 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);
|
||||
}
|
||||
});
|
||||
|
@ -1590,10 +1607,10 @@ Wallet.prototype._doBroadcastTx = function(ntxid, cb) {
|
|||
* @param {string} txid - the transaction id on the blockchain
|
||||
* @param {signCallback} cb
|
||||
*/
|
||||
Wallet.prototype.broadcastTx = function(ntxid, cb) {
|
||||
Wallet.prototype.issueTx = function(ntxid, cb) {
|
||||
var self = this;
|
||||
|
||||
self._doBroadcastTx(ntxid, function(err, txid) {
|
||||
self.broadcastToBitcoinNetwork(ntxid, function(err, txid) {
|
||||
if (err) return cb(err);
|
||||
preconditions.checkState(txid);
|
||||
|
||||
|
@ -1610,8 +1627,6 @@ Wallet.prototype.broadcastTx = function(ntxid, cb) {
|
|||
self.onPayProPaymentAck(ntxid, data);
|
||||
});
|
||||
}
|
||||
|
||||
self.sendTxProposal(ntxid);
|
||||
self.emitAndKeepAlive('txProposalsUpdated');
|
||||
return cb(null, txid, Wallet.TX_BROADCASTED);
|
||||
});
|
||||
|
@ -2183,7 +2198,7 @@ Wallet.prototype.spend = function(opts, cb) {
|
|||
self.sendIndexes();
|
||||
// Needs only one signature? Broadcast it!
|
||||
if (!self.requiresMultipleSignatures()) {
|
||||
self.broadcastTx(ntxid, cb);
|
||||
self.issueTx(ntxid, cb);
|
||||
} else {
|
||||
self.sendTxProposal(ntxid);
|
||||
self.emitAndKeepAlive('txProposalsUpdated');
|
||||
|
|
|
@ -154,6 +154,10 @@ angular.module('copayApp.services')
|
|||
notification.info('[' + name + '] Transaction Signed',
|
||||
$filter('translate')('A transaction was signed by') + ' ' + user);
|
||||
break;
|
||||
case 'signedAndBroadcasted':
|
||||
notification.info('[' + name + '] Transaction Approved',
|
||||
$filter('translate')('A transaction was signed and broadcasted by') + ' ' + user);
|
||||
break;
|
||||
case 'rejected':
|
||||
notification.info('[' + name + '] Transaction Rejected',
|
||||
$filter('translate')('A transaction was rejected by') + ' ' + user);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
var Transaction = bitcore.Transaction;
|
||||
var WalletKey = bitcore.WalletKey;
|
||||
var Key = bitcore.Key;
|
||||
|
@ -19,7 +18,18 @@ describe('TxProposal', function() {
|
|||
|
||||
// These 2 signed the scripts below
|
||||
var PUBKEYS = ['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3'];
|
||||
// 1,2 signatures
|
||||
|
||||
|
||||
// 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'
|
||||
|
@ -27,12 +37,15 @@ describe('TxProposal', function() {
|
|||
return new Buffer(hex, 'hex');
|
||||
});
|
||||
|
||||
|
||||
var someKeys = ["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5", "022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"];
|
||||
|
||||
|
||||
function dummyBuilder(opts) {
|
||||
opts = opts || {};
|
||||
|
||||
var script = SCRIPTSIG[opts.nsig - 1 || 1];
|
||||
var index = opts.nsig ? opts.nsig - 1 : 1;
|
||||
var script = SCRIPTSIG[index];
|
||||
|
||||
var aIn = {
|
||||
s: script
|
||||
|
@ -48,6 +61,7 @@ describe('TxProposal', function() {
|
|||
tx.hashForSignature = sinon.stub().returns(
|
||||
new Buffer('31103626e162f1cbfab6b95b08c9f6e78aae128523261cb37f8dfd4783cb09a7', 'hex'));
|
||||
|
||||
|
||||
var builder = {};
|
||||
|
||||
builder.opts = opts.opts || {};
|
||||
|
@ -57,6 +71,7 @@ describe('TxProposal', function() {
|
|||
version: 1,
|
||||
opts: builder.opts,
|
||||
});
|
||||
builder.isFullySigned = sinon.stub().returns(false);
|
||||
|
||||
builder.vanilla = {
|
||||
scriptSig: [SCRIPTSIG[1]],
|
||||
|
@ -246,6 +261,7 @@ describe('TxProposal', function() {
|
|||
});
|
||||
|
||||
describe('Signature verification', function() {
|
||||
var validScriptSig1Sig = new bitcore.Script(SCRIPTSIG[0]);
|
||||
var validScriptSig = new bitcore.Script(SCRIPTSIG[1]);
|
||||
|
||||
var pubkeys = [
|
||||
|
@ -260,33 +276,43 @@ describe('TxProposal', function() {
|
|||
var keyBuf = someKeys.map(function(hex) {
|
||||
return new Buffer(hex, 'hex');
|
||||
});
|
||||
it('#_formatKeys', function() {
|
||||
it('#formatKeys', function() {
|
||||
(function() {
|
||||
TxProposal._formatKeys(someKeys);
|
||||
TxProposal.formatKeys(someKeys);
|
||||
}).should.throw('buffers');
|
||||
var res = TxProposal._formatKeys(keyBuf);
|
||||
var res = TxProposal.formatKeys(keyBuf);
|
||||
});
|
||||
it('#_verifyScriptSig arg checks', function() {
|
||||
var txp = dummyProposal();
|
||||
(function() {
|
||||
TxProposal._verifySignatures(
|
||||
txp.verifySignatures(
|
||||
keyBuf,
|
||||
new bitcore.Script(new Buffer('112233', 'hex')),
|
||||
new Buffer('1a', 'hex'));
|
||||
}).should.throw('script');
|
||||
});
|
||||
it('#_verifyScriptSig, no signatures', function() {
|
||||
var ret = TxProposal._verifySignatures(keyBuf, validScriptSig, new Buffer(32));
|
||||
ret.length.should.equal(0);
|
||||
var txp = dummyProposal();
|
||||
(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() {
|
||||
// Data taken from bitcore's TransactionBuilder test
|
||||
var txp = dummyProposal();
|
||||
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']);
|
||||
});
|
||||
it('#_infoFromRedeemScript', function() {
|
||||
var info = TxProposal._infoFromRedeemScript(validScriptSig);
|
||||
it('#infoFromRedeemScript', function() {
|
||||
var info = TxProposal.infoFromRedeemScript(validScriptSig);
|
||||
var keys = info.keys;
|
||||
keys.length.should.equal(5);
|
||||
for (var i in keys) {
|
||||
|
@ -300,14 +326,57 @@ describe('TxProposal', function() {
|
|||
pubkeys.should.deep.equal([PUBKEYS]);
|
||||
});
|
||||
|
||||
|
||||
|
||||
describe('#getSignatures', function() {
|
||||
it('should', 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('304502200708a381dde585ef7fdfaeaeb5da9b451d3e22b01eac8a5e3d03b959e24a7478022100c90e76e423523a54a9e9c43858337ebcef1a539a7fc685c2698dd8648fcf1b9101');
|
||||
sigs[0][1].should.equal('3044022030a77c9613d6ee010717c1abc494668d877e3fa0ae4c520f65cc3b308754c98c02205219d387bcb291bd44805b9468439e4168b02a6a180cdbcc24d84d71d696c1ae01');
|
||||
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() {
|
||||
|
@ -452,7 +521,7 @@ describe('TxProposal', function() {
|
|||
it('with less signatures', function() {
|
||||
var txp = dummyProposal();
|
||||
var txp1Sig = dummyProposal({
|
||||
onsig: true
|
||||
nsig:1
|
||||
});
|
||||
var backup = txp.builder.vanilla.scriptSig[0];
|
||||
var hasChanged = txp.merge(txp);
|
||||
|
@ -580,15 +649,13 @@ describe('TxProposal', function() {
|
|||
delete txp['creatorTs'];
|
||||
sinon.stub(txp, 'getSignersPubKeys').returns(['pk0', 'pk1']);
|
||||
(function() {
|
||||
txp.setCopayers(
|
||||
{
|
||||
txp.setCopayers({
|
||||
pk0: 'creator',
|
||||
pk1: 'pepe',
|
||||
pk2: 'john'
|
||||
}, {
|
||||
'creator2': 1
|
||||
}
|
||||
);
|
||||
});
|
||||
}).should.throw('only 1');
|
||||
})
|
||||
|
||||
|
|
392
test/Wallet.js
392
test/Wallet.js
|
@ -586,46 +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);
|
||||
//stub.restore();
|
||||
});
|
||||
|
||||
var newId = '00bacacafe';
|
||||
it('handle new connections', function(done) {
|
||||
var w = createW();
|
||||
|
@ -871,8 +831,9 @@ describe('Wallet model', function() {
|
|||
w.blockchain.fixUnspent(utxo);
|
||||
sinon.spy(w, 'sendIndexes');
|
||||
sinon.spy(w, 'sendTxProposal');
|
||||
sinon.spy(w, 'broadcastTx');
|
||||
sinon.spy(w, 'issueTx');
|
||||
sinon.stub(w, 'requiresMultipleSignatures').returns(false);
|
||||
sinon.stub(w.blockchain, 'broadcast').yields(null, 1234);
|
||||
w.spend({
|
||||
toAddress: toAddress,
|
||||
amountSat: amountSatStr,
|
||||
|
@ -880,9 +841,8 @@ describe('Wallet model', function() {
|
|||
should.not.exist(err);
|
||||
should.exist(id);
|
||||
status.should.equal(Wallet.TX_BROADCASTED);
|
||||
w.sendTxProposal.calledOnce.should.equal(true);
|
||||
w.sendIndexes.calledOnce.should.equal(true);
|
||||
w.broadcastTx.calledOnce.should.equal(true);
|
||||
w.blockchain.broadcast.calledOnce.should.equal(true);
|
||||
w.issueTx.calledOnce.should.equal(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -894,7 +854,7 @@ describe('Wallet model', function() {
|
|||
sinon.stub(w, 'requiresMultipleSignatures').returns(false);
|
||||
sinon.spy(w, 'sendIndexes');
|
||||
sinon.spy(w, 'sendTxProposal');
|
||||
sinon.stub(w, '_doBroadcastTx').yields('error');
|
||||
sinon.stub(w, 'broadcastToBitcoinNetwork').yields('error');
|
||||
w.spend({
|
||||
toAddress: toAddress,
|
||||
amountSat: amountSatStr,
|
||||
|
@ -939,25 +899,7 @@ describe('Wallet model', function() {
|
|||
|
||||
|
||||
});
|
||||
describe('#broadcastTx', 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
|
||||
sinon.stub(txp.builder, 'build').returns({
|
||||
isComplete: function() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
(function() {
|
||||
w.broadcastTx(ntxid);
|
||||
}).should.throw('Tx is not complete. Can not broadcast');
|
||||
done();
|
||||
});
|
||||
|
||||
describe('#issueTx', function() {
|
||||
it('should broadcast a TX', function(done) {
|
||||
var w = createW2(null, 1);
|
||||
var utxo = createUTXO(w);
|
||||
|
@ -965,7 +907,7 @@ describe('Wallet model', function() {
|
|||
var ntxid = w.txProposals.add(txp);
|
||||
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);
|
||||
txid.should.equal(1234);
|
||||
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) {
|
||||
var w = createW2(null, 1);
|
||||
var utxo = createUTXO(w);
|
||||
|
@ -992,7 +933,7 @@ describe('Wallet model', function() {
|
|||
sinon.stub(w, 'onPayProPaymentAck');
|
||||
|
||||
|
||||
w.broadcastTx(ntxid, function(err, txid, status) {
|
||||
w.issueTx(ntxid, function(err, txid, status) {
|
||||
should.not.exist(err);
|
||||
txid.should.equal(1234);
|
||||
status.should.equal(Wallet.TX_BROADCASTED);
|
||||
|
@ -1004,6 +945,26 @@ describe('Wallet model', function() {
|
|||
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();
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
@ -1661,20 +1622,20 @@ describe('Wallet model', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe.skip('_onTxProposal', function() {
|
||||
describe('_onTxProposal', function() {
|
||||
var w, data, txp;
|
||||
|
||||
beforeEach(function() {
|
||||
w = cachedCreateW();
|
||||
w._getPubkeyToCopayerMap = sinon.stub();
|
||||
w.sendSeen = sinon.spy();
|
||||
w.sendTxProposal = sinon.spy();
|
||||
data = {
|
||||
txProposal: {
|
||||
dummy: 1,
|
||||
builderObj: {
|
||||
dummy: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
txp = {
|
||||
getId: sinon.stub().returns('ntxid'),
|
||||
getSeen: sinon.stub().returns(false),
|
||||
setSeen: sinon.spy(),
|
||||
setCopayers: sinon.spy(),
|
||||
|
@ -1685,197 +1646,132 @@ describe('Wallet model', function() {
|
|||
},
|
||||
};
|
||||
|
||||
w.txProposals.get = sinon.stub().returns(txp);
|
||||
|
||||
w.txProposals.merge = sinon.stub().returns({
|
||||
ntxid: 1,
|
||||
txp: txp,
|
||||
new: true,
|
||||
hasChanged: true,
|
||||
});
|
||||
sinon.stub(w, '_processIncomingNewTxProposal').yields(null);
|
||||
sinon.stub(w.txProposals, 'get').returns(null);
|
||||
sinon.stub(w.txProposals, 'deleteOne');
|
||||
sinon.stub(w, '_txProposalFromUntrustedObj').returns(txp);
|
||||
sinon.stub(w, '_getPubkeyToCopayerMap');
|
||||
});
|
||||
|
||||
it('should handle corrupt tx', function() {
|
||||
w.txProposals.merge = sinon.stub().throws(new Error('test error'));
|
||||
afterEach(function() {});
|
||||
|
||||
sinon.stub(w, 'on');
|
||||
it('should handle corrupt message', function() {
|
||||
w._txProposalFromUntrustedObj.throws('error');
|
||||
w._onTxProposal('senderID', data);
|
||||
w.on.called.should.equal(false);
|
||||
w._processIncomingNewTxProposal.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, '_getPubkeyToCopayerMap').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, '_getPubkeyToCopayerMap').throws(new Error('test error'));
|
||||
w._onTxProposal('senderID', data);
|
||||
w.on.called.should.equal(false);
|
||||
});
|
||||
it('should handle new 1', function(done) {
|
||||
|
||||
var spy1 = sinon.spy();
|
||||
var spy2 = sinon.spy();
|
||||
w.on('txProposalEvent', spy1);
|
||||
w.on('txProposalsUpdated', spy2);
|
||||
w.on('txProposalEvent', function(e) {
|
||||
e.type.should.equal('new');
|
||||
spy1.called.should.be.true;
|
||||
spy2.called.should.be.true;
|
||||
txp.setSeen.calledOnce.should.be.true;
|
||||
w.sendSeen.calledOnce.should.equal(true);
|
||||
w.sendTxProposal.calledOnce.should.equal(true);
|
||||
done();
|
||||
});
|
||||
|
||||
w._onTxProposal('senderID', data);
|
||||
});
|
||||
|
||||
it('should handle signed', function(done) {
|
||||
var data = {
|
||||
txProposal: {
|
||||
dummy: 1,
|
||||
},
|
||||
};
|
||||
var txp = {
|
||||
getSeen: sinon.stub().returns(true),
|
||||
setSeen: sinon.spy(),
|
||||
setCopayers: sinon.stub().returns(['new copayer']),
|
||||
builder: {
|
||||
build: sinon.stub().returns({
|
||||
isComplete: sinon.stub().returns(false),
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
it('should ignore localTx', function() {
|
||||
w.txProposals.get = sinon.stub().returns(txp);
|
||||
w.txProposals.merge = sinon.stub().returns({
|
||||
ntxid: 1,
|
||||
txp: txp,
|
||||
new: false,
|
||||
hasChanged: true,
|
||||
w._txProposalFromUntrustedObj.throws('error');
|
||||
w._onTxProposal('senderID', data);
|
||||
w._processIncomingNewTxProposal.called.should.equal(false);
|
||||
});
|
||||
|
||||
var spy1 = sinon.spy();
|
||||
var spy2 = sinon.spy();
|
||||
w.on('txProposalEvent', spy1);
|
||||
w.on('txProposalsUpdated', spy2);
|
||||
it('should accept a new valid TXP', function(done) {
|
||||
w.txProposals.get = sinon.stub().returns(null);
|
||||
w.on('txProposalEvent', function(e) {
|
||||
e.type.should.equal('new');
|
||||
w._processIncomingNewTxProposal.called.should.equal(true);
|
||||
w._getPubkeyToCopayerMap.called.should.equal(true);
|
||||
done();
|
||||
})
|
||||
w._onTxProposal('senderID', data);
|
||||
});
|
||||
|
||||
|
||||
it('should ignore is a TXP arrived 2 times', function(done) {
|
||||
w.txProposals.get = sinon.stub().returns(null);
|
||||
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 = {
|
||||
'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.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();
|
||||
|
||||
w.on('txProposalEvent', function(e) {
|
||||
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();
|
||||
})
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue