class Tapyrus::Tx

Transaction class

Constants

MAX_STANDARD_TX_WEIGHT

The maximum weight for transactions we're willing to relay/mine

MAX_STANDARD_VERSION

Attributes

features[RW]
in[R]
inputs[R]
lock_time[RW]
out[R]
outputs[R]

Public Class Methods

new() click to toggle source
# File lib/tapyrus/tx.rb, line 19
def initialize
  @inputs = []
  @outputs = []
  @features = 1
  @lock_time = 0
end
parse_from_payload(payload) click to toggle source
# File lib/tapyrus/tx.rb, line 29
def self.parse_from_payload(payload)
  buf = payload.is_a?(String) ? StringIO.new(payload) : payload
  tx = new
  tx.features = buf.read(4).unpack('V').first

  in_count = Tapyrus.unpack_var_int_from_io(buf)

  in_count.times { tx.inputs << TxIn.parse_from_payload(buf) }

  out_count = Tapyrus.unpack_var_int_from_io(buf)
  out_count.times { tx.outputs << TxOut.parse_from_payload(buf) }

  tx.lock_time = buf.read(4).unpack('V').first

  tx
end

Public Instance Methods

==(other) click to toggle source
# File lib/tapyrus/tx.rb, line 74
def ==(other)
  to_payload == other.to_payload
end
coinbase_tx?() click to toggle source
# File lib/tapyrus/tx.rb, line 70
def coinbase_tx?
  inputs.length == 1 && inputs.first.coinbase?
end
hash() click to toggle source
# File lib/tapyrus/tx.rb, line 46
def hash
  to_hex.to_i(16)
end
sighash_for_input( input_index, output_script, hash_type: SIGHASH_TYPE[:all], sig_version: :base, amount: nil, skip_separator_index: 0 ) click to toggle source

get signature hash @param [Integer] input_index input index. @param [Integer] hash_type signature hash type @param [Tapyrus::Script] output_script script pubkey or script code. if script pubkey is P2WSH, set witness script to this. @param [Integer] amount tapyrus amount locked in input. required for witness input only. @param [Integer] skip_separator_index If output_script is P2WSH and output_script contains any OP_CODESEPARATOR, the script code needs is the witnessScript but removing everything up to and including the last executed OP_CODESEPARATOR before the signature checking opcode being executed.

# File lib/tapyrus/tx.rb, line 113
def sighash_for_input(
  input_index,
  output_script,
  hash_type: SIGHASH_TYPE[:all],
  sig_version: :base,
  amount: nil,
  skip_separator_index: 0
)
  raise ArgumentError, 'input_index must be specified.' unless input_index
  raise ArgumentError, 'does not exist input corresponding to input_index.' if input_index >= inputs.size
  raise ArgumentError, 'script_pubkey must be specified.' unless output_script
  raise ArgumentError, 'unsupported sig version specified.' unless SIG_VERSION.include?(sig_version)
  sighash_for_legacy(input_index, output_script, hash_type)
end
size() click to toggle source

The serialized transaction size

# File lib/tapyrus/tx.rb, line 102
def size
  to_payload.bytesize
end
standard?() click to toggle source

check this tx is standard.

# File lib/tapyrus/tx.rb, line 79
def standard?
  return false if features > MAX_STANDARD_VERSION
  inputs.each do |i|
    # Biggest 'standard' txin is a 15-of-15 P2SH multisig with compressed keys (remember the 520 byte limit on redeemScript size).
    # That works out to a (15*(33+1))+3=513 byte redeemScript, 513+1+15*(73+1)+3=1627
    # bytes of scriptSig, which we round off to 1650 bytes for some minor future-proofing.
    # That's also enough to spend a 20-of-20 CHECKMULTISIG scriptPubKey, though such a scriptPubKey is not considered standard.
    return false if i.script_sig.size > 1650
    return false unless i.script_sig.push_only?
  end
  data_count = 0
  outputs.each do |o|
    return false unless o.script_pubkey.standard?
    data_count += 1 if o.script_pubkey.op_return?

    # TODO add non P2SH multisig relay(permitbaremultisig)
    return false if o.dust?
  end
  return false if data_count > 1
  true
end
to_h() click to toggle source
# File lib/tapyrus/tx.rb, line 138
def to_h
  {
    txid: txid,
    hash: tx_hash,
    features: features,
    size: size,
    locktime: lock_time,
    vin: inputs.map(&:to_h),
    vout: outputs.map.with_index { |tx_out, index| tx_out.to_h.merge({ n: index }) }
  }
end
to_payload() click to toggle source
# File lib/tapyrus/tx.rb, line 62
def to_payload
  buf = [features].pack('V')
  buf << Tapyrus.pack_var_int(inputs.length) << inputs.map(&:to_payload).join
  buf << Tapyrus.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
  buf << [lock_time].pack('V')
  buf
end
tx_hash() click to toggle source
# File lib/tapyrus/tx.rb, line 50
def tx_hash
  Tapyrus.double_sha256(to_payload).bth
end
txid() click to toggle source
# File lib/tapyrus/tx.rb, line 54
def txid
  buf = [features].pack('V')
  buf << Tapyrus.pack_var_int(inputs.length) << inputs.map { |i| i.to_payload(use_malfix: true) }.join
  buf << Tapyrus.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
  buf << [lock_time].pack('V')
  Tapyrus.double_sha256(buf).reverse.bth
end
valid?() click to toggle source

Verify transaction validity. @return [Boolean] whether this tx is valid or not.

# File lib/tapyrus/tx.rb, line 152
def valid?
  state = Tapyrus::ValidationState.new
  validation = Tapyrus::Validation.new
  validation.check_tx(self, state) && state.valid?
end
verify_input_sig(input_index, script_pubkey, amount: nil, flags: STANDARD_SCRIPT_VERIFY_FLAGS) click to toggle source

verify input signature. @param [Integer] input_index @param [Tapyrus::Script] script_pubkey the script pubkey for target input. @param [Integer] amount the amount of tapyrus, require for witness program only. @param [Array] flags the flags used when execute script interpreter.

# File lib/tapyrus/tx.rb, line 133
def verify_input_sig(input_index, script_pubkey, amount: nil, flags: STANDARD_SCRIPT_VERIFY_FLAGS)
  flags << SCRIPT_VERIFY_P2SH if script_pubkey.p2sh?
  verify_input_sig_for_legacy(input_index, script_pubkey, flags)
end

Private Instance Methods

sighash_for_legacy(index, script_code, hash_type) click to toggle source

generate sighash with legacy format

# File lib/tapyrus/tx.rb, line 161
def sighash_for_legacy(index, script_code, hash_type)
  ins =
    inputs.map.with_index do |i, idx|
      if idx == index
        i.to_payload(script_code.delete_opcode(Tapyrus::Opcodes::OP_CODESEPARATOR))
      else
        case hash_type & 0x1f
        when SIGHASH_TYPE[:none], SIGHASH_TYPE[:single]
          i.to_payload(Tapyrus::Script.new, 0)
        else
          i.to_payload(Tapyrus::Script.new)
        end
      end
    end

  outs = outputs.map(&:to_payload)
  out_size = Tapyrus.pack_var_int(outputs.size)

  case hash_type & 0x1f
  when SIGHASH_TYPE[:none]
    outs = ''
    out_size = Tapyrus.pack_var_int(0)
  when SIGHASH_TYPE[:single]
    return "\x01".ljust(32, "\x00") if index >= outputs.size
    outs =
      outputs[0...(index + 1)].map.with_index { |o, idx| (idx == index) ? o.to_payload : o.to_empty_payload }.join
    out_size = Tapyrus.pack_var_int(index + 1)
  end

  ins = [ins[index]] if hash_type & SIGHASH_TYPE[:anyonecanpay] != 0

  buf = [
    [features].pack('V'),
    Tapyrus.pack_var_int(ins.size),
    ins,
    out_size,
    outs,
    [lock_time, hash_type].pack('VV')
  ].join

  Tapyrus.double_sha256(buf)
end
verify_input_sig_for_legacy(input_index, script_pubkey, flags) click to toggle source

verify input signature for legacy tx.

# File lib/tapyrus/tx.rb, line 205
def verify_input_sig_for_legacy(input_index, script_pubkey, flags)
  script_sig = inputs[input_index].script_sig
  checker = Tapyrus::TxChecker.new(tx: self, input_index: input_index)
  interpreter = Tapyrus::ScriptInterpreter.new(checker: checker, flags: flags)

  interpreter.verify_script(script_sig, script_pubkey)
end