You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
605 lines
17 KiB
605 lines
17 KiB
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/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.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;
|
|
};
|
|
this.class = Transaction;
|
|
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;
|
|
};
|
|
|
|
// 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()
|
|
}
|
|
};
|
|
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 getHash() {
|
|
if (!this.size) {
|
|
this.size = this.calcSize();
|
|
}
|
|
return this.size;
|
|
};
|
|
|
|
|
|
Transaction.prototype.countInputMissingSignatures = function(index) {
|
|
var ret = 0;
|
|
var script = new Script(this.ins[index].s);
|
|
return script.countMissingSignatures();
|
|
};
|
|
|
|
|
|
Transaction.prototype.isInputComplete = function(index) {
|
|
return this.countInputMissingSignatures(index)===0;
|
|
};
|
|
|
|
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);
|
|
|