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