import { BufferWriter , StreamReader , varIntBytes } from "@node-lightning/bufio" ; import { Hex } from "@node-lightning/bufio" ; import { hash256 } from "@node-lightning/crypto" ; import { HashByteOrder } from "./HashByteOrder" ; import { HashValue } from "./HashValue" ; import { LockTime } from "./LockTime" ; import { OutPoint } from "./OutPoint" ; import { Script } from "./Script" ; import { Sequence } from "./Sequence" ; import { SizeResult } from "./SizeResult" ; import { TxIn } from "./TxIn" ; import { TxOut } from "./TxOut" ; import { Value } from "./Value" ; import { Witness } from "./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.
 */ export class Tx { param reader public static decode ( reader ) { const data = reader . readBytes ( ) ; try { return Tx . parse ( StreamReader . fromBuffer ( data ) , true ) ; } catch ( ex ) { return Tx . parse ( StreamReader . fromBuffer ( data ) , false ) ; } } param reader private static parse ( reader , allowWitness ) { const version = reader . readUInt32LE ( ) ; let vins = Tx . parseInputs ( reader ) ; let vouts ; let flags = 0 ; if ( allowWitness && vins . length === 0 ) { flags = reader . readUInt8 ( ) ; if ( flags !== 0 ) { vins = Tx . parseInputs ( reader ) ; vouts = Tx . parseOutputs ( reader ) ; } } else { vouts = Tx . parseOutputs ( reader ) ; } 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 . parse ( reader ) ) ; } } } const locktime = LockTime . parse ( reader ) ; return new Tx ( version , vins , vouts , locktime ) ; } param reader returns private static parseInputs ( reader ) { const vinLen = Number ( reader . readVarInt ( ) ) ; const inputs = [ ] ; for ( let idx = 0 ; idx < vinLen ; idx ++ ) { inputs . push ( new TxIn ( OutPoint . parse ( reader ) , Script . parse ( reader ) , Sequence . parse ( reader ) ) ) ; } return inputs ; } param reader returns private static parseOutputs ( reader ) { const voutLen = Number ( reader . readVarInt ( ) ) ; const outputs = [ ] ; for ( let idx = 0 ; idx < voutLen ; idx ++ ) { outputs . push ( new TxOut ( Value . fromSats ( reader . readBigUInt64LE ( ) ) , Script . parse ( reader ) ) ) ; } return outputs ; } param buf public static fromBuffer ( buf ) { return Tx . decode ( StreamReader . fromBuffer ( buf ) ) ; } param hex public static fromHex ( hex ) { return Tx . decode ( StreamReader . fromHex ( hex ) ) ; } /**
     * Get the transaction version. The transaction version corresponds
     * to features that are enabled for the transaction such as time
     * locks.
     */ public get version ( ) : number { return this . _version ; } /**
     * Gets the transaction identifier. The `txId` for both legacy and
     * segwit transaction is the hash256 of
     * `hash256(version||inputs||ouputs||locktime)`.
     */ public get txId ( ) : HashValue { 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.
     */ public get witnessTxId ( ) : HashValue { if ( ! this . _wtxid ) this . _lazyCalc ( ) ; return this . _wtxid ; } /**
     * Gets the transaction inputs.
     */ public get inputs ( ) : TxIn [ ] { return this . _inputs ; } /**
     * Gets the transaction outputs
     */ public get outputs ( ) : TxOut [ ] { return this . _outputs ; } /**
     * Gets the transaction `nLocktime` value that is used to control
     * absolute timelocks.
     */ public get locktime ( ) : LockTime { return this . _locktime ; } public get isSegWit ( ) : boolean { return this . _inputs . some ( p => p . witness . length > 0 ) ; } public get size ( ) : number { if ( ! this . _sizes ) this . _lazyCalc ( ) ; return this . _sizes . size ; } public get vsize ( ) : number { if ( ! this . _sizes ) this . _lazyCalc ( ) ; return this . _sizes . vsize ; } public get weight ( ) : number { if ( ! this . _sizes ) this . _lazyCalc ( ) ; return this . _sizes . weight ; } private _version : number ; private _txId : HashValue ; private _wtxid : HashValue ; private _inputs : TxIn [ ] ; private _outputs : TxOut [ ] ; private _locktime : LockTime ; private _sizes : SizeResult ; public constructor ( version = 2 , inputs = [ ] , outputs = [ ] , locktime = new 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
     */ public serialize ( ) { if ( this . isSegWit ) return this . _serializeSegWit ( ) ; else return this . _serializeLegacy ( ) ; } public toJSON ( ) { return { version : this . version , inputs : this . inputs . map ( vin => vin . toJSON ( ) ) , outputs : this . outputs . map ( vout => vout . toJSON ( ) ) , locktime : this . locktime . toJSON ( ) , } ; } public toHex ( pretty = false ) { if ( ! pretty ) return this . serialize ( ) . toString ( "hex" ) ; else return this . _prettyHex ( ) ; } private _prettyHex ( ) { const nl = "\n" ; const pad = "    " ; let s = "" ; s += Hex . uint32LE ( this . version ) + nl ; if ( this . isSegWit ) { s += "0001" + nl ; } s += Hex . varint ( this . inputs . length ) + nl ; for ( const vin of this . inputs ) { s += pad + vin . outpoint . txid . serialize ( HashByteOrder . Internal ) . toString ( "hex" ) + nl ; s += pad + Hex . uint32LE ( vin . outpoint . outputIndex ) + nl ; s += pad + vin . scriptSig . serialize ( ) . toString ( "hex" ) + nl ; s += pad + Hex . uint32LE ( vin . sequence . value ) + nl ; } s += Hex . varint ( this . outputs . length ) + nl ; for ( const vout of this . outputs ) { s += pad + 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 += Hex . varint ( vin . witness . length ) + nl ; for ( const w of vin . witness ) { s += pad + w . serialize ( ) . toString ( "hex" ) + nl ; } } } s += Hex . uint32LE ( this . locktime . value ) ; return s ; } private _serializeLegacy ( ) { const writer = new BufferWriter ( ) ; writer . writeUInt32LE ( this . version ) ; writer . writeVarInt ( this . inputs . length ) ; for ( const input of this . inputs ) { writer . writeBytes ( input . serialize ( ) ) ; } writer . writeVarInt ( this . outputs . length ) ; for ( const output of this . outputs ) { writer . writeBytes ( output . serialize ( ) ) ; } writer . writeBytes ( this . locktime . serialize ( ) ) ; return writer . toBuffer ( ) ; } private _serializeSegWit ( ) { const writer = new BufferWriter ( ) ; writer . writeUInt32LE ( this . version ) ; writer . writeBytes ( Buffer . from ( [ 0x00 , 0x01 ] ) ) ; writer . writeVarInt ( this . inputs . length ) ; for ( const input of this . inputs ) { writer . writeBytes ( input . serialize ( ) ) ; } writer . writeVarInt ( this . outputs . length ) ; for ( const output of this . outputs ) { writer . writeBytes ( output . serialize ( ) ) ; } if ( this . isSegWit ) { for ( const input of this . inputs ) { writer . writeVarInt ( input . witness . length ) ; for ( const witness of input . witness ) { writer . writeBytes ( witness . serialize ( ) ) ; } } } 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.
     */ private _calcTxId ( ) { const txId = hash256 ( this . _serializeLegacy ( ) ) ; const hash = this . isSegWit ? hash256 ( this . _serializeSegWit ( ) ) : Buffer . from ( txId ) ; return { txId : new HashValue ( txId ) , hash : new 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.
     */ private _calcSize ( ) { const hasWitness = this . isSegWit ; let standardBytes = 0 ; let witnessBytes = 0 ; standardBytes += 4 ; if ( hasWitness ) { witnessBytes += 2 ; } standardBytes += varIntBytes ( this . inputs . length ) ; for ( const input of this . inputs ) { standardBytes += 32 ; standardBytes += 4 ; const scriptSig = input . scriptSig . serializeCmds ( ) ; standardBytes += varIntBytes ( scriptSig . length ) ; standardBytes += scriptSig . length ; standardBytes += 4 ; if ( hasWitness ) { witnessBytes += varIntBytes ( input . witness . length ) ; for ( const witness of input . witness ) { witnessBytes += varIntBytes ( witness . data . length ) ; witnessBytes += witness . data . length ; } } } standardBytes += varIntBytes ( this . outputs . length ) ; for ( const output of this . outputs ) { standardBytes += 8 ; const scriptPubKey = output . scriptPubKey . serializeCmds ( ) ; standardBytes += varIntBytes ( scriptPubKey . length ) ; standardBytes += scriptPubKey . length ; } standardBytes += 4 ; const size = standardBytes + witnessBytes ; const weight = standardBytes * 4 + witnessBytes ; const vsize = Math . ceil ( weight / 4 ) ; return { size , vsize , weight , } ; } private _lazyCalc ( ) { this . _sizes = this . _calcSize ( ) ; const ids = this . _calcTxId ( ) ; this . _txId = ids . txId ; this . _wtxid = ids . hash ; } }