2014-07-30 21:19:39 -03:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
var bitcore = require('bitcore');
|
|
|
|
|
var util = bitcore.util;
|
|
|
|
|
var Transaction = bitcore.Transaction;
|
|
|
|
|
var BuilderMockV0 = require('./BuilderMockV0');;
|
|
|
|
|
var TransactionBuilder = bitcore.TransactionBuilder;
|
|
|
|
|
var Script = bitcore.Script;
|
|
|
|
|
var Key = bitcore.Key;
|
|
|
|
|
var buffertools = bitcore.buffertools;
|
|
|
|
|
var preconditions = require('preconditions').instance();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function TxProposal(opts) {
|
|
|
|
|
preconditions.checkArgument(opts);
|
|
|
|
|
preconditions.checkArgument(opts.inputChainPaths);
|
|
|
|
|
preconditions.checkArgument(opts.creator);
|
|
|
|
|
preconditions.checkArgument(opts.createdTs);
|
|
|
|
|
preconditions.checkArgument(opts.builder);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.creator = opts.creator;
|
|
|
|
|
this.createdTs = opts.createdTs;
|
|
|
|
|
this.builder = opts.builder;
|
|
|
|
|
this.inputChainPaths = opts.inputChainPaths;
|
|
|
|
|
|
2014-07-31 16:04:41 -03:00
|
|
|
this._inputSignatures = [];
|
2014-07-30 21:19:39 -03:00
|
|
|
this.seenBy = opts.seenBy || {};
|
|
|
|
|
this.signedBy = opts.signedBy || {};
|
|
|
|
|
this.rejectedBy = opts.rejectedBy || {};
|
|
|
|
|
this.sentTs = opts.sentTs || null;
|
|
|
|
|
this.sentTxid = opts.sentTxid || null;
|
|
|
|
|
this.comment = opts.comment || null;
|
|
|
|
|
this.readonly = opts.readonly || null;
|
2014-07-31 10:42:09 -03:00
|
|
|
// this._updateSignedBy();
|
2014-07-30 21:19:39 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TxProposal.prototype.getId = function() {
|
|
|
|
|
return this.builder.build().getNormalizedHash().toString('hex');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
TxProposal.prototype.toObj = function() {
|
|
|
|
|
var o = JSON.parse(JSON.stringify(this));
|
|
|
|
|
delete o['builder'];
|
|
|
|
|
o.builderObj = this.builder.toObj();
|
|
|
|
|
return o;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TxProposal.prototype.setSent = function(sentTxid) {
|
|
|
|
|
this.sentTxid = sentTxid;
|
|
|
|
|
this.sentTs = Date.now();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
TxProposal.fromObj = function(o, forceOpts) {
|
|
|
|
|
preconditions.checkArgument(o.builderObj);
|
|
|
|
|
delete o['builder'];
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// force opts is requested.
|
|
|
|
|
for (var k in forceOpts) {
|
|
|
|
|
o.builderObj.opts[k] = forceOpts[k];
|
|
|
|
|
}
|
|
|
|
|
o.builder = TransactionBuilder.fromObj(o.builderObj);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (!o.version) {
|
|
|
|
|
o.builder = new BuilderMockV0(o.builderObj);
|
|
|
|
|
o.readonly = 1;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
var t = new TxProposal(o);
|
2014-07-31 16:04:41 -03:00
|
|
|
t._check();
|
2014-07-30 21:19:39 -03:00
|
|
|
t._updateSignedBy();
|
|
|
|
|
return t;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-07-31 16:04:41 -03:00
|
|
|
TxProposal._formatKeys = function(keys) {
|
|
|
|
|
var ret = [];
|
|
|
|
|
for (var i in keys) {
|
|
|
|
|
if (!Buffer.isBuffer(keys[i]))
|
|
|
|
|
throw new Error('keys must be buffers');
|
2014-07-30 21:19:39 -03:00
|
|
|
|
|
|
|
|
var k = new Key();
|
2014-07-31 16:04:41 -03:00
|
|
|
k.public = keys[i];
|
|
|
|
|
ret.push(k);
|
2014-07-30 21:19:39 -03:00
|
|
|
};
|
2014-07-31 16:04:41 -03:00
|
|
|
return ret;
|
2014-07-30 21:19:39 -03:00
|
|
|
};
|
|
|
|
|
|
2014-07-31 10:50:00 -03:00
|
|
|
TxProposal._verifySignatures = function(inKeys, scriptSig, txSigHash) {
|
2014-07-30 21:19:39 -03:00
|
|
|
preconditions.checkArgument(Buffer.isBuffer(txSigHash));
|
2014-07-31 10:42:09 -03:00
|
|
|
preconditions.checkArgument(inKeys);
|
|
|
|
|
preconditions.checkState(Buffer.isBuffer(inKeys[0]));
|
2014-07-30 21:19:39 -03:00
|
|
|
|
|
|
|
|
if (scriptSig.chunks[0] !== 0)
|
|
|
|
|
throw new Error('Invalid scriptSig');
|
2014-07-31 23:32:24 -03:00
|
|
|
|
2014-07-31 10:45:50 -03:00
|
|
|
var keys = TxProposal._formatKeys(inKeys);
|
2014-07-30 21:19:39 -03:00
|
|
|
var ret = [];
|
2014-07-31 10:42:09 -03:00
|
|
|
for (var i = 1; i <= scriptSig.countSignatures(); i++) {
|
2014-07-30 21:19:39 -03:00
|
|
|
var chunk = scriptSig.chunks[i];
|
|
|
|
|
var sigRaw = new Buffer(chunk.slice(0, chunk.length - 1));
|
|
|
|
|
for (var j in keys) {
|
|
|
|
|
var k = keys[j];
|
|
|
|
|
if (k.verifySignatureSync(txSigHash, sigRaw)) {
|
2014-07-31 10:42:09 -03:00
|
|
|
ret.push(parseInt(j));
|
|
|
|
|
break;
|
2014-07-31 16:04:41 -03:00
|
|
|
}
|
2014-07-30 21:19:39 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
};
|
|
|
|
|
|
2014-07-31 16:04:41 -03:00
|
|
|
TxProposal._infoFromRedeemScript = function(s) {
|
2014-07-31 10:42:09 -03:00
|
|
|
var redeemScript = new Script(s.chunks[s.chunks.length - 1]);
|
2014-07-30 21:19:39 -03:00
|
|
|
if (!redeemScript)
|
|
|
|
|
throw new Error('Bad scriptSig');
|
2014-07-31 23:32:24 -03:00
|
|
|
|
2014-07-30 21:19:39 -03:00
|
|
|
var pubkeys = redeemScript.capture();
|
|
|
|
|
if (!pubkeys || !pubkeys.length)
|
|
|
|
|
throw new Error('Bad scriptSig');
|
|
|
|
|
|
2014-07-31 16:04:41 -03:00
|
|
|
return {
|
|
|
|
|
keys: pubkeys,
|
2014-07-31 23:32:24 -03:00
|
|
|
script: redeemScript,
|
2014-07-31 16:04:41 -03:00
|
|
|
};
|
2014-07-30 21:19:39 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
TxProposal.prototype._updateSignedBy = function() {
|
2014-07-31 16:04:41 -03:00
|
|
|
this._inputSignatures = [];
|
2014-07-30 21:19:39 -03:00
|
|
|
|
|
|
|
|
var tx = this.builder.build();
|
|
|
|
|
for (var i in tx.ins) {
|
|
|
|
|
var scriptSig = new Script(tx.ins[i].s);
|
2014-07-31 16:04:41 -03:00
|
|
|
var signatureCount = scriptSig.countSignatures();
|
|
|
|
|
var info = TxProposal._infoFromRedeemScript(scriptSig);
|
2014-07-31 23:32:24 -03:00
|
|
|
var txSigHash = tx.hashForSignature(info.script, parseInt(i), Transaction.SIGHASH_ALL);
|
2014-07-31 16:04:41 -03:00
|
|
|
var signatureIndexes = TxProposal._verifySignatures(info.keys, scriptSig, txSigHash);
|
|
|
|
|
if (signatureIndexes.length !== signatureCount)
|
2014-07-30 21:19:39 -03:00
|
|
|
throw new Error('Invalid signature');
|
2014-07-31 16:04:41 -03:00
|
|
|
this._inputSignatures[i] = signatureIndexes.map(function(i) {
|
|
|
|
|
return info.keys[i].toString('hex');
|
|
|
|
|
});
|
2014-07-30 21:19:39 -03:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2014-07-31 16:04:41 -03:00
|
|
|
TxProposal.prototype._check = function() {
|
2014-07-30 21:19:39 -03:00
|
|
|
|
|
|
|
|
if (this.builder.signhash && this.builder.signhash !== Transaction.SIGHASH_ALL) {
|
2014-07-31 16:04:41 -03:00
|
|
|
throw new Error('Invalid tx proposal');
|
2014-07-30 21:19:39 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var tx = this.builder.build();
|
|
|
|
|
if (!tx.ins.length)
|
2014-07-31 16:04:41 -03:00
|
|
|
throw new Error('Invalid tx proposal: no ins');
|
|
|
|
|
|
2014-07-31 23:32:24 -03:00
|
|
|
var scriptSig = this.builder.vanilla.scriptSig;
|
|
|
|
|
if (!scriptSig || !scriptSig.length) {
|
2014-07-31 16:04:41 -03:00
|
|
|
throw new Error('Invalid tx proposal: no signatures');
|
|
|
|
|
}
|
2014-07-30 21:19:39 -03:00
|
|
|
|
|
|
|
|
for (var i = 0; i < tx.ins.length; i++) {
|
|
|
|
|
var hashType = tx.getHashType(i);
|
2014-07-31 16:04:41 -03:00
|
|
|
if (hashType && hashType !== Transaction.SIGHASH_ALL)
|
|
|
|
|
throw new Error('Invalid tx proposal: bad signatures');
|
2014-07-30 21:19:39 -03:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2014-07-31 16:04:41 -03:00
|
|
|
TxProposal.prototype.mergeBuilder = function(incoming) {
|
|
|
|
|
var b0 = this.builder;
|
|
|
|
|
var b1 = incoming.builder;
|
2014-07-30 21:19:39 -03:00
|
|
|
|
2014-07-31 16:04:41 -03:00
|
|
|
var before = JSON.stringify(b0.toObj());
|
|
|
|
|
b0.merge(b1);
|
|
|
|
|
var after = JSON.stringify(b0.toObj());
|
|
|
|
|
return after !== before;
|
|
|
|
|
};
|
2014-07-30 21:19:39 -03:00
|
|
|
|
|
|
|
|
|
2014-07-31 16:04:41 -03:00
|
|
|
/* OTDO
|
|
|
|
|
events.push({
|
2014-07-30 21:19:39 -03:00
|
|
|
type: 'seen',
|
|
|
|
|
cId: k,
|
|
|
|
|
txId: ntxid
|
|
|
|
|
});
|
|
|
|
|
events.push({
|
|
|
|
|
type: 'signed',
|
|
|
|
|
cId: k,
|
|
|
|
|
txId: ntxid
|
|
|
|
|
});
|
|
|
|
|
events.push({
|
|
|
|
|
type: 'rejected',
|
|
|
|
|
cId: k,
|
|
|
|
|
txId: ntxid
|
|
|
|
|
});
|
|
|
|
|
ret.events = this.mergeMetadata(incoming);
|
|
|
|
|
*/
|
2014-07-31 16:04:41 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
TxProposal.prototype._allSignatures = function() {
|
|
|
|
|
var ret = {};
|
|
|
|
|
for(var i in this._inputSignatures)
|
|
|
|
|
for (var j in this._inputSignatures[i])
|
|
|
|
|
ret[this._inputSignatures[i][j]] = true;
|
|
|
|
|
|
2014-07-30 21:19:39 -03:00
|
|
|
return ret;
|
|
|
|
|
};
|
|
|
|
|
|
2014-07-31 16:04:41 -03:00
|
|
|
TxProposal.prototype.merge = function(incoming) {
|
|
|
|
|
var ret = {};
|
|
|
|
|
var newSignatures = [];
|
2014-07-30 21:19:39 -03:00
|
|
|
|
2014-07-31 16:04:41 -03:00
|
|
|
incoming._check();
|
|
|
|
|
incoming._updateSignedBy();
|
|
|
|
|
|
|
|
|
|
var prevInputSignatures = this._allSignatures();
|
|
|
|
|
|
|
|
|
|
ret.hasChanged = this.mergeBuilder(incoming);
|
|
|
|
|
this._updateSignedBy();
|
2014-07-30 21:19:39 -03:00
|
|
|
|
2014-07-31 16:04:41 -03:00
|
|
|
if (ret.hasChanged)
|
|
|
|
|
for(var i in this._inputSignatures)
|
|
|
|
|
for (var j in this._inputSignatures[i])
|
|
|
|
|
if (!prevInputSignatures[this._inputSignatures[i][j]])
|
|
|
|
|
newSignatures.push(this._inputSignatures[i][j]);
|
|
|
|
|
|
|
|
|
|
ret.newSignatures = newSignatures;
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
};
|
2014-07-30 21:19:39 -03:00
|
|
|
|
|
|
|
|
//This should be on bitcore / Transaction
|
|
|
|
|
TxProposal.prototype.countSignatures = function() {
|
|
|
|
|
var tx = this.builder.build();
|
|
|
|
|
var ret = 0;
|
|
|
|
|
for (var i in tx.ins) {
|
|
|
|
|
ret += tx.countInputSignatures(i);
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
};
|
2014-07-31 16:04:41 -03:00
|
|
|
|
2014-07-30 21:19:39 -03:00
|
|
|
module.exports = TxProposal;
|