UNPKG

@node-dlc/bitcoin

Version:
408 lines 14.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Tx = void 0; const bufio_1 = require("@node-dlc/bufio"); const bufio_2 = require("@node-dlc/bufio"); const crypto_1 = require("@node-dlc/crypto"); const HashByteOrder_1 = require("./HashByteOrder"); const HashValue_1 = require("./HashValue"); const LockTime_1 = require("./LockTime"); const OutPoint_1 = require("./OutPoint"); const Script_1 = require("./Script"); const Sequence_1 = require("./Sequence"); const TxIn_1 = require("./TxIn"); const TxOut_1 = require("./TxOut"); const Value_1 = require("./Value"); const Witness_1 = require("./Witness"); /** * This class is an immutable Bitcoin transaction. This class is used * as a data container from parsed blocks, RPC, or other sources. To use * a mutable transaction, you should use `TxBuilder` class. */ class Tx { /** * Decodes a `Tx` stream similar to Bitcoin Core's DecodeTx method * in that it will first try to parse with SegWit markers enabled. * If there is an error (such as with a base transaction with no * inputs), then it will try parsing using the legacy method. * @param reader */ static decode(reader) { const data = reader.readBytes(); try { return Tx.parse(bufio_1.StreamReader.fromBuffer(data), true); } catch (ex) { return Tx.parse(bufio_1.StreamReader.fromBuffer(data), false); } } /** * Parses a transaction from its byte format in a stream. Capable of * parsing both legacy and segwit transactions. This method is * similar to Bitcoin Core's `UnserializeTransaction` on the * `Transaction` type. This method is expected to throw if witness * is enabled and we have an ambiguous base transaction (zero inputs). * @param reader */ static parse(reader, allowWitness) { // Read the version const version = reader.readUInt32LE(); // Try reading inputs. If this is segwit or a base/dummy, we get // an empty array let vins = Tx.parseInputs(reader); let vouts; let flags = 0; // If witness is allowed and we had an empty input array we // will try parsing a normal witness transaction. This may throw // if this is a base transaction. if (allowWitness && vins.length === 0) { flags = reader.readUInt8(); if (flags !== 0) { vins = Tx.parseInputs(reader); vouts = Tx.parseOutputs(reader); } } // Otherwise, we had success reading inputs and can move along // and parse the outputs! else { vouts = Tx.parseOutputs(reader); } // If we have witness and read a flag, then we we need to // process the witness for each input. if (allowWitness && flags & 1) { for (let i = 0; i < vins.length; i++) { const items = Number(reader.readVarInt()); for (let item = 0; item < items; item++) { vins[i].witness.push(Witness_1.Witness.parse(reader)); } } } // Finally read the locktime const locktime = LockTime_1.LockTime.parse(reader); return new Tx(version, vins, vouts, locktime); } /** * Parses the inputs for a transaction * @param reader * @returns */ static parseInputs(reader) { const vinLen = Number(reader.readVarInt()); const inputs = []; for (let idx = 0; idx < vinLen; idx++) { inputs.push(new TxIn_1.TxIn(OutPoint_1.OutPoint.parse(reader), Script_1.Script.parse(reader), Sequence_1.Sequence.parse(reader))); } return inputs; } /** * Parses the outputs for a transaction * @param reader * @returns */ static parseOutputs(reader) { const voutLen = Number(reader.readVarInt()); const outputs = []; for (let idx = 0; idx < voutLen; idx++) { outputs.push(new TxOut_1.TxOut(Value_1.Value.fromSats(reader.readBigUInt64LE()), Script_1.Script.parse(reader))); // prettier-ignore } return outputs; } /** * Parses a transaction from a buffer that contains the fully * serialization transaction bytes. * @param buf */ static fromBuffer(buf) { return Tx.decode(bufio_1.StreamReader.fromBuffer(buf)); } /** * Parses a transaction from a hex string containing the fully * serialized transaction bytes. * @param hex */ static fromHex(hex) { return Tx.decode(bufio_1.StreamReader.fromHex(hex)); } /** * Get the transaction version. The transaction version corresponds * to features that are enabled for the transaction such as time * locks. */ get version() { return this._version; } /** * Gets the transaction identifier. The `txId` for both legacy and * segwit transaction is the hash256 of * `hash256(version||inputs||ouputs||locktime)`. */ get txId() { if (!this._txId) this._lazyCalc(); return this._txId; } /** * Gets the transaction segwit transaction identifier. For legacy * transaction this is the same as the `txId` property. For segwit * transaction this is the hash256 of * `hash256(version||0x0001||inputs||outputs||witness||locktime)`. * * This is the same value as the `hash` property in bitcoind RPC * results. */ get witnessTxId() { if (!this._wtxid) this._lazyCalc(); return this._wtxid; } /** * Gets the transaction inputs. */ get inputs() { return this._inputs; } /** * Gets the transaction outputs */ get outputs() { return this._outputs; } /** * Gets the transaction `nLocktime` value that is used to control * absolute timelocks. */ get locktime() { return this._locktime; } get isSegWit() { return this._inputs.some((p) => p.witness.length > 0); } get size() { if (!this._sizes) this._lazyCalc(); return this._sizes.size; } get vsize() { if (!this._sizes) this._lazyCalc(); return this._sizes.vsize; } get weight() { if (!this._sizes) this._lazyCalc(); return this._sizes.weight; } constructor(version = 2, inputs = [], outputs = [], locktime = new LockTime_1.LockTime(), sizes) { this._version = version; this._inputs = inputs; this._outputs = outputs; this._locktime = locktime; this._sizes = sizes; } /** * Serializes legacy or segwit transactions into a Buffer */ serialize() { if (this.isSegWit) return this._serializeSegWit(); else return this._serializeLegacy(); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types toJSON() { return { version: this.version, inputs: this.inputs.map((vin) => vin.toJSON()), outputs: this.outputs.map((vout) => vout.toJSON()), locktime: this.locktime.toJSON(), }; } toHex(pretty = false) { if (!pretty) return this.serialize().toString('hex'); else return this._prettyHex(); } _prettyHex() { const nl = '\n'; const pad = ' '; let s = ''; s += bufio_2.Hex.uint32LE(this.version) + nl; if (this.isSegWit) { s += '0001' + nl; } s += bufio_2.Hex.varint(this.inputs.length) + nl; for (const vin of this.inputs) { s += pad + vin.outpoint.txid.serialize(HashByteOrder_1.HashByteOrder.Internal).toString('hex') + nl; s += pad + bufio_2.Hex.uint32LE(vin.outpoint.outputIndex) + nl; s += pad + vin.scriptSig.serialize().toString('hex') + nl; s += pad + bufio_2.Hex.uint32LE(vin.sequence.value) + nl; } s += bufio_2.Hex.varint(this.outputs.length) + nl; for (const vout of this.outputs) { s += pad + bufio_2.Hex.uint64LE(vout.value.sats) + nl; s += pad + vout.scriptPubKey.serialize().toString('hex'); s += nl; } if (this.isSegWit) { for (const vin of this.inputs) { s += bufio_2.Hex.varint(vin.witness.length) + nl; for (const w of vin.witness) { s += pad + w.serialize().toString('hex') + nl; } } } s += bufio_2.Hex.uint32LE(this.locktime.value); return s; } _serializeLegacy() { const writer = new bufio_1.BufferWriter(); // version writer.writeUInt32LE(this.version); // inputs writer.writeVarInt(this.inputs.length); for (const input of this.inputs) { writer.writeBytes(input.serialize()); } // outputs writer.writeVarInt(this.outputs.length); for (const output of this.outputs) { writer.writeBytes(output.serialize()); } // locktime writer.writeBytes(this.locktime.serialize()); return writer.toBuffer(); } _serializeSegWit() { const writer = new bufio_1.BufferWriter(); // version writer.writeUInt32LE(this.version); // write segwit marker and version writer.writeBytes(Buffer.from([0x00, 0x01])); // inputs writer.writeVarInt(this.inputs.length); for (const input of this.inputs) { writer.writeBytes(input.serialize()); } // outputs writer.writeVarInt(this.outputs.length); for (const output of this.outputs) { writer.writeBytes(output.serialize()); } // witness data if (this.isSegWit) { for (const input of this.inputs) { writer.writeVarInt(input.witness.length); for (const witness of input.witness) { writer.writeBytes(witness.serialize()); } } } // locktime writer.writeBytes(this.locktime.serialize()); return writer.toBuffer(); } /** * Decodes the txId and hash from the Buffer. * * For non-segwit transitions, the hash value is the double-sha256 of * version|vins|vouts|locktime. The txid is the reverse of the hash. * * For segwit transactions, the hash value is returned as the wtxid as * calculated by the double-sha256 of * version|0x00|0x01|inputs|outputs|witness|locktime. The txId is * calculate the same as legacy transactions by performing a double * sha256 hash of the data minus segwit data and markers. */ _calcTxId() { const txId = (0, crypto_1.hash256)(this._serializeLegacy()); const hash = this.isSegWit ? (0, crypto_1.hash256)(this._serializeSegWit()) : Buffer.from(txId); // prettier-ignore return { txId: new HashValue_1.HashValue(txId), hash: new HashValue_1.HashValue(hash), }; } /** * Calculates the size, virtual size, and weight properties from the * based on the current inputs and outputs. * * `size` is the number of raw bytes. * `weight` is the number of witness bytes + the number of non-witness * bytes multiplied by four. * `vsize` is the weight divided by four. */ _calcSize() { const hasWitness = this.isSegWit; let standardBytes = 0; let witnessBytes = 0; // version is 4-bytes standardBytes += 4; // witness flags are 2 bytes if (hasWitness) { witnessBytes += 2; } // number of inputs standardBytes += (0, bufio_1.varIntBytes)(this.inputs.length); // add each input for (const input of this.inputs) { // prev out hash standardBytes += 32; // prev out index standardBytes += 4; // scriptSig length const scriptSig = input.scriptSig.serializeCmds(); standardBytes += (0, bufio_1.varIntBytes)(scriptSig.length); standardBytes += scriptSig.length; // sequence, 4-bytes standardBytes += 4; // input witness if (hasWitness) { // number of witness witnessBytes += (0, bufio_1.varIntBytes)(input.witness.length); // for each witness for (const witness of input.witness) { witnessBytes += (0, bufio_1.varIntBytes)(witness.data.length); witnessBytes += witness.data.length; } } } // number of outputs standardBytes += (0, bufio_1.varIntBytes)(this.outputs.length); // add each output for (const output of this.outputs) { // value standardBytes += 8; // scriptPubKey length const scriptPubKey = output.scriptPubKey.serializeCmds(); standardBytes += (0, bufio_1.varIntBytes)(scriptPubKey.length); standardBytes += scriptPubKey.length; } // locktime standardBytes += 4; // size will be the raw length of bytes const size = standardBytes + witnessBytes; // weight is non-witness bytes * 4 + witness bytes const weight = standardBytes * 4 + witnessBytes; // virtual size is weight / 4 // this is equivalent for non-segwit transactions const vsize = Math.ceil(weight / 4); return { size, vsize, weight, }; } _lazyCalc() { this._sizes = this._calcSize(); const ids = this._calcTxId(); this._txId = ids.txId; this._wtxid = ids.hash; } } exports.Tx = Tx; //# sourceMappingURL=Tx.js.map