class Bitcoin::Tx
Transaction class
Constants
- FLAG
- MARKER
- MAX_STANDARD_TX_WEIGHT
The maximum weight for transactions we're willing to relay/mine
- MAX_STANDARD_VERSION
Attributes
Public Class Methods
# File lib/bitcoin/tx.rb, line 26 def initialize @inputs = [] @outputs = [] @version = 1 @lock_time = 0 end
# File lib/bitcoin/tx.rb, line 36 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
Public Instance Methods
# File lib/bitcoin/tx.rb, line 117 def ==(other) to_payload == other.to_payload end
# File lib/bitcoin/tx.rb, line 109 def coinbase_tx? inputs.length == 1 && inputs.first.coinbase? end
# File lib/bitcoin/tx.rb, line 74 def hash to_hex.to_i(16) end
serialize tx with old tx format
# File lib/bitcoin/tx.rb, line 122 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 tx with segwit tx format github.com/bitcoin/bips/blob/master/bip-0144.mediawiki
# File lib/bitcoin/tx.rb, line 132 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
get signature hash @param [Integer] input_index input index. @param [Integer] hash_type signature hash type @param [Bitcoin::Script] output_script script pubkey or script code. if script pubkey is P2WSH, set witness script to this. @param [Hash] opts Data required for each sig version (amount and skip_separator_index params can also be set to this parameter) @param [Integer] amount bitcoin 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. @param [Array prevouts Previous outputs referenced by all Tx
inputs, required for taproot. @return [String] signature hash with binary format.
# File lib/bitcoin/tx.rb, line 197 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
The serialized transaction size
# File lib/bitcoin/tx.rb, line 168 def size to_payload.bytesize end
check this tx is standard.
# File lib/bitcoin/tx.rb, line 145 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
# File lib/bitcoin/tx.rb, line 237 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
# File lib/bitcoin/tx.rb, line 105 def to_payload witness? ? serialize_witness_format : serialize_old_format end
# File lib/bitcoin/tx.rb, line 78 def tx_hash Bitcoin.double_sha256(serialize_old_format).bth end
# File lib/bitcoin/tx.rb, line 82 def txid tx_hash.rhex end
Verify transaction validity. @return [Boolean] whether this tx is valid or not.
# File lib/bitcoin/tx.rb, line 246 def valid? state = Bitcoin::ValidationState.new validation = Bitcoin::Validation.new validation.check_tx(self, state) && state.valid? end
verify input signature. @param [Integer] input_index @param [Bitcoin::Script] script_pubkey the script pubkey for target input. @param [Integer] amount the amount of bitcoin, require for witness program only. @param [Array] flags the flags used when execute script interpreter. @param [Array] prevouts Previous outputs referenced by all Tx
inputs, required for taproot. @return [Boolean] result
# File lib/bitcoin/tx.rb, line 220 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
The virtual transaction size (differs from size for witness transactions)
# File lib/bitcoin/tx.rb, line 173 def vsize (weight.to_f / 4).ceil end
calculate tx weight weight = (legacy tx payload) * 3 + (witness tx payload)
# File lib/bitcoin/tx.rb, line 179 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
# File lib/bitcoin/tx.rb, line 113 def witness? !inputs.find { |i| !i.script_witness.empty? }.nil? end
get the witness commitment of coinbase tx. if this tx does not coinbase or not have commitment, return nil.
# File lib/bitcoin/tx.rb, line 96 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
# File lib/bitcoin/tx.rb, line 86 def witness_hash Bitcoin.double_sha256(to_payload).bth end
# File lib/bitcoin/tx.rb, line 140 def witness_payload inputs.map { |i| i.script_witness.to_payload }.join end
# File lib/bitcoin/tx.rb, line 90 def wtxid witness_hash.rhex end
Private Instance Methods
verify input signature for legacy tx.
# File lib/bitcoin/tx.rb, line 255 def verify_input_sig_for_legacy(input_index, script_pubkey, flags) script_sig = inputs[input_index].script_sig checker = Bitcoin::TxChecker.new(tx: self, input_index: input_index) interpreter = Bitcoin::ScriptInterpreter.new(checker: checker, flags: flags) interpreter.verify_script(script_sig, script_pubkey) end
verify input signature for witness tx.
# File lib/bitcoin/tx.rb, line 264 def verify_input_sig_for_witness(input_index, script_pubkey, amount, flags, prevouts) checker = Bitcoin::TxChecker.new(tx: self, input_index: input_index, amount: amount, prevouts: prevouts) interpreter = Bitcoin::ScriptInterpreter.new(checker: checker, flags: flags) i = inputs[input_index] interpreter.verify_script(i.script_sig, script_pubkey, i.script_witness) end