lib/bitcoin/protocol/tx.rb in bitcoin-ruby-0.0.1 vs lib/bitcoin/protocol/tx.rb in bitcoin-ruby-0.0.2
- old
+ new
@@ -1,5 +1,7 @@
+# encoding: ascii-8bit
+
require 'bitcoin/script'
module Bitcoin
module Protocol
@@ -37,138 +39,151 @@
end
# create tx from raw binary +data+
def initialize(data=nil)
@ver, @lock_time, @in, @out = 1, 0, [], []
- parse_data(data) if data
+ 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.unpack("H*")[0]
+ Digest::SHA256.digest(Digest::SHA256.digest( payload )).reverse_hth
end
alias generate_hash hash_from_payload
# add an input
def add_in(input); (@in ||= []) << input; end
# add an output
def add_out(output); (@out ||= []) << output; end
# parse raw binary data
- def parse_data(data)
- @ver = data.unpack("I")[0]
- idx = 4
- in_size, tmp = Protocol.unpack_var_int(data[idx..-1])
- idx += data[idx..-1].bytesize-tmp.bytesize
- # raise "unkown transaction version: #{@ver}" unless @ver == 1
+ def parse_data_from_io(data)
+ buf = data.is_a?(String) ? StringIO.new(data) : data
+ payload_start = buf.pos
- @in = (0...in_size).map{
- txin = TxIn.new
- idx += txin.parse_data(data[idx..-1])
- txin
- }
+ @ver = buf.read(4).unpack("V")[0]
- out_size, tmp = Protocol.unpack_var_int(data[idx..-1])
- idx += data[idx..-1].bytesize-tmp.bytesize
+ in_size = Protocol.unpack_var_int_from_io(buf)
+ @in = []
+ in_size.times{ @in << TxIn.from_io(buf) }
- @out = (0...out_size).map{
- txout = TxOut.new
- idx += txout.parse_data(data[idx..-1])
- txout
- }
+ out_size = Protocol.unpack_var_int_from_io(buf)
+ @out = []
+ out_size.times{ @out << TxOut.from_io(buf) }
- @lock_time = data[idx...idx+=4].unpack("I")[0]
+ @lock_time = buf.read(4).unpack("V")[0]
- @payload = data[0...idx]
+ payload_end = buf.pos;
+ buf.seek(payload_start)
+ @payload = buf.read( payload_end-payload_start )
@hash = hash_from_payload(@payload)
- if data[idx] == nil
- true # reached the end.
+ if buf.eof?
+ true
else
- data[idx..-1] # rest of buffer.
+ data.is_a?(StringIO) ? buf : buf.read
end
end
+ alias :parse_data :parse_data_from_io
+
# output transaction in raw binary format
def to_payload
- pin = @in.map(&:to_payload).join
- pout = @out.map(&:to_payload).join
+ pin = ""
+ @in.each{|input| pin << input.to_payload }
+ pout = ""
+ @out.each{|output| pout << output.to_payload }
- in_size, out_size = Protocol.pack_var_int(@in.size), Protocol.pack_var_int(@out.size)
- [[@ver].pack("I"), in_size, pin, out_size, pout, [@lock_time].pack("I")].join
+ [@ver].pack("V") << Protocol.pack_var_int(@in.size) << pin << Protocol.pack_var_int(@out.size) << pout << [@lock_time].pack("V")
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, outpoint_tx, script_pubkey=nil, hash_type=nil, drop_sigs=nil, script=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 ||= 1 # 1: ALL, 2: NONE, 3: SINGLE
+ 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
script_pubkey ||= outpoint_tx.out[ input.prev_out_index ].pk_script
- script_pubkey = Bitcoin::Script.binary_from_string(script) if script # force this string a script
- script_pubkey = Bitcoin::Script.drop_signatures(script_pubkey, drop_sigs) if drop_sigs # array of signature to drop
+ script_pubkey = script if script # force binary aa script
+ script_pubkey = Bitcoin::Script.drop_signatures(script_pubkey, drop_sigs) if drop_sigs # array of signature to drop (slow)
+ #p Bitcoin::Script.new(script_pubkey).to_string
input.to_payload(script_pubkey)
else
- case hash_type
- when 2; input.to_payload("", "\x00\x00\x00\x00")
- else; input.to_payload("")
+ 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("")
end
end
- }.join
+ }
- pout = @out.map(&:to_payload).join
+ pout = @out.map(&:to_payload)
+ in_size, out_size = Protocol.pack_var_int(@in.size), Protocol.pack_var_int(@out.size)
- case hash_type
- when 2
+ case (hash_type & 0x1f)
+ when SIGHASH_TYPE[:none]
pout = ""
- in_size, out_size = Protocol.pack_var_int(@in.size), Protocol.pack_var_int(0)
- else
- in_size, out_size = Protocol.pack_var_int(@in.size), Protocol.pack_var_int(@out.size)
+ 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)
end
- buf = [ [@ver].pack("I"), in_size, pin, out_size, pout, [@lock_time, hash_type].pack("II") ].join
+ if (hash_type & SIGHASH_TYPE[:anyonecanpay]) != 0
+ in_size, pin = Protocol.pack_var_int(1), [ 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 ) )
end
# verify input signature +in_idx+ against the corresponding
# output in +outpoint_tx+
- def verify_input_signature(in_idx, outpoint_tx)
+ def verify_input_signature(in_idx, outpoint_tx, block_timestamp=Time.now.to_i)
outpoint_idx = @in[in_idx].prev_out_index
script_sig = @in[in_idx].script_sig
script_pubkey = outpoint_tx.out[outpoint_idx].pk_script
script = script_sig + script_pubkey
- Bitcoin::Script.new(script).run do |pubkey,sig,hash_type,drop_sigs,script|
+ Bitcoin::Script.new(script).run(block_timestamp) do |pubkey,sig,hash_type,drop_sigs,script|
# this IS the checksig callback, must return true/false
hash = signature_hash_for_input(in_idx, outpoint_tx, nil, hash_type, drop_sigs, script)
#hash = signature_hash_for_input(in_idx, nil, script_pubkey, hash_type, drop_sigs, script)
Bitcoin.verify_signature( hash, sig, pubkey.unpack("H*")[0] )
end
end
# convert to ruby hash (see also #from_hash)
- def to_hash
+ def to_hash(options = {})
@hash ||= hash_from_payload(to_payload)
- {
+ h = {
'hash' => @hash, 'ver' => @ver,
'vin_sz' => @in.size, 'vout_sz' => @out.size,
'lock_time' => @lock_time, 'size' => (@payload ||= to_payload).bytesize,
- 'in' => @in.map(&:to_hash),
- 'out' => @out.map(&:to_hash)
+ 'in' => @in.map{|i| i.to_hash(options) },
+ 'out' => @out.map{|o| o.to_hash(options) }
}
+ h
end
# generates rawblock json as seen in the block explorer.
def to_json(options = {:space => ''}, *a)
- JSON.pretty_generate( to_hash, options )
+ JSON.pretty_generate( to_hash(options), options )
end
# write json representation to a file
# (see also #to_json)
def to_json_file(path)
@@ -197,9 +212,47 @@
# read binary block from a file
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
- end
+ def validator(store, block = nil)
+ @validator ||= Bitcoin::Validation::Tx.new(self, store, block)
+ end
+
+ def minimum_relay_fee; calculate_minimum_fee(1_000, true, :relay); end
+ def minimum_block_fee; calculate_minimum_fee(1_000, true, :block); end
+
+ def calculate_minimum_fee(block_size=1, allow_free=true, mode=:block)
+ base_fee = (mode == :relay) ? Bitcoin.network[:min_relay_tx_fee] : Bitcoin.network[:min_tx_fee]
+ tx_size = to_payload.bytesize
+ new_block_size = block_size + tx_size
+ min_fee = (1 + tx_size / 1_000) * base_fee
+
+ if allow_free
+ if block_size == 1
+ min_fee = 0 if tx_size < 10_000
+ else
+ min_fee = 0 if new_block_size < 27_000
+ end
+ end
+
+ if min_fee < base_fee
+ outputs.each{|output| (min_fee = base_fee; break) if output.value < Bitcoin::CENT }
+ end
+
+ if block_size != 1 && new_block_size >= (Bitcoin::MAX_BLOCK_SIZE_GEN/2)
+ #return Bitcoin::network[:max_money] if new_block_size >= Bitcoin::MAX_BLOCK_SIZE_GEN
+ min_fee *= Bitcoin::MAX_BLOCK_SIZE_GEN / (Bitcoin::MAX_BLOCK_SIZE_GEN - new_block_size)
+ end
+
+ min_fee = Bitcoin::network[:max_money] unless min_fee.between?(0, Bitcoin::network[:max_money])
+ min_fee
+ end
+
+ def is_coinbase?
+ inputs.size == 1 and inputs.first.coinbase?
+ end
+
+ end
end
end