lib/bitcoin/protocol/tx.rb in bitcoin-ruby-0.0.11 vs lib/bitcoin/protocol/tx.rb in bitcoin-ruby-0.0.12
- old
+ new
@@ -8,10 +8,12 @@
class Tx
MARKER = 0
FLAG = 1
+ SIGHASH_TYPE = Script::SIGHASH_TYPE
+
# transaction hash
attr_reader :hash
# inputs (Array of TxIn)
attr_reader :in
@@ -58,10 +60,15 @@
def hash_from_payload(payload)
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
# add an output
def add_out(output); (@out ||= []) << output; end
@@ -79,12 +86,19 @@
# segwit serialization format is defined by https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki
witness = false
if in_size.zero?
@marker = 0
@flag = buf.read(1).unpack('c').first
- in_size = Protocol.unpack_var_int_from_io(buf)
- witness = true
+
+ # marker must be zero and flag must be non-zero to be valid segwit
+ if @marker == 0 && @flag != 0
+ in_size = Protocol.unpack_var_int_from_io(buf)
+ witness = true
+ else
+ # Undo the last read in case this isn't a segwit transaction
+ buf.seek(buf.pos - 1)
+ end
end
@in = []
in_size.times{
break if buf.eof?
@@ -113,10 +127,11 @@
end
@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
@@ -154,35 +169,56 @@
def witness?
!@in.find { |i| !i.script_witness.empty? }.nil?
end
- SIGHASH_TYPE = { all: 1, none: 2, single: 3, anyonecanpay: 128 }
-
# 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)
+ 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
+ hash_type ||= SIGHASH_TYPE[:all]
+
+ # 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?
+
+ # 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
+
+ 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()()).
# 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
- hash_type ||= SIGHASH_TYPE[:all]
-
pin = @in.map.with_index{|input,idx|
if idx == input_idx
subscript = subscript.out[ input.prev_out_index ].script if subscript.respond_to?(:out) # legacy api (outpoint_tx)
+
+ # 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")
@@ -220,70 +256,59 @@
hash_type ||= SIGHASH_TYPE[:all]
script = Bitcoin::Script.new(witness_program)
raise "ScriptPubkey does not contain witness program." unless script.is_witness?
- 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")
- nsequence = @in[input_idx].sequence
-
if script.is_witness_v0_keyhash?
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
script_code = Bitcoin::Protocol.pack_var_string(script)
end
- hash_outputs = Digest::SHA256.digest(Digest::SHA256.digest(@out.map{|o|o.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")
- end
-
- if (hash_type & SIGHASH_TYPE[:anyonecanpay]) != 0
- 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
-
- Digest::SHA256.digest( Digest::SHA256.digest( buf ) )
+ signature_hash_for_input_bip143(input_idx, script_code, prev_out_value, hash_type)
end
# verify input signature +in_idx+ against the corresponding
# output in +outpoint_tx+
# outpoint
#
- # options are: verify_sigpushonly, verify_minimaldata, verify_cleanstack, verify_dersig, verify_low_s, verify_strictenc
+ # options are: verify_sigpushonly, verify_minimaldata, verify_cleanstack, verify_dersig, verify_low_s, verify_strictenc, fork_id
def verify_input_signature(in_idx, outpoint_tx_or_script, block_timestamp=Time.now.to_i, opts={})
if @enable_bitcoinconsensus
return bitcoinconsensus_verify_script(in_idx, outpoint_tx_or_script, block_timestamp, opts)
end
+ # If FORKID is enabled, we also ensure strict encoding.
+ opts[:verify_strictenc] ||= !!opts[:fork_id]
+
outpoint_idx = @in[in_idx].prev_out_index
script_sig = @in[in_idx].script_sig
-
- # If given an entire previous transaction, take the script from it
- script_pubkey = if outpoint_tx_or_script.respond_to?(:out)
- outpoint_tx_or_script.out[outpoint_idx].pk_script
+
+ amount = nil
+ script_pubkey = nil
+ if outpoint_tx_or_script.respond_to?(:out)
+ # If given an entire previous transaction, take the script from it
+ prevout = outpoint_tx_or_script.out[outpoint_idx]
+ amount = prevout.value
+ script_pubkey = prevout.pk_script
else
+ if opts[:fork_id]
+ raise "verify_input_signature must be called with a previous transaction if " \
+ "SIGHASH_FORKID is enabled"
+ end
+
# Otherwise, it's already a script.
- outpoint_tx_or_script
+ script_pubkey = outpoint_tx_or_script
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|
- hash = signature_hash_for_input(in_idx, subscript, hash_type)
+ hash = signature_hash_for_input(in_idx, subscript, hash_type, amount, opts[:fork_id])
Bitcoin.verify_signature( hash, sig, pubkey.unpack("H*")[0] )
end
# BIP62 rule #6
return false if opts[:verify_cleanstack] && !@scripts[in_idx].stack.empty?
@@ -403,11 +428,14 @@
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{ @hash = hash_from_payload(@payload = to_old_payload) }
+ tx.instance_eval{
+ @hash = hash_from_payload(to_old_payload)
+ @payload = to_payload
+ }
if h['hash'] && (h['hash'] != tx.hash)
raise "Tx hash mismatch! Claimed: #{h['hash']}, Actual: #{tx.hash}" if do_raise
end
tx
end
@@ -525,8 +553,36 @@
def lexicographical_sort!
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")
+ nsequence = @in[input_idx].sequence
+
+ hash_outputs = Digest::SHA256.digest(Digest::SHA256.digest(@out.map{|o|o.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")
+ end
+
+ if (hash_type & SIGHASH_TYPE[:anyonecanpay]) != 0
+ 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
+
+ Digest::SHA256.digest( Digest::SHA256.digest( buf ) )
+ end
end
end
end