lib/bitcoin/protocol/tx.rb in bitcoin-ruby-0.0.18 vs lib/bitcoin/protocol/tx.rb in bitcoin-ruby-0.0.19
- old
+ new
@@ -2,13 +2,12 @@
require 'bitcoin/script'
module Bitcoin
module Protocol
-
+ # https://en.bitcoin.it/wiki/Protocol_documentation#tx
class Tx
-
MARKER = 0
FLAG = 1
SIGHASH_TYPE = Script::SIGHASH_TYPE
@@ -34,82 +33,91 @@
attr_reader :scripts
attr_accessor :marker
attr_accessor :flag
- alias :inputs :in
- alias :outputs :out
+ alias inputs in
+ alias outputs out
# compare to another tx
def ==(other)
@hash == other.hash
end
# return the tx hash in binary format
def binary_hash
- @binary_hash ||= [@hash].pack("H*").reverse
+ @binary_hash ||= [@hash].pack('H*').reverse
end
# create tx from raw binary +data+
- def initialize(data=nil)
- @ver, @lock_time, @in, @out, @scripts = 1, 0, [], [], []
- @enable_bitcoinconsensus = !!ENV['USE_BITCOINCONSENSUS']
+ def initialize(data = nil)
+ @ver = 1
+ @lock_time = 0
+ @in = []
+ @out = []
+ @scripts = []
+ @enable_bitcoinconsensus = !ENV['USE_BITCOINCONSENSUS'].nil?
parse_data_from_io(data) if data
end
# generate the tx hash for given +payload+ in hex format
def hash_from_payload(payload)
- Digest::SHA256.digest(Digest::SHA256.digest( payload )).reverse_hth
+ Digest::SHA256.digest(Digest::SHA256.digest(payload)).reverse_hth
end
alias generate_hash hash_from_payload
# refresh_hash recalculates the tx hash and sets it on the instance
def refresh_hash
@hash = generate_hash(to_old_payload)
end
# add an input
- def add_in(input); (@in ||= []) << input; end
+ def add_in(input)
+ (@in ||= []) << input
+ end
# add an output
- def add_out(output); (@out ||= []) << output; end
+ def add_out(output)
+ (@out ||= []) << output
+ end
# parse raw binary data
+ # rubocop:disable CyclomaticComplexity,PerceivedComplexity
def parse_data_from_io(data)
buf = data.is_a?(String) ? StringIO.new(data) : data
- @ver = buf.read(4).unpack("V")[0]
+ @ver = buf.read(4).unpack('V')[0]
return false if buf.eof?
# segwit serialization format is defined by https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki
- # Also note that it is impossible to parse 0 input transactions. Regular transactions with 0 inputs look
- # like malformed segwit transactions.
+ # Also note that it is impossible to parse 0 input transactions. Regular transactions
+ # with 0 inputs look like malformed segwit transactions.
@marker = buf.read(1).unpack('c').first
@flag = buf.read(1).unpack('c').first
- witness = @marker == 0 && @flag != 0
+ witness = @marker.zero? && !@flag.zero?
# Non-segwit format does not contain marker or flag fields.
buf.seek(buf.pos - 2) unless witness
in_size = Protocol.unpack_var_int_from_io(buf)
@in = []
- in_size.times{
+ in_size.times do
break if buf.eof?
@in << TxIn.from_io(buf)
- }
+ end
return false if buf.eof?
out_size = Protocol.unpack_var_int_from_io(buf)
@out = []
- out_size.times{
+ out_size.times do
break if buf.eof?
@out << TxOut.from_io(buf)
- }
+ end
return false if buf.eof?
if witness
in_size.times do |i|
@@ -119,36 +127,39 @@
@in[i].script_witness.stack << buf.read(size)
end
end
end
- @lock_time = buf.read(4).unpack("V")[0]
+ @lock_time = buf.read(4).unpack('V')[0]
@hash = hash_from_payload(to_old_payload)
@payload = to_payload
if buf.eof?
true
else
data.is_a?(StringIO) ? buf : buf.read
end
end
+ # rubocop:enable CyclomaticComplexity,PerceivedComplexity
- alias :parse_data :parse_data_from_io
+ alias parse_data parse_data_from_io
# output transaction in raw binary format
def to_payload
witness? ? to_witness_payload : to_old_payload
end
def to_old_payload
- pin = ""
- @in.each{|input| pin << input.to_payload }
- pout = ""
- @out.each{|output| pout << output.to_payload }
+ pin = ''
+ @in.each { |input| pin << input.to_payload }
+ pout = ''
+ @out.each { |output| pout << output.to_payload }
- [@ver].pack("V") << Protocol.pack_var_int(@in.size) << pin << Protocol.pack_var_int(@out.size) << pout << [@lock_time].pack("V")
+ [@ver].pack('V') << Protocol.pack_var_int(@in.size) \
+ << pin << Protocol.pack_var_int(@out.size) \
+ << pout << [@lock_time].pack('V')
end
# output transaction in raw binary format with witness
def to_witness_payload
buf = [@ver, MARKER, FLAG].pack('Vcc')
@@ -166,11 +177,14 @@
!@in.find { |i| !i.script_witness.empty? }.nil?
end
# generate a signature hash for input +input_idx+.
# either pass the +outpoint_tx+ or the +script_pubkey+ directly.
- def signature_hash_for_input(input_idx, subscript, hash_type=nil, prev_out_value=nil, fork_id=nil)
+ # rubocop:disable CyclomaticComplexity,PerceivedComplexity
+ def signature_hash_for_input(
+ input_idx, subscript, hash_type = nil, prev_out_value = nil, fork_id = nil
+ )
# https://github.com/bitcoin/bitcoin/blob/e071a3f6c06f41068ad17134189a4ac3073ef76b/script.cpp#L834
# http://code.google.com/p/bitcoinj/source/browse/trunk/src/com/google/bitcoin/core/Script.java#318
# https://en.bitcoin.it/wiki/OP_CHECKSIG#How_it_works
# https://github.com/bitcoin/bitcoin/blob/c2e8c8acd8ae0c94c70b59f55169841ad195bb99/src/script.cpp#L1058
# https://en.bitcoin.it/wiki/OP_CHECKSIG
@@ -179,136 +193,172 @@
# fork_id is optional and if set, SIGHASH_FORKID flag as defined by the
# Bitcoin Cash protocol will be respected.
#
# https://github.com/Bitcoin-ABC/bitcoin-abc/blob/master/doc/abc/replay-protected-sighash.md
- if fork_id && (hash_type&SIGHASH_TYPE[:forkid]) != 0
- raise "SIGHASH_FORKID is enabled, so prev_out_value is required" if prev_out_value.nil?
+ if fork_id && (hash_type & SIGHASH_TYPE[:forkid]) != 0
+ raise 'SIGHASH_FORKID is enabled, so prev_out_value is required' if prev_out_value.nil?
# According to the spec, we should modify the sighash by replacing the 24 most significant
# bits with the fork ID. However, Bitcoin ABC does not currently implement this since the
# fork_id is an implicit 0 and it would make the sighash JSON tests fail. Will leave as a
# TODO for now.
- raise NotImplementedError, "fork_id must be 0" unless fork_id == 0
+ raise NotImplementedError, 'fork_id must be 0' unless fork_id.zero?
script_code = Bitcoin::Protocol.pack_var_string(subscript)
return signature_hash_for_input_bip143(input_idx, script_code, prev_out_value, hash_type)
end
# Note: BitcoinQT checks if input_idx >= @in.size and returns 1 with an error message.
- # But this check is never actually useful because BitcoinQT would crash
- # right before VerifyScript if input index is out of bounds (inside CScriptCheck::operator()()).
+ # But this check is never actually useful because BitcoinQT would crash right before
+ # VerifyScript if input index is out of bounds (inside CScriptCheck::operator()()).
# That's why we don't need to do such a check here.
#
- # However, if you look at the case SIGHASH_TYPE[:single] below, we must
- # return 1 because it's possible to have more inputs than outputs and BitcoinQT returns 1 as well.
- return "\x01".ljust(32, "\x00") if input_idx >= @in.size # ERROR: SignatureHash() : input_idx=%d out of range
+ # However, if you look at the case SIGHASH_TYPE[:single] below, we must return 1
+ # because it's possible to have more inputs than outputs and BitcoinQT returns 1 as well.
- pin = @in.map.with_index{|input,idx|
+ # ERROR: SignatureHash() : input_idx=%d out of range
+ return "\x01".ljust(32, "\x00") if input_idx >= @in.size
+
+ pin = @in.map.with_index do |input, idx|
if idx == input_idx
- subscript = subscript.out[ input.prev_out_index ].script if subscript.respond_to?(:out) # legacy api (outpoint_tx)
+ # legacy api (outpoint_tx)
+ subscript = subscript.out[input.prev_out_index].script if subscript.respond_to?(:out)
# Remove all instances of OP_CODESEPARATOR from the script.
parsed_subscript = Script.new(subscript)
parsed_subscript.chunks.delete(Script::OP_CODESEPARATOR)
subscript = parsed_subscript.to_binary
input.to_payload(subscript)
else
case (hash_type & 0x1f)
- when SIGHASH_TYPE[:none]; input.to_payload("", "\x00\x00\x00\x00")
- when SIGHASH_TYPE[:single]; input.to_payload("", "\x00\x00\x00\x00")
- else; input.to_payload("")
+ when SIGHASH_TYPE[:none] then input.to_payload('', "\x00\x00\x00\x00")
+ when SIGHASH_TYPE[:single] then input.to_payload('', "\x00\x00\x00\x00")
+ else; input.to_payload('')
end
end
- }
+ end
pout = @out.map(&:to_payload)
- in_size, out_size = Protocol.pack_var_int(@in.size), Protocol.pack_var_int(@out.size)
+ in_size = Protocol.pack_var_int(@in.size)
+ out_size = Protocol.pack_var_int(@out.size)
case (hash_type & 0x1f)
when SIGHASH_TYPE[:none]
- pout = ""
+ pout = ''
out_size = Protocol.pack_var_int(0)
when SIGHASH_TYPE[:single]
- return "\x01".ljust(32, "\x00") if input_idx >= @out.size # ERROR: SignatureHash() : input_idx=%d out of range (SIGHASH_SINGLE)
- pout = @out[0...(input_idx+1)].map.with_index{|out,idx| (idx==input_idx) ? out.to_payload : out.to_null_payload }.join
- out_size = Protocol.pack_var_int(input_idx+1)
+ # ERROR: SignatureHash() : input_idx=%d out of range (SIGHASH_SINGLE)
+ return "\x01".ljust(32, "\x00") if input_idx >= @out.size
+
+ pout = @out[0...(input_idx + 1)].map.with_index do |out, idx|
+ idx == input_idx ? out.to_payload : out.to_null_payload
+ end.join
+
+ out_size = Protocol.pack_var_int(input_idx + 1)
end
if (hash_type & SIGHASH_TYPE[:anyonecanpay]) != 0
- in_size, pin = Protocol.pack_var_int(1), [ pin[input_idx] ]
+ in_size = Protocol.pack_var_int(1)
+ pin = [pin[input_idx]]
end
- buf = [ [@ver].pack("V"), in_size, pin, out_size, pout, [@lock_time, hash_type].pack("VV") ].join
- Digest::SHA256.digest( Digest::SHA256.digest( buf ) )
+ buf = [
+ [@ver].pack('V'), in_size, pin, out_size,
+ pout, [@lock_time, hash_type].pack('VV')
+ ].join
+ Digest::SHA256.digest(Digest::SHA256.digest(buf))
end
+ # rubocop:enable CyclomaticComplexity,PerceivedComplexity
# generate a witness signature hash for input +input_idx+.
# https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
- def signature_hash_for_witness_input(input_idx, witness_program, prev_out_value, witness_script = nil, hash_type=nil, skip_separator_index = 0)
- return "\x01".ljust(32, "\x00") if input_idx >= @in.size # ERROR: SignatureHash() : input_idx=%d out of range
+ # rubocop:disable Metrics/ParameterLists
+ def signature_hash_for_witness_input(
+ input_idx, witness_program, prev_out_value,
+ witness_script = nil, hash_type = nil, skip_separator_index = 0
+ )
+ # ERROR: SignatureHash() : input_idx=%d out of range
+ return "\x01".ljust(32, "\x00") if input_idx >= @in.size
hash_type ||= SIGHASH_TYPE[:all]
-
script = Bitcoin::Script.new(witness_program)
- raise "ScriptPubkey does not contain witness program." unless script.is_witness?
+ raise 'ScriptPubkey does not contain witness program.' unless script.is_witness?
if script.is_witness_v0_keyhash?
- script_code = [["1976a914", script.get_hash160, "88ac"].join].pack("H*")
+ script_code = [['1976a914', script.get_hash160, '88ac'].join].pack('H*')
elsif script.is_witness_v0_scripthash?
- raise "witness script does not match script pubkey" unless Bitcoin::Script.to_witness_p2sh_script(Digest::SHA256.digest(witness_script).bth) == witness_program
- script = skip_separator_index > 0 ? Bitcoin::Script.new(witness_script).subscript_codeseparator(skip_separator_index) : witness_script
+ witness_pubkey_script_match = Bitcoin::Script.to_witness_p2sh_script(
+ Digest::SHA256.digest(witness_script).bth
+ ) == witness_program
+ raise 'witness script does not match script pubkey' unless witness_pubkey_script_match
+
+ script = if skip_separator_index > 0
+ s = Bitcoin::Script.new(witness_script)
+ s.subscript_codeseparator(skip_separator_index)
+ else
+ witness_script
+ end
script_code = Bitcoin::Protocol.pack_var_string(script)
end
signature_hash_for_input_bip143(input_idx, script_code, prev_out_value, hash_type)
end
+ # rubocop:enable Metrics/ParameterLists
# verify input signature +in_idx+ against the corresponding
# output in +outpoint_tx+
# outpoint. This arg can also be a Script or TxOut.
#
- # options are: verify_sigpushonly, verify_minimaldata, verify_cleanstack, verify_dersig, verify_low_s, verify_strictenc, fork_id
- def verify_input_signature(in_idx, outpoint_data, block_timestamp=Time.now.to_i, opts={})
+ # options are: verify_sigpushonly, verify_minimaldata, verify_cleanstack,
+ # verify_dersig, verify_low_s, verify_strictenc, fork_id
+ def verify_input_signature(in_idx, outpoint_data, block_timestamp = Time.now.to_i, opts = {})
if @enable_bitcoinconsensus
return bitcoinconsensus_verify_script(in_idx, outpoint_data, block_timestamp, opts)
end
# If FORKID is enabled, we also ensure strict encoding.
- opts[:verify_strictenc] ||= !!opts[:fork_id]
+ opts[:verify_strictenc] ||= !opts[:fork_id].nil?
outpoint_idx = @in[in_idx].prev_out_index
script_sig = @in[in_idx].script_sig
amount = amount_from_outpoint_data(outpoint_data, outpoint_idx)
script_pubkey = script_pubkey_from_outpoint_data(outpoint_data, outpoint_idx)
if opts[:fork_id] && amount.nil?
- raise "verify_input_signature must be called with a previous transaction or " \
- "transaction output if SIGHASH_FORKID is enabled"
+ raise 'verify_input_signature must be called with a previous transaction or ' \
+ 'transaction output if SIGHASH_FORKID is enabled'
end
@scripts[in_idx] = Bitcoin::Script.new(script_sig, script_pubkey)
return false if opts[:verify_sigpushonly] && !@scripts[in_idx].is_push_only?(script_sig)
return false if opts[:verify_minimaldata] && !@scripts[in_idx].pushes_are_canonical?
- sig_valid = @scripts[in_idx].run(block_timestamp, opts) do |pubkey,sig,hash_type,subscript|
+ sig_valid = @scripts[in_idx].run(
+ block_timestamp, opts
+ ) do |pubkey, sig, hash_type, subscript|
hash = signature_hash_for_input(in_idx, subscript, hash_type, amount, opts[:fork_id])
- Bitcoin.verify_signature( hash, sig, pubkey.unpack("H*")[0] )
+ Bitcoin.verify_signature(hash, sig, pubkey.unpack('H*')[0])
end
# BIP62 rule #6
return false if opts[:verify_cleanstack] && !@scripts[in_idx].stack.empty?
- return sig_valid
+ sig_valid
end
# verify witness input signature +in_idx+ against the corresponding
# output in +outpoint_tx+
# outpoint. This arg can also be a Script or TxOut
#
- # options are: verify_sigpushonly, verify_minimaldata, verify_cleanstack, verify_dersig, verify_low_s, verify_strictenc
- def verify_witness_input_signature(in_idx, outpoint_data, prev_out_amount, block_timestamp=Time.now.to_i, opts={})
+ # options are: verify_sigpushonly, verify_minimaldata, verify_cleanstack,
+ # verify_dersig, verify_low_s, verify_strictenc
+ #
+ # rubocop:disable CyclomaticComplexity,PerceivedComplexity
+ def verify_witness_input_signature(
+ in_idx, outpoint_data, prev_out_amount, block_timestamp = Time.now.to_i, opts = {}
+ )
if @enable_bitcoinconsensus
return bitcoinconsensus_verify_script(in_idx, outpoint_data, block_timestamp, opts)
end
outpoint_idx = @in[in_idx].prev_out_index
@@ -317,56 +367,73 @@
script_pubkey = script_pubkey_from_outpoint_data(outpoint_data, outpoint_idx)
script_pubkey = Bitcoin::Script.new(script_pubkey)
if script_pubkey.is_p2sh?
redeem_script = Bitcoin::Script.new(@in[in_idx].script_sig).get_pubkey
- script_pubkey = Bitcoin::Script.new(redeem_script.htb) if Bitcoin.hash160(redeem_script) == script_pubkey.get_hash160 # P2SH-P2WPKH or P2SH-P2WSH
+
+ # P2SH-P2WPKH or P2SH-P2WSH
+ script_pubkey = if Bitcoin.hash160(redeem_script) == script_pubkey.get_hash160
+ Bitcoin::Script.new(redeem_script.htb)
+ end
end
- @in[in_idx].script_witness.stack.each{|s|script_sig << Bitcoin::Script.pack_pushdata(s)}
+ @in[in_idx].script_witness.stack.each { |s| script_sig << Bitcoin::Script.pack_pushdata(s) }
code_separator_index = 0
if script_pubkey.is_witness_v0_keyhash? # P2WPKH
- @scripts[in_idx] = Bitcoin::Script.new(script_sig, Bitcoin::Script.to_hash160_script(script_pubkey.get_hash160))
+ @scripts[in_idx] = Bitcoin::Script.new(
+ script_sig, Bitcoin::Script.to_hash160_script(script_pubkey.get_hash160)
+ )
elsif script_pubkey.is_witness_v0_scripthash? # P2WSH
witness_hex = @in[in_idx].script_witness.stack.last.bth
witness_script = Bitcoin::Script.new(witness_hex.htb)
return false unless Bitcoin.sha256(witness_hex) == script_pubkey.get_hash160
- @scripts[in_idx] = Bitcoin::Script.new(script_sig, Bitcoin::Script.to_p2sh_script(Bitcoin.hash160(witness_hex)))
+ @scripts[in_idx] = Bitcoin::Script.new(
+ script_sig, Bitcoin::Script.to_p2sh_script(Bitcoin.hash160(witness_hex))
+ )
else
return false
end
return false if opts[:verify_sigpushonly] && !@scripts[in_idx].is_push_only?(script_sig)
return false if opts[:verify_minimaldata] && !@scripts[in_idx].pushes_are_canonical?
- sig_valid = @scripts[in_idx].run(block_timestamp, opts) do |pubkey,sig,hash_type,subscript|
+ sig_valid = @scripts[in_idx].run(block_timestamp, opts) do |pubkey, sig, hash_type, _|
if script_pubkey.is_witness_v0_keyhash?
- hash = signature_hash_for_witness_input(in_idx, script_pubkey.to_payload, prev_out_amount, nil, hash_type)
+ hash = signature_hash_for_witness_input(
+ in_idx, script_pubkey.to_payload, prev_out_amount, nil, hash_type
+ )
elsif script_pubkey.is_witness_v0_scripthash?
- hash = signature_hash_for_witness_input(in_idx, script_pubkey.to_payload, prev_out_amount, witness_hex.htb, hash_type, code_separator_index)
+ hash = signature_hash_for_witness_input(
+ in_idx, script_pubkey.to_payload, prev_out_amount,
+ witness_hex.htb, hash_type, code_separator_index
+ )
code_separator_index += 1 if witness_script.codeseparator_count > code_separator_index
end
- Bitcoin.verify_signature( hash, sig, pubkey.unpack("H*")[0] )
+ Bitcoin.verify_signature(hash, sig, pubkey.unpack('H*')[0])
end
# BIP62 rule #6
return false if opts[:verify_cleanstack] && !@scripts[in_idx].stack.empty?
- return sig_valid
+ sig_valid
end
+ # rubocop:enable CyclomaticComplexity,PerceivedComplexity
- def bitcoinconsensus_verify_script(in_idx, outpoint_data, block_timestamp=Time.now.to_i, opts={})
- raise "Bitcoin::BitcoinConsensus shared library not found" unless Bitcoin::BitcoinConsensus.lib_available?
+ def bitcoinconsensus_verify_script(
+ in_idx, outpoint_data, block_timestamp = Time.now.to_i, opts = {}
+ )
+ consensus_available = Bitcoin::BitcoinConsensus.lib_available?
+ raise 'Bitcoin::BitcoinConsensus shared library not found' unless consensus_available
outpoint_idx = @in[in_idx].prev_out_index
script_pubkey = script_pubkey_from_outpoint_data(outpoint_data, outpoint_idx)
flags = Bitcoin::BitcoinConsensus::SCRIPT_VERIFY_NONE
- flags |= Bitcoin::BitcoinConsensus::SCRIPT_VERIFY_P2SH if block_timestamp >= 1333238400
+ flags |= Bitcoin::BitcoinConsensus::SCRIPT_VERIFY_P2SH if block_timestamp >= 1_333_238_400
flags |= Bitcoin::BitcoinConsensus::SCRIPT_VERIFY_SIGPUSHONLY if opts[:verify_sigpushonly]
flags |= Bitcoin::BitcoinConsensus::SCRIPT_VERIFY_MINIMALDATA if opts[:verify_minimaldata]
- flags |= Bitcoin::BitcoinConsensus::SCRIPT_VERIFY_CLEANSTACK if opts[:verify_cleanstack]
- flags |= Bitcoin::BitcoinConsensus::SCRIPT_VERIFY_LOW_S if opts[:verify_low_s]
+ flags |= Bitcoin::BitcoinConsensus::SCRIPT_VERIFY_CLEANSTACK if opts[:verify_cleanstack]
+ flags |= Bitcoin::BitcoinConsensus::SCRIPT_VERIFY_LOW_S if opts[:verify_low_s]
payload ||= to_payload
Bitcoin::BitcoinConsensus.verify_script(in_idx, script_pubkey, payload, flags)
end
@@ -375,42 +442,41 @@
@hash ||= hash_from_payload(to_old_payload)
h = {
'hash' => @hash, 'ver' => @ver, # 'nid' => normalized_hash,
'vin_sz' => @in.size, 'vout_sz' => @out.size,
'lock_time' => @lock_time, 'size' => (@payload ||= to_payload).bytesize,
- 'in' => @in.map{|i|i.to_hash(options)},
- 'out' => @out.map{|o| o.to_hash(options) }
+ 'in' => @in.map { |i| i.to_hash(options) },
+ 'out' => @out.map { |o| o.to_hash(options) }
}
- h['nid'] = normalized_hash if options[:with_nid]
+ h['nid'] = normalized_hash if options[:with_nid]
h
end
# generates rawblock json as seen in the block explorer.
- def to_json(options = {:space => ''}, *a)
- JSON.pretty_generate( to_hash(options), options )
+ def to_json(options = { space: '' }, *_a)
+ JSON.pretty_generate(to_hash(options), options)
end
# write json representation to a file
# (see also #to_json)
def to_json_file(path)
- File.open(path, 'wb'){|f| f.print to_json; }
+ File.open(path, 'wb') { |f| f.print to_json; }
end
# parse ruby hash (see also #to_hash)
- def self.from_hash(h, do_raise=true)
+ def self.from_hash(h, do_raise = true)
tx = new(nil)
- tx.ver, tx.lock_time = (h['ver'] || h['version']), h['lock_time']
+ tx.ver = (h['ver'] || h['version'])
+ tx.lock_time = h['lock_time']
ins = h['in'] || h['inputs']
outs = h['out'] || h['outputs']
- ins .each{|input|
- tx.add_in(TxIn.from_hash(input))
- }
- outs.each{|output| tx.add_out TxOut.from_hash(output) }
- tx.instance_eval{
+ ins.each { |input| tx.add_in(TxIn.from_hash(input)) }
+ outs.each { |output| tx.add_out TxOut.from_hash(output) }
+ tx.instance_eval do
@hash = hash_from_payload(to_old_payload)
@payload = to_payload
- }
+ end
if h['hash'] && (h['hash'] != tx.hash)
raise "Tx hash mismatch! Claimed: #{h['hash']}, Actual: #{tx.hash}" if do_raise
end
tx
end
@@ -420,74 +486,104 @@
tx = from_hash(h)
tx.to_payload
end
# parse json representation
- def self.from_json(json_string); from_hash( JSON.load(json_string) ); end
+ def self.from_json(json_string)
+ from_hash(JSON.parse(json_string))
+ end
# convert json representation to raw binary
- def self.binary_from_json(json_string); from_json(json_string).to_payload; end
+ def self.binary_from_json(json_string)
+ from_json(json_string).to_payload
+ end
# read binary block from a file
- def self.from_file(path); new( Bitcoin::Protocol.read_binary_file(path) ); end
+ def self.from_file(path)
+ new(Bitcoin::Protocol.read_binary_file(path))
+ end
# read json block from a file
- def self.from_json_file(path); from_json( Bitcoin::Protocol.read_binary_file(path) ); end
+ def self.from_json_file(path)
+ from_json(Bitcoin::Protocol.read_binary_file(path))
+ end
def size
payload.bytesize
end
-
- # Checks if transaction is final taking into account height and time
+
+ def is_final?(block_height, block_time) # rubocop:disable Naming/PredicateName
+ warn '[DEPRECATION] `Tx.is_final?` is deprecated. Use `final?` instead.'
+ final?(block_height, block_time)
+ end
+
+ # Checks if transaction is final taking into account height and time
# of a block in which it is located (or about to be included if it's unconfirmed tx).
- def is_final?(block_height, block_time)
+ def final?(block_height, block_time)
# No time lock - tx is final.
- return true if lock_time == 0
+ return true if lock_time.zero?
# Time based nLockTime implemented in 0.1.6
# If lock_time is below the magic threshold treat it as a block height.
# If lock_time is above the threshold, it's a unix timestamp.
- return true if lock_time < (lock_time < Bitcoin::LOCKTIME_THRESHOLD ? block_height : block_time)
+ lock_threshold = lock_time < Bitcoin::LOCKTIME_THRESHOLD ? block_height : block_time
+ return true if lock_time < lock_threshold
- inputs.each{|input| return false if !input.is_final? }
+ inputs.each { |input| return false unless input.final? }
- return true
+ true
end
-
+
def legacy_sigops_count
- # Note: input scripts normally never have any opcodes since every input script
+ # Note: input scripts normally never have any opcodes since every input script
# can be statically reduced to a pushdata-only script.
- # However, anyone is allowed to create a non-standard transaction with any opcodes in the inputs.
+ # However, anyone is allowed to create a non-standard transaction
+ # with any opcodes in the inputs.
count = 0
self.in.each do |txin|
count += Bitcoin::Script.new(txin.script_sig).sigops_count_accurate(false)
end
- self.out.each do |txout|
+ out.each do |txout|
count += Bitcoin::Script.new(txout.pk_script).sigops_count_accurate(false)
end
count
end
- DEFAULT_BLOCK_PRIORITY_SIZE = 27000
+ DEFAULT_BLOCK_PRIORITY_SIZE = 27_000
- def minimum_relay_fee; calculate_minimum_fee(allow_free=true, :relay); end
- def minimum_block_fee; calculate_minimum_fee(allow_free=true, :block); end
+ def minimum_relay_fee
+ calculate_minimum_fee(true, :relay)
+ end
- def calculate_minimum_fee(allow_free=true, mode=:block)
+ def minimum_block_fee
+ calculate_minimum_fee(true, :block)
+ end
+
+ # rubocop:disable PerceivedComplexity
+ def calculate_minimum_fee(allow_free = true, mode = :block)
# Base fee is either nMinTxFee or nMinRelayTxFee
- base_fee = (mode == :relay) ? Bitcoin.network[:min_relay_tx_fee] : Bitcoin.network[:min_tx_fee]
- tx_size = to_payload.bytesize
- min_fee = (1 + tx_size / 1_000) * base_fee
+ base_fee = if mode == :relay
+ Bitcoin.network[:min_relay_tx_fee]
+ else
+ Bitcoin.network[:min_tx_fee]
+ end
+ tx_size = to_payload.bytesize
+ min_fee = (1 + tx_size / 1_000) * base_fee
if allow_free
# There is a free transaction area in blocks created by most miners,
# * If we are relaying we allow transactions up to DEFAULT_BLOCK_PRIORITY_SIZE - 1000
# to be considered to fall into this category. We don't want to encourage sending
# multiple transactions instead of one big transaction to avoid fees.
# * If we are creating a transaction we allow transactions up to 1,000 bytes
# to be considered safe and assume they can likely make it into this section.
- min_fee = 0 if tx_size < (mode == :block ? Bitcoin.network[:free_tx_bytes] : DEFAULT_BLOCK_PRIORITY_SIZE - 1_000)
+ min_free_size = if mode == :block
+ Bitcoin.network[:free_tx_bytes]
+ else
+ DEFAULT_BLOCK_PRIORITY_SIZE - 1_000
+ end
+ min_fee = 0 if tx_size < min_free_size
end
# This code can be removed after enough miners have upgraded to version 0.9.
# Until then, be safe when sending and require a fee if any output is less than CENT
if min_fee < base_fee && mode == :block
@@ -503,61 +599,78 @@
end
end
end
end
- min_fee = Bitcoin::network[:max_money] unless min_fee.between?(0, Bitcoin::network[:max_money])
+ min_fee = Bitcoin.network[:max_money] unless min_fee.between?(
+ 0, Bitcoin.network[:max_money]
+ )
min_fee
end
+ # rubocop:enable PerceivedComplexity
- def is_coinbase?
- inputs.size == 1 and inputs.first.coinbase?
+ def is_coinbase? # rubocop:disable Naming/PredicateName
+ warn '[DEPRECATION] `Tx.is_coinbase?` is deprecated. Use `coinbase?` instead.'
+ coinbase?
end
+ def coinbase?
+ inputs.size == 1 && inputs.first.coinbase?
+ end
+
def normalized_hash
signature_hash_for_input(-1, nil, SIGHASH_TYPE[:all]).reverse.hth
end
- alias :nhash :normalized_hash
+ alias nhash normalized_hash
+
# get witness hash
def witness_hash
hash_from_payload(to_witness_payload)
end
# sort transaction inputs and outputs under BIP 69
# https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki
def lexicographical_sort!
- inputs.sort_by!{|i| [i.previous_output, i.prev_out_index]}
- outputs.sort_by!{|o| [o.amount, o.pk_script.bth]}
+ inputs.sort_by! { |i| [i.previous_output, i.prev_out_index] }
+ outputs.sort_by! { |o| [o.amount, o.pk_script.bth] }
end
private
def signature_hash_for_input_bip143(input_idx, script_code, prev_out_value, hash_type)
- hash_prevouts = Digest::SHA256.digest(Digest::SHA256.digest(@in.map{|i| [i.prev_out_hash, i.prev_out_index].pack("a32V")}.join))
- hash_sequence = Digest::SHA256.digest(Digest::SHA256.digest(@in.map{|i|i.sequence}.join))
- outpoint = [@in[input_idx].prev_out_hash, @in[input_idx].prev_out_index].pack("a32V")
- amount = [prev_out_value].pack("Q")
+ hash_prevouts = Digest::SHA256.digest(
+ Digest::SHA256.digest(
+ @in.map { |i| [i.prev_out_hash, i.prev_out_index].pack('a32V') }.join
+ )
+ )
+ hash_sequence = Digest::SHA256.digest(Digest::SHA256.digest(@in.map(&:sequence).join))
+ outpoint = [@in[input_idx].prev_out_hash, @in[input_idx].prev_out_index].pack('a32V')
+ amount = [prev_out_value].pack('Q')
nsequence = @in[input_idx].sequence
- hash_outputs = Digest::SHA256.digest(Digest::SHA256.digest(@out.map{|o|o.to_payload}.join))
+ hash_outputs = Digest::SHA256.digest(Digest::SHA256.digest(@out.map(&:to_payload).join))
case (hash_type & 0x1f)
- when SIGHASH_TYPE[:single]
- hash_outputs = input_idx >= @out.size ? "\x00".ljust(32, "\x00") : Digest::SHA256.digest(Digest::SHA256.digest(@out[input_idx].to_payload))
- hash_sequence = "\x00".ljust(32, "\x00")
- when SIGHASH_TYPE[:none]
- hash_sequence = hash_outputs = "\x00".ljust(32, "\x00")
+ when SIGHASH_TYPE[:single]
+ hash_outputs = if input_idx >= @out.size
+ "\x00".ljust(32, "\x00")
+ else
+ Digest::SHA256.digest(Digest::SHA256.digest(@out[input_idx].to_payload))
+ end
+ 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")
+ hash_prevouts = hash_sequence = "\x00".ljust(32, "\x00")
end
- buf = [ [@ver].pack("V"), hash_prevouts, hash_sequence, outpoint,
- script_code, amount, nsequence, hash_outputs, [@lock_time, hash_type].pack("VV")].join
+ buf = [[@ver].pack('V'), hash_prevouts, hash_sequence, outpoint, script_code,
+ amount, nsequence, hash_outputs, [@lock_time, hash_type].pack('VV')].join
- Digest::SHA256.digest( Digest::SHA256.digest( buf ) )
+ Digest::SHA256.digest(Digest::SHA256.digest(buf))
end
def script_pubkey_from_outpoint_data(outpoint_data, outpoint_idx)
if outpoint_data.respond_to?(:out)
# If given an entire previous transaction, take the script from it