Class: Bitcoin::Tx

Inherits:
Object
  • Object
show all
Includes:
HexConverter
Defined in:
lib/bitcoin/tx.rb

Overview

Transaction class

Constant Summary collapse

MAX_STANDARD_VERSION =
2
MAX_STANDARD_TX_WEIGHT =

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

400000
MARKER =
0x00
FLAG =
0x01

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from HexConverter

#to_hex

Constructor Details

#initializeTx

Returns a new instance of Tx.



26
27
28
29
30
31
# File 'lib/bitcoin/tx.rb', line 26

def initialize
  @inputs = []
  @outputs = []
  @version = 1
  @lock_time = 0
end

Instance Attribute Details

#flagObject

Returns the value of attribute flag.



21
22
23
# File 'lib/bitcoin/tx.rb', line 21

def flag
  @flag
end

#inputsObject (readonly) Also known as: in

Returns the value of attribute inputs.



22
23
24
# File 'lib/bitcoin/tx.rb', line 22

def inputs
  @inputs
end

#lock_timeObject

Returns the value of attribute lock_time.



24
25
26
# File 'lib/bitcoin/tx.rb', line 24

def lock_time
  @lock_time
end

#markerObject

Returns the value of attribute marker.



20
21
22
# File 'lib/bitcoin/tx.rb', line 20

def marker
  @marker
end

#outputsObject (readonly) Also known as: out

Returns the value of attribute outputs.



23
24
25
# File 'lib/bitcoin/tx.rb', line 23

def outputs
  @outputs
end

#versionObject

Returns the value of attribute version.



19
20
21
# File 'lib/bitcoin/tx.rb', line 19

def version
  @version
end

Class Method Details

.create_coinbase(msg, script, rewards = 50 * 100000000) ⇒ Bitcoin::Tx

Create coinbase transaction.

Parameters:

  • msg (String)

    Message embedded in coinbase transaction.

  • script (Bitcoin::Script)

    Coinbase transaction scriptPubkey.

  • rewards (Integer) (defaults to: 50 * 100000000)

    Coinbase Transaction Rewards

Returns:



41
42
43
44
45
46
47
# File 'lib/bitcoin/tx.rb', line 41

def self.create_coinbase(msg, script, rewards = 50 * 100000000)
  coinbase = Tx.new
  script_sig = Bitcoin::Script.new << 486604799 << "04" << msg.bth
  coinbase.in << TxIn.new(out_point: OutPoint.create_coinbase_outpoint, script_sig: script_sig)
  coinbase.out << TxOut.new(value: rewards, script_pubkey: script)
  coinbase
end

.parse_from_payload(payload, non_witness: false, strict: false) ⇒ Object

Raises:

  • (ArgumentError)


49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/bitcoin/tx.rb', line 49

def self.parse_from_payload(payload, non_witness: false, strict: false)
  buf = payload.is_a?(String) ? StringIO.new(payload) : payload
  tx = new
  tx.version = buf.read(4).unpack1('V')

  in_count = Bitcoin.unpack_var_int_from_io(buf)
  has_witness = false
  if in_count.zero? && !non_witness
    tx.marker = 0
    tx.flag = buf.read(1).unpack1('c')
    if tx.flag.zero?
      buf.pos -= 1
    else
      in_count = Bitcoin.unpack_var_int_from_io(buf)
      has_witness = true
    end
  end

  in_count.times do
    tx.inputs << TxIn.parse_from_payload(buf)
  end

  out_count = Bitcoin.unpack_var_int_from_io(buf)
  out_count.times do
    tx.outputs << TxOut.parse_from_payload(buf)
  end

  if has_witness
    in_count.times do |i|
      tx.inputs[i].script_witness = Bitcoin::ScriptWitness.parse_from_payload(buf)
    end
  end

  raise ArgumentError, 'Transaction has unexpected data.' if strict &&  (buf.pos + 4) != buf.length
  tx.lock_time = buf.read(4).unpack1('V')
  tx
end

Instance Method Details

#==(other) ⇒ Object



130
131
132
# File 'lib/bitcoin/tx.rb', line 130

def ==(other)
  to_payload == other.to_payload
end

#coinbase_tx?Boolean

Returns:

  • (Boolean)


122
123
124
# File 'lib/bitcoin/tx.rb', line 122

def coinbase_tx?
  inputs.length == 1 && inputs.first.coinbase?
end

#hashObject



87
88
89
# File 'lib/bitcoin/tx.rb', line 87

def hash
  to_hex.to_i(16)
end

#serialize_old_formatObject

serialize tx with old tx format



135
136
137
138
139
140
141
# File 'lib/bitcoin/tx.rb', line 135

def serialize_old_format
  buf = [version].pack('V')
  buf << Bitcoin.pack_var_int(inputs.length) << inputs.map(&:to_payload).join
  buf << Bitcoin.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
  buf << [lock_time].pack('V')
  buf
end

#serialize_witness_formatObject

serialize tx with segwit tx format github.com/bitcoin/bips/blob/master/bip-0144.mediawiki



145
146
147
148
149
150
151
# File 'lib/bitcoin/tx.rb', line 145

def serialize_witness_format
  buf = [version, MARKER, FLAG].pack('Vcc')
  buf << Bitcoin.pack_var_int(inputs.length) << inputs.map(&:to_payload).join
  buf << Bitcoin.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
  buf << witness_payload << [lock_time].pack('V')
  buf
end

#sighash_for_input(input_index, output_script = nil, opts: {}, hash_type: , sig_version: :base, amount: nil, skip_separator_index: 0, prevouts: []) ⇒ String

get signature hash 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.

Parameters:

  • input_index (Integer)

    input index.

  • hash_type (Integer) (defaults to: )

    signature hash type

  • output_script (Bitcoin::Script) (defaults to: nil)

    script pubkey or script code. if script pubkey is P2WSH, set witness script to this.

  • opts (Hash) (defaults to: {})

    Data required for each sig version (amount and skip_separator_index params can also be set to this parameter)

  • amount (Integer) (defaults to: nil)

    bitcoin amount locked in input. required for witness input only.

  • skip_separator_index (Integer) (defaults to: 0)

    If output_script is P2WSH and output_script contains any OP_CODESEPARATOR,

  • Array[Bitcoin::TxOut] (Array[Bitcoin::TxOut] prevouts Previous outputs referenced by all Tx inputs, required for taproot.)

    prevouts Previous outputs referenced by all Tx inputs, required for taproot.

Returns:

  • (String)

    signature hash with binary format.

Raises:

  • (ArgumentError)


210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/bitcoin/tx.rb', line 210

def sighash_for_input(input_index, output_script = nil, opts: {}, hash_type: SIGHASH_TYPE[:all],
                      sig_version: :base, amount: nil, skip_separator_index: 0, prevouts: [])
  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.' if [:base, :witness_v0].include?(sig_version) && output_script.nil?

  opts[:amount] = amount if amount
  opts[:skip_separator_index] = skip_separator_index
  opts[:sig_version] = sig_version
  opts[:script_code] = output_script
  opts[:prevouts] = prevouts
  opts[:last_code_separator_pos] ||= 0xffffffff
  sig_hash_gen = SigHashGenerator.load(sig_version)
  sig_hash_gen.generate(self, input_index, hash_type, opts)
end

#sizeObject

The serialized transaction size



181
182
183
# File 'lib/bitcoin/tx.rb', line 181

def size
  to_payload.bytesize
end

#standard?Boolean

check this tx is standard.

Returns:

  • (Boolean)


158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/bitcoin/tx.rb', line 158

def standard?
  return false if version > MAX_STANDARD_VERSION
  return false if weight > MAX_STANDARD_TX_WEIGHT
  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_hObject



250
251
252
253
254
255
# File 'lib/bitcoin/tx.rb', line 250

def to_h
  {
      txid: txid, hash: witness_hash.rhex, version: version, size: size, vsize: vsize, 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_payloadObject



118
119
120
# File 'lib/bitcoin/tx.rb', line 118

def to_payload
  witness? ? serialize_witness_format : serialize_old_format
end

#tx_hashObject



91
92
93
# File 'lib/bitcoin/tx.rb', line 91

def tx_hash
  Bitcoin.double_sha256(serialize_old_format).bth
end

#txidObject



95
96
97
# File 'lib/bitcoin/tx.rb', line 95

def txid
  tx_hash.rhex
end

#valid?Boolean

Verify transaction validity.

Returns:

  • (Boolean)

    whether this tx is valid or not.



259
260
261
262
263
# File 'lib/bitcoin/tx.rb', line 259

def valid?
  state = Bitcoin::ValidationState.new
  validation = Bitcoin::Validation.new
  validation.check_tx(self, state) && state.valid?
end

#verify_input_sig(input_index, script_pubkey, amount: nil, flags: STANDARD_SCRIPT_VERIFY_FLAGS, prevouts: []) ⇒ Boolean

verify input signature.

Parameters:

  • input_index (Integer)
  • script_pubkey (Bitcoin::Script)

    the script pubkey for target input.

  • amount (Integer) (defaults to: nil)

    the amount of bitcoin, require for witness program only.

  • flags (Array) (defaults to: STANDARD_SCRIPT_VERIFY_FLAGS)

    the flags used when execute script interpreter.

  • prevouts (Array[Bitcoin::TxOut]) (defaults to: [])

    Previous outputs referenced by all Tx inputs, required for taproot.

Returns:

  • (Boolean)

    result



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/bitcoin/tx.rb', line 233

def verify_input_sig(input_index, script_pubkey, amount: nil, flags: STANDARD_SCRIPT_VERIFY_FLAGS, prevouts: [])
  script_sig = inputs[input_index].script_sig
  has_witness = inputs[input_index].has_witness?

  if script_pubkey.p2sh?
    flags << SCRIPT_VERIFY_P2SH
    redeem_script = Script.parse_from_payload(script_sig.chunks.last)
    script_pubkey = redeem_script if redeem_script.p2wpkh?
  end

  if has_witness
    verify_input_sig_for_witness(input_index, script_pubkey, amount, flags, prevouts)
  else
    verify_input_sig_for_legacy(input_index, script_pubkey, flags)
  end
end

#vsizeObject

The virtual transaction size (differs from size for witness transactions)



186
187
188
# File 'lib/bitcoin/tx.rb', line 186

def vsize
  (weight.to_f / 4).ceil
end

#weightObject

calculate tx weight weight = (legacy tx payload) * 3 + (witness tx payload)



192
193
194
195
196
197
198
# File 'lib/bitcoin/tx.rb', line 192

def weight
  if witness?
    serialize_old_format.bytesize * (WITNESS_SCALE_FACTOR - 1) + serialize_witness_format.bytesize
  else
    serialize_old_format.bytesize * WITNESS_SCALE_FACTOR
  end
end

#witness?Boolean

Returns:

  • (Boolean)


126
127
128
# File 'lib/bitcoin/tx.rb', line 126

def witness?
  inputs.any?(&:has_witness?)
end

#witness_commitmentObject

get the witness commitment of coinbase tx. if this tx does not coinbase or not have commitment, return nil.



109
110
111
112
113
114
115
116
# File 'lib/bitcoin/tx.rb', line 109

def witness_commitment
  return nil unless coinbase_tx?
  outputs.each do |output|
    commitment = output.script_pubkey.witness_commitment
    return commitment if commitment
  end
  nil
end

#witness_hashObject



99
100
101
# File 'lib/bitcoin/tx.rb', line 99

def witness_hash
  Bitcoin.double_sha256(to_payload).bth
end

#witness_payloadObject



153
154
155
# File 'lib/bitcoin/tx.rb', line 153

def witness_payload
  inputs.map { |i| i.script_witness.to_payload }.join
end

#wtxidObject



103
104
105
# File 'lib/bitcoin/tx.rb', line 103

def wtxid
  witness_hash.rhex
end