flocore/lib/Transaction.js
Ryan X. Charles 7f348ca0bd Move BigNumber to internal
...no longer relies on Manuel's repo hostig a version of "bignum" that actually
contained bignumber.js. This moves bignumber.js internally and removes bignum
from the browser build process. Also adds a bitcore.Bignum that links to the
right thing. In node, browser.Bignum is require('bignum'). And in the browser,
bitcore.Bignum is is now Bignumber.js (same as before ... except bignumber.js
is now inside bitcore).
2014-04-28 18:00:59 -04:00

632 lines
17 KiB
JavaScript

var imports = require('soop').imports();
var config = imports.config || require('../config');
var log = imports.log || require('../util/log');
var Address = imports.Address || require('./Address');
var Script = imports.Script || require('./Script');
var ScriptInterpreter = imports.ScriptInterpreter || require('./ScriptInterpreter');
var util = imports.util || require('../util');
var bignum = imports.bignum || require('./Bignum');
var Put = imports.Put || require('bufferput');
var Parser = imports.Parser || require('../util/BinaryParser');
var Step = imports.Step || require('step');
var buffertools = imports.buffertools || require('buffertools');
var error = imports.error || require('../util/error');
var networks = imports.networks || require('../networks');
var WalletKey = imports.WalletKey || require('./WalletKey');
var PrivateKey = imports.PrivateKey || require('./PrivateKey');
var COINBASE_OP = Buffer.concat([util.NULL_HASH, new Buffer('FFFFFFFF', 'hex')]);
var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN);
Transaction.COINBASE_OP = COINBASE_OP;
function TransactionIn(data) {
if ("object" !== typeof data) {
data = {};
}
if (data.o) {
this.o = data.o;
} else {
if (data.oTxHash && typeof data.oIndex !== 'undefined' && data.oIndex >= 0) {
var hash = new Buffer(data.oTxHash, 'hex');
hash = buffertools.reverse(hash);
var voutBuf = new Buffer(4);
voutBuf.writeUInt32LE(data.oIndex, 0);
this.o = Buffer.concat([hash, voutBuf]);
}
}
this.s = Buffer.isBuffer(data.s) ? data.s :
Buffer.isBuffer(data.script) ? data.script : util.EMPTY_BUFFER;
this.q = data.q ? data.q : data.sequence;
}
TransactionIn.MAX_SEQUENCE = 0xffffffff;
TransactionIn.prototype.getScript = function getScript() {
return new Script(this.s);
};
TransactionIn.prototype.isCoinBase = function isCoinBase() {
if (!this.o) return false;
//The new Buffer is for Firefox compatibility
return buffertools.compare(new Buffer(this.o), COINBASE_OP) === 0;
};
TransactionIn.prototype.serialize = function serialize() {
var slen = util.varIntBuf(this.s.length);
var qbuf = new Buffer(4);
qbuf.writeUInt32LE(this.q, 0);
var ret = Buffer.concat([this.o, slen, this.s, qbuf]);
return ret;
};
TransactionIn.prototype.getOutpointHash = function getOutpointHash() {
if ("undefined" !== typeof this.o.outHashCache) {
return this.o.outHashCache;
}
return this.o.outHashCache = this.o.slice(0, 32);
};
TransactionIn.prototype.getOutpointIndex = function getOutpointIndex() {
return (this.o[32]) +
(this.o[33] << 8) +
(this.o[34] << 16) +
(this.o[35] << 24);
};
TransactionIn.prototype.setOutpointIndex = function setOutpointIndex(n) {
this.o[32] = n & 0xff;
this.o[33] = n >> 8 & 0xff;
this.o[34] = n >> 16 & 0xff;
this.o[35] = n >> 24 & 0xff;
};
function TransactionOut(data) {
if ("object" !== typeof data) {
data = {};
}
this.v = data.v ? data.v : data.value;
this.s = data.s ? data.s : data.script;
};
TransactionOut.prototype.getValue = function getValue() {
return new Parser(this.v).word64lu();
};
TransactionOut.prototype.getScript = function getScript() {
return new Script(this.s);
};
TransactionOut.prototype.serialize = function serialize() {
var slen = util.varIntBuf(this.s.length);
return Buffer.concat([this.v, slen, this.s]);
};
function Transaction(data) {
if ("object" !== typeof data) {
data = {};
}
this.hash = data.hash || null;
this.version = data.version;
this.lock_time = data.lock_time;
this.ins = Array.isArray(data.ins) ? data.ins.map(function(data) {
var txin = new TransactionIn();
txin.s = data.s;
txin.q = data.q;
txin.o = data.o;
return txin;
}) : [];
this.outs = Array.isArray(data.outs) ? data.outs.map(function(data) {
var txout = new TransactionOut();
txout.v = data.v;
txout.s = data.s;
return txout;
}) : [];
if (data.buffer) this._buffer = data.buffer;
};
Transaction.In = TransactionIn;
Transaction.Out = TransactionOut;
Transaction.prototype.isCoinBase = function() {
return this.ins.length == 1 && this.ins[0].isCoinBase();
};
Transaction.prototype.isStandard = function isStandard() {
var i;
for (i = 0; i < this.ins.length; i++) {
if (this.ins[i].getScript().getInType() == "Strange") {
return false;
}
}
for (i = 0; i < this.outs.length; i++) {
if (this.outs[i].getScript().getOutType() == "Strange") {
return false;
}
}
return true;
};
Transaction.prototype.serialize = function serialize() {
var bufs = [];
var buf = new Buffer(4);
buf.writeUInt32LE(this.version, 0);
bufs.push(buf);
bufs.push(util.varIntBuf(this.ins.length));
this.ins.forEach(function(txin) {
bufs.push(txin.serialize());
});
bufs.push(util.varIntBuf(this.outs.length));
this.outs.forEach(function(txout) {
bufs.push(txout.serialize());
});
var buf = new Buffer(4);
buf.writeUInt32LE(this.lock_time, 0);
bufs.push(buf);
this._buffer = Buffer.concat(bufs);
return this._buffer;
};
Transaction.prototype.getBuffer = function getBuffer() {
if (this._buffer) return this._buffer;
return this.serialize();
};
Transaction.prototype.calcHash = function calcHash() {
this.hash = util.twoSha256(this.getBuffer());
return this.hash;
};
Transaction.prototype.checkHash = function checkHash() {
if (!this.hash || !this.hash.length) return false;
return buffertools.compare(this.calcHash(), this.hash) === 0;
};
Transaction.prototype.getHash = function getHash() {
if (!this.hash || !this.hash.length) {
this.hash = this.calcHash();
}
return this.hash;
};
Transaction.prototype.calcNormalizedHash = function () {
this.normalizedHash = this.hashForSignature(new Script(),0, SIGHASH_ALL);
return this.normalizedHash;
};
Transaction.prototype.getNormalizedHash = function () {
if (!this.normalizedHash || !this.normalizedHash.length) {
this.normalizedHash = this.calcNormalizedHash();
}
return this.normalizedHash;
};
// convert encoded list of inputs to easy-to-use JS list-of-lists
Transaction.prototype.inputs = function inputs() {
var res = [];
for (var i = 0; i < this.ins.length; i++) {
var txin = this.ins[i];
var outHash = txin.getOutpointHash();
var outIndex = txin.getOutpointIndex();
res.push([outHash, outIndex]);
}
return res;
};
Transaction.prototype.verifyInput = function verifyInput(n, scriptPubKey, opts, callback) {
var scriptSig = this.ins[n].getScript();
return ScriptInterpreter.verifyFull(
scriptSig,
scriptPubKey,
this, n, 0,
opts,
callback);
};
/**
* Returns an object containing all pubkey hashes affected by this transaction.
*
* The return object contains the base64-encoded pubKeyHash values as keys
* and the original pubKeyHash buffers as values.
*/
Transaction.prototype.getAffectedKeys = function getAffectedKeys(txCache) {
// TODO: Function won't consider results cached if there are no affected
// accounts.
if (!(this.affects && this.affects.length)) {
this.affects = [];
// Index any pubkeys affected by the outputs of this transaction
for (var i = 0, l = this.outs.length; i < l; i++) {
var txout = this.outs[i];
var script = txout.getScript();
var outPubKey = script.simpleOutPubKeyHash();
if (outPubKey) {
this.affects.push(outPubKey);
}
};
// Index any pubkeys affected by the inputs of this transaction
var txIndex = txCache.txIndex;
for (var i = 0, l = this.ins.length; i < l; i++) {
var txin = this.ins[i];
if (txin.isCoinBase()) continue;
// In the case of coinbase or IP transactions, the txin doesn't
// actually contain the pubkey, so we look at the referenced txout
// instead.
var outHash = txin.getOutpointHash();
var outIndex = txin.getOutpointIndex();
var outHashBase64 = outHash.toString('base64');
var fromTxOuts = txIndex[outHashBase64];
if (!fromTxOuts) {
throw new Error("Input not found!");
}
var txout = fromTxOuts[outIndex];
var script = txout.getScript();
var outPubKey = script.simpleOutPubKeyHash();
if (outPubKey) {
this.affects.push(outPubKey);
}
}
}
var affectedKeys = {};
this.affects.forEach(function(pubKeyHash) {
affectedKeys[pubKeyHash.toString('base64')] = pubKeyHash;
});
return affectedKeys;
};
var OP_CODESEPARATOR = 171;
var SIGHASH_ALL = 1;
var SIGHASH_NONE = 2;
var SIGHASH_SINGLE = 3;
var SIGHASH_ANYONECANPAY = 0x80;
Transaction.SIGHASH_ALL = SIGHASH_ALL;
Transaction.SIGHASH_NONE = SIGHASH_NONE;
Transaction.SIGHASH_SINGLE = SIGHASH_SINGLE;
Transaction.SIGHASH_ANYONECANPAY = SIGHASH_ANYONECANPAY;
var TransactionSignatureSerializer = function(txTo, scriptCode, nIn, nHashType) {
this.txTo = txTo;
this.scriptCode = scriptCode;
this.nIn = nIn;
this.anyoneCanPay = !!(nHashType & SIGHASH_ANYONECANPAY);
var hashTypeMode = nHashType & 0x1f;
this.hashSingle = hashTypeMode === SIGHASH_SINGLE;
this.hashNone = hashTypeMode === SIGHASH_NONE;
this.bytes = new Put();
};
// serialize an output of txTo
TransactionSignatureSerializer.prototype.serializeOutput = function(nOutput) {
if (this.hashSingle && nOutput != this.nIn) {
// Do not lock-in the txout payee at other indices as txin
// ::Serialize(s, CTxOut(), nType, nVersion);
this.bytes.put(util.INT64_MAX);
this.bytes.varint(0);
} else {
//::Serialize(s, txTo.vout[nOutput], nType, nVersion);
var out = this.txTo.outs[nOutput];
this.bytes.put(out.v);
this.bytes.varint(out.s.length);
this.bytes.put(out.s);
}
};
// serialize the script
TransactionSignatureSerializer.prototype.serializeScriptCode = function() {
this.scriptCode.findAndDelete(OP_CODESEPARATOR);
this.bytes.varint(this.scriptCode.buffer.length);
this.bytes.put(this.scriptCode.buffer);
};
// serialize an input of txTo
TransactionSignatureSerializer.prototype.serializeInput = function(nInput) {
// In case of SIGHASH_ANYONECANPAY, only the input being signed is serialized
if (this.anyoneCanPay) nInput = this.nIn;
// Serialize the prevout
this.bytes.put(this.txTo.ins[nInput].o);
// Serialize the script
if (nInput !== this.nIn) {
// Blank out other inputs' signatures
this.bytes.varint(0);
} else {
this.serializeScriptCode();
}
// Serialize the nSequence
if (nInput !== this.nIn && (this.hashSingle || this.hashNone)) {
// let the others update at will
this.bytes.word32le(0);
} else {
this.bytes.word32le(this.txTo.ins[nInput].q);
}
};
// serialize txTo for signature
TransactionSignatureSerializer.prototype.serialize = function() {
// serialize nVersion
this.bytes.word32le(this.txTo.version);
// serialize vin
var nInputs = this.anyoneCanPay ? 1 : this.txTo.ins.length;
this.bytes.varint(nInputs);
for (var nInput = 0; nInput < nInputs; nInput++) {
this.serializeInput(nInput);
}
// serialize vout
var nOutputs = this.hashNone ? 0 : (this.hashSingle ? this.nIn + 1 : this.txTo.outs.length);
this.bytes.varint(nOutputs);
for (var nOutput = 0; nOutput < nOutputs; nOutput++) {
this.serializeOutput(nOutput);
}
// serialize nLockTime
this.bytes.word32le(this.txTo.lock_time);
};
TransactionSignatureSerializer.prototype.buffer = function() {
this.serialize();
return this.bytes.buffer();
};
Transaction.Serializer = TransactionSignatureSerializer;
var oneBuffer = function() {
// bug present in bitcoind which must be also present in bitcore
// see https://bitcointalk.org/index.php?topic=260595
var ret = new Buffer(32);
ret.writeUInt8(1, 0);
for (var i=1; i<32; i++) ret.writeUInt8(0, i);
return ret; // return 1 bug
};
Transaction.prototype.hashForSignature =
function hashForSignature(script, inIndex, hashType) {
if (+inIndex !== inIndex ||
inIndex < 0 || inIndex >= this.ins.length) {
return oneBuffer();
}
// Check for invalid use of SIGHASH_SINGLE
var hashTypeMode = hashType & 0x1f;
if (hashTypeMode === SIGHASH_SINGLE) {
if (inIndex >= this.outs.length) {
return oneBuffer();
}
}
// Wrapper to serialize only the necessary parts of the transaction being signed
var serializer = new TransactionSignatureSerializer(this, script, inIndex, hashType);
// Serialize
var buffer = serializer.buffer();
// Append hashType
var hashBuf = new Put().word32le(hashType).buffer();
buffer = Buffer.concat([buffer, hashBuf]);
return util.twoSha256(buffer);
};
/**
* Returns an object with the same field names as jgarzik's getblock patch.
*/
Transaction.prototype.getStandardizedObject = function getStandardizedObject() {
var tx = {
hash: util.formatHashFull(this.getHash()),
version: this.version,
lock_time: this.lock_time
};
var totalSize = 8; // version + lock_time
totalSize += util.getVarIntSize(this.ins.length); // tx_in count
var ins = this.ins.map(function(txin) {
var txinObj = {
prev_out: {
hash: buffertools.reverse(new Buffer(txin.getOutpointHash())).toString('hex'),
n: txin.getOutpointIndex()
},
sequence: txin.q
};
if (txin.isCoinBase()) {
txinObj.coinbase = txin.s.toString('hex');
} else {
txinObj.scriptSig = new Script(txin.s).getStringContent(false, 0);
}
totalSize += 36 + util.getVarIntSize(txin.s.length) +
txin.s.length + 4; // outpoint + script_len + script + sequence
return txinObj;
});
totalSize += util.getVarIntSize(this.outs.length);
var outs = this.outs.map(function(txout) {
totalSize += util.getVarIntSize(txout.s.length) +
txout.s.length + 8; // script_len + script + value
return {
value: util.formatValue(txout.v),
scriptPubKey: new Script(txout.s).getStringContent(false, 0)
};
});
tx.size = totalSize;
tx["in"] = ins;
tx["out"] = outs;
return tx;
};
// Add some Mongoose compatibility functions to the plain object
Transaction.prototype.toObject = function toObject() {
return this;
};
Transaction.prototype.fromObj = function fromObj(obj) {
var txobj = {};
txobj.version = obj.version || 1;
txobj.lock_time = obj.lock_time || 0;
txobj.ins = [];
txobj.outs = [];
obj.inputs.forEach(function(inputobj) {
var txin = new TransactionIn();
txin.s = util.EMPTY_BUFFER;
txin.q = 0xffffffff;
var hash = new Buffer(inputobj.txid, 'hex');
hash = buffertools.reverse(hash);
var vout = parseInt(inputobj.vout);
var voutBuf = new Buffer(4);
voutBuf.writeUInt32LE(vout, 0);
txin.o = Buffer.concat([hash, voutBuf]);
txobj.ins.push(txin);
});
var keys = Object.keys(obj.outputs);
keys.forEach(function(addrStr) {
var addr = new Address(addrStr);
var script = Script.createPubKeyHashOut(addr.payload());
var valueNum = bignum(obj.outputs[addrStr]);
var value = util.bigIntToValue(valueNum);
var txout = new TransactionOut();
txout.v = value;
txout.s = script.getBuffer();
txobj.outs.push(txout);
});
this.lock_time = txobj.lock_time;
this.version = txobj.version;
this.ins = txobj.ins;
this.outs = txobj.outs;
}
Transaction.prototype.parse = function(parser) {
if (Buffer.isBuffer(parser)) {
this._buffer = parser;
parser = new Parser(parser);
}
var i, sLen, startPos = parser.pos;
this.version = parser.word32le();
var txinCount = parser.varInt();
this.ins = [];
for (j = 0; j < txinCount; j++) {
var txin = new TransactionIn();
txin.o = parser.buffer(36); // outpoint
sLen = parser.varInt(); // script_len
txin.s = parser.buffer(sLen); // script
txin.q = parser.word32le(); // sequence
this.ins.push(txin);
}
var txoutCount = parser.varInt();
this.outs = [];
for (j = 0; j < txoutCount; j++) {
var txout = new TransactionOut();
txout.v = parser.buffer(8); // value
sLen = parser.varInt(); // script_len
txout.s = parser.buffer(sLen); // script
this.outs.push(txout);
}
this.lock_time = parser.word32le();
this.calcHash();
};
Transaction.prototype.calcSize = function() {
var totalSize = 8; // version + lock_time
totalSize += util.getVarIntSize(this.ins.length); // tx_in count
this.ins.forEach(function(txin) {
totalSize += 36 + util.getVarIntSize(txin.s.length) +
txin.s.length + 4; // outpoint + script_len + script + sequence
});
totalSize += util.getVarIntSize(this.outs.length);
this.outs.forEach(function(txout) {
totalSize += util.getVarIntSize(txout.s.length) +
txout.s.length + 8; // script_len + script + value
});
this.size = totalSize;
return totalSize;
};
Transaction.prototype.getSize = function () {
if (!this.size) {
this.size = this.calcSize();
}
return this.size;
};
Transaction.prototype.countInputSignatures = function(index) {
var ret = 0;
var script = new Script(this.ins[index].s);
return script.countSignatures();
};
// Works on p2pubkey, p2pubkeyhash & p2sh (no normal multisig)
Transaction.prototype.countInputMissingSignatures = function(index) {
var ret = 0;
var script = new Script(this.ins[index].s);
return script.countMissingSignatures();
};
// Works on p2pubkey, p2pubkeyhash & p2sh (no normal multisig)
Transaction.prototype.isInputComplete = function(index) {
var m = this.countInputMissingSignatures(index);
if (m===null) return null;
return m === 0;
};
// Works on p2pubkey, p2pubkeyhash & p2sh (no normal multisig)
Transaction.prototype.isComplete = function() {
var ret = true;
var l = this.ins.length;
for (var i = 0; i < l; i++) {
if (!this.isInputComplete(i)){
ret = false;
break;
}
}
return ret;
};
module.exports = require('soop')(Transaction);