lib/tapyrus/tx.rb in tapyrus-0.1.0 vs lib/tapyrus/tx.rb in tapyrus-0.2.0
- old
+ new
@@ -3,158 +3,88 @@
module Tapyrus
# Transaction class
class Tx
+ include Tapyrus::HexConverter
MAX_STANDARD_VERSION = 2
# The maximum weight for transactions we're willing to relay/mine
MAX_STANDARD_TX_WEIGHT = 400000
- MARKER = 0x00
- FLAG = 0x01
-
- attr_accessor :version
- attr_accessor :marker
- attr_accessor :flag
+ attr_accessor :features
attr_reader :inputs
attr_reader :outputs
attr_accessor :lock_time
def initialize
@inputs = []
@outputs = []
- @version = 1
+ @features = 1
@lock_time = 0
end
alias_method :in, :inputs
alias_method :out, :outputs
- def self.parse_from_payload(payload, non_witness: false)
+ def self.parse_from_payload(payload)
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
tx = new
- tx.version = buf.read(4).unpack('V').first
+ tx.features = buf.read(4).unpack('V').first
in_count = Tapyrus.unpack_var_int_from_io(buf)
- witness = false
- if in_count.zero? && !non_witness
- tx.marker = 0
- tx.flag = buf.read(1).unpack('c').first
- if tx.flag.zero?
- buf.pos -= 1
- else
- in_count = Tapyrus.unpack_var_int_from_io(buf)
- witness = true
- end
- end
in_count.times do
tx.inputs << TxIn.parse_from_payload(buf)
end
out_count = Tapyrus.unpack_var_int_from_io(buf)
out_count.times do
tx.outputs << TxOut.parse_from_payload(buf)
end
- if witness
- in_count.times do |i|
- tx.inputs[i].script_witness = Tapyrus::ScriptWitness.parse_from_payload(buf)
- end
- end
-
tx.lock_time = buf.read(4).unpack('V').first
tx
end
def hash
- to_payload.bth.to_i(16)
+ to_hex.to_i(16)
end
def tx_hash
- Tapyrus.double_sha256(serialize_old_format).bth
+ Tapyrus.double_sha256(to_payload).bth
end
def txid
- buf = [version].pack('V')
+ 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
- def witness_hash
- Tapyrus.double_sha256(to_payload).bth
- end
-
- def wtxid
- witness_hash.rhex
- end
-
- # get the witness commitment of coinbase tx.
- # if this tx does not coinbase or not have commitment, return nil.
- 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
-
def to_payload
- witness? ? serialize_witness_format : serialize_old_format
+ 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
- # convert tx to hex format.
- # @return [String] tx with hex format.
- def to_hex
- to_payload.bth
- end
-
def coinbase_tx?
inputs.length == 1 && inputs.first.coinbase?
end
- def witness?
- !inputs.find { |i| !i.script_witness.empty? }.nil?
- end
-
def ==(other)
to_payload == other.to_payload
end
- # serialize tx with old tx format
- def serialize_old_format
- buf = [version].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
-
- # serialize tx with segwit tx format
- # https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki
- def serialize_witness_format
- buf = [version, MARKER, FLAG].pack('Vcc')
- 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 << witness_payload << [lock_time].pack('V')
- buf
- end
-
- def witness_payload
- inputs.map { |i| i.script_witness.to_payload }.join
- end
-
# check this tx is standard.
def standard?
- return false if version > MAX_STANDARD_VERSION
- return false if weight > MAX_STANDARD_TX_WEIGHT
+ 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.
@@ -175,25 +105,10 @@
# The serialized transaction size
def size
to_payload.bytesize
end
- # The virtual transaction size (differs from size for witness transactions)
- def vsize
- (weight.to_f / 4).ceil
- end
-
- # calculate tx weight
- # weight = (legacy tx payload) * 3 + (witness tx payload)
- 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
-
# 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.
@@ -203,44 +118,28 @@
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)
-
- if sig_version == :witness_v0 || Tapyrus.chain_params.fork_chain?
- raise ArgumentError, 'amount must be specified.' unless amount
- sighash_for_witness(input_index, output_script, hash_type, amount, skip_separator_index)
- else
- sighash_for_legacy(input_index, output_script, hash_type)
- end
+ sighash_for_legacy(input_index, output_script, hash_type)
end
# 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.
def verify_input_sig(input_index, script_pubkey, amount: nil, flags: STANDARD_SCRIPT_VERIFY_FLAGS)
- 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 || Tapyrus.chain_params.fork_chain?
- verify_input_sig_for_witness(input_index, script_pubkey, amount, flags)
- else
- verify_input_sig_for_legacy(input_index, script_pubkey, flags)
- end
+ verify_input_sig_for_legacy(input_index, script_pubkey, flags)
end
def to_h
{
- txid: txid, hash: witness_hash.rhex, version: version, size: size, vsize: vsize, locktime: lock_time,
+ 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
# Verify transaction validity.
@@ -283,64 +182,23 @@
if hash_type & SIGHASH_TYPE[:anyonecanpay] != 0
ins = [ins[index]]
end
- buf = [[version].pack('V'), Tapyrus.pack_var_int(ins.size),
+ 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
- # generate sighash with BIP-143 format
- # https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
- def sighash_for_witness(index, script_pubkey_or_script_code, hash_type, amount, skip_separator_index)
- hash_prevouts = Tapyrus.double_sha256(inputs.map{|i|i.out_point.to_payload}.join)
- hash_sequence = Tapyrus.double_sha256(inputs.map{|i|[i.sequence].pack('V')}.join)
- outpoint = inputs[index].out_point.to_payload
- amount = [amount].pack('Q')
- nsequence = [inputs[index].sequence].pack('V')
- hash_outputs = Tapyrus.double_sha256(outputs.map{|o|o.to_payload}.join)
- script_code = script_pubkey_or_script_code.to_script_code(skip_separator_index)
-
- case (hash_type & 0x1f)
- when SIGHASH_TYPE[:single]
- hash_outputs = index >= outputs.size ? "\x00".ljust(32, "\x00") : Tapyrus.double_sha256(outputs[index].to_payload)
- hash_sequence = "\x00".ljust(32, "\x00")
- when SIGHASH_TYPE[:none]
- hash_sequence = hash_outputs = "\x00".ljust(32, "\x00")
- end
-
- if (hash_type & SIGHASH_TYPE[:anyonecanpay]) != 0
- hash_prevouts = hash_sequence ="\x00".ljust(32, "\x00")
- end
- hash_type |= (Tapyrus.chain_params.fork_id << 8) if Tapyrus.chain_params.fork_chain?
- buf = [ [version].pack('V'), hash_prevouts, hash_sequence, outpoint,
- script_code ,amount, nsequence, hash_outputs, [@lock_time, hash_type].pack('VV')].join
- Tapyrus.double_sha256(buf)
- end
-
# verify input signature for legacy tx.
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
-
- # verify input signature for witness tx.
- def verify_input_sig_for_witness(input_index, script_pubkey, amount, flags)
- flags |= SCRIPT_VERIFY_WITNESS
- flags |= SCRIPT_VERIFY_WITNESS_PUBKEYTYPE
- checker = Tapyrus::TxChecker.new(tx: self, input_index: input_index, amount: amount)
- interpreter = Tapyrus::ScriptInterpreter.new(checker: checker, flags: flags)
- i = inputs[input_index]
-
- script_sig = i.script_sig
- witness = i.script_witness
- interpreter.verify_script(script_sig, script_pubkey, witness)
end
end
end