# encoding: ascii-8bit module Bitcoin module Protocol # TxIn section of https://en.bitcoin.it/wiki/Protocol_documentation#tx class TxIn # previous output hash attr_accessor :prev_out_hash alias prev_out prev_out_hash def prev_out=(hash) @prev_out_hash = hash end # previous output index attr_accessor :prev_out_index # script_sig input Script (signature) attr_reader :script_sig attr_accessor :script_sig_length # signature hash and the address of the key that needs to sign it # (used when dealing with unsigned or partly signed tx) attr_accessor :sig_hash, :sig_address # segregated witness attr_accessor :script_witness alias script script_sig alias script_length script_sig_length # sequence attr_accessor :sequence DEFAULT_SEQUENCE = "\xff\xff\xff\xff".freeze NULL_HASH = "\x00" * 32 COINBASE_INDEX = 0xffffffff def initialize(*args) @prev_out_hash, @prev_out_index, @script_sig_length, @script_sig, @sequence = *args @script_sig_length ||= 0 @script_sig ||= '' @sequence ||= DEFAULT_SEQUENCE @script_witness = ScriptWitness.new end # compare to another txout def ==(other) @prev_out_hash == other.prev_out_hash && @prev_out_index == other.prev_out_index && @script_sig == other.script_sig && @sequence == other.sequence rescue StandardError false end def is_final? # rubocop:disable Naming/PredicateName warn '[DEPRECATION] `TxIn.is_final?` is deprecated. Use `final?` instead.' final? end # returns true if the sequence number is final (DEFAULT_SEQUENCE) def final? sequence == DEFAULT_SEQUENCE end # parse raw binary data for transaction input def parse_data(data) buf = data.is_a?(String) ? StringIO.new(data) : data parse_data_from_io(buf) buf.pos end def self.from_io(buf) txin = new txin.parse_data_from_io(buf) txin end def parse_data_from_io(buf) @prev_out_hash, @prev_out_index = buf.read(36).unpack('a32V') @script_sig_length = Protocol.unpack_var_int_from_io(buf) @script_sig = buf.read(@script_sig_length) @sequence = buf.read(4) end def parsed_script @parsed_script ||= Bitcoin::Script.new(script_sig) end def to_payload(script = @script_sig, sequence = @sequence) [@prev_out_hash, @prev_out_index].pack('a32V') << Protocol.pack_var_int(script.bytesize) \ << script << (sequence || DEFAULT_SEQUENCE) end def to_hash(_options = {}) t = { 'prev_out' => { 'hash' => @prev_out_hash.reverse_hth, 'n' => @prev_out_index } } if coinbase? t['coinbase'] = @script_sig.unpack('H*')[0] else # coinbase tx t['scriptSig'] = Bitcoin::Script.new(@script_sig).to_string end t['sequence'] = @sequence.unpack('V')[0] unless @sequence == "\xff\xff\xff\xff" t['witness'] = @script_witness.stack.map(&:bth) unless @script_witness.empty? t end def self.from_hash(input) previous_hash = input['previous_transaction_hash'] || input['prev_out']['hash'] previous_output_index = input['output_index'] || input['prev_out']['n'] txin = TxIn.new([previous_hash].pack('H*').reverse, previous_output_index) txin.script_sig = if input['coinbase'] [input['coinbase']].pack('H*') else Script.binary_from_string(input['scriptSig'] || input['script']) end input['witness'].each { |w| txin.script_witness.stack << w.htb } if input['witness'] txin.sequence = [input['sequence'] || 0xffffffff].pack('V') txin end def self.from_hex_hash(hash, index) TxIn.new([hash].pack('H*').reverse, index, 0) end # previous output in hex def previous_output @prev_out_hash.reverse_hth end # check if input is coinbase def coinbase? (@prev_out_index == COINBASE_INDEX) && (@prev_out_hash == NULL_HASH) end # set script_sig and script_sig_length def script_sig=(script_sig) @script_sig_length = script_sig.bytesize @script_sig = script_sig end alias script= script_sig= def add_signature_pubkey_script(sig, pubkey_hex) self.script = Bitcoin::Script.to_signature_pubkey_script(sig, [pubkey_hex].pack('H*')) end end end end