Class: Bitcoin::Protocol::Tx

Inherits:
Object
  • Object
show all
Defined in:
lib/bitcoin/protocol/tx.rb

Direct Known Subclasses

Storage::Models::Tx

Constant Summary collapse

SIGHASH_TYPE =
{ all: 1, none: 2, single: 3, anyonecanpay: 128 }
DEFAULT_BLOCK_PRIORITY_SIZE =
27000

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data = nil) ⇒ Tx

create tx from raw binary data



42
43
44
45
# File 'lib/bitcoin/protocol/tx.rb', line 42

def initialize(data=nil)
  @ver, @lock_time, @in, @out = 1, 0, [], []
  parse_data_from_io(data) if data
end

Instance Attribute Details

#hashObject (readonly)

transaction hash



11
12
13
# File 'lib/bitcoin/protocol/tx.rb', line 11

def hash
  @hash
end

#inObject (readonly) Also known as: inputs

inputs (Array of TxIn)



14
15
16
# File 'lib/bitcoin/protocol/tx.rb', line 14

def in
  @in
end

#lock_timeObject

lock time



26
27
28
# File 'lib/bitcoin/protocol/tx.rb', line 26

def lock_time
  @lock_time
end

#outObject (readonly) Also known as: outputs

outputs (Array of TxOut)



17
18
19
# File 'lib/bitcoin/protocol/tx.rb', line 17

def out
  @out
end

#payloadObject (readonly)

raw protocol payload



20
21
22
# File 'lib/bitcoin/protocol/tx.rb', line 20

def payload
  @payload
end

#verObject

version (usually 1)



23
24
25
# File 'lib/bitcoin/protocol/tx.rb', line 23

def ver
  @ver
end

Class Method Details

.binary_from_hash(h) ⇒ Object

convert ruby hash to raw binary



204
# File 'lib/bitcoin/protocol/tx.rb', line 204

def self.binary_from_hash(h); from_hash(h).to_payload; end

.binary_from_json(json_string) ⇒ Object

convert json representation to raw binary



210
# File 'lib/bitcoin/protocol/tx.rb', line 210

def self.binary_from_json(json_string); from_json(json_string).to_payload; end

.from_file(path) ⇒ Object

read binary block from a file



213
# File 'lib/bitcoin/protocol/tx.rb', line 213

def self.from_file(path); new( Bitcoin::Protocol.read_binary_file(path) ); end

.from_hash(h) ⇒ Object

parse ruby hash (see also #to_hash)



194
195
196
197
198
199
200
201
# File 'lib/bitcoin/protocol/tx.rb', line 194

def self.from_hash(h)
  tx = new(nil)
  tx.ver, tx.lock_time = *h.values_at('ver', 'lock_time')
  h['in'] .each{|input|   tx.add_in  TxIn.from_hash(input)   }
  h['out'].each{|output|  tx.add_out TxOut.from_hash(output) }
  tx.instance_eval{ @hash = hash_from_payload(@payload = to_payload) }
  tx
end

.from_json(json_string) ⇒ Object

parse json representation



207
# File 'lib/bitcoin/protocol/tx.rb', line 207

def self.from_json(json_string); from_hash( JSON.load(json_string) ); end

.from_json_file(path) ⇒ Object

read json block from a file



216
# File 'lib/bitcoin/protocol/tx.rb', line 216

def self.from_json_file(path); from_json( Bitcoin::Protocol.read_binary_file(path) ); end

Instance Method Details

#==(other) ⇒ Object

compare to another tx



32
33
34
# File 'lib/bitcoin/protocol/tx.rb', line 32

def ==(other)
  @hash == other.hash
end

#add_in(input) ⇒ Object

add an input



54
# File 'lib/bitcoin/protocol/tx.rb', line 54

def add_in(input); (@in ||= []) << input; end

#add_out(output) ⇒ Object

add an output



57
# File 'lib/bitcoin/protocol/tx.rb', line 57

def add_out(output); (@out ||= []) << output; end

#binary_hashObject

return the tx hash in binary format



37
38
39
# File 'lib/bitcoin/protocol/tx.rb', line 37

def binary_hash
  [@hash].pack("H*").reverse
end

#calculate_minimum_fee(allow_free = true, mode = :block) ⇒ Object



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/bitcoin/protocol/tx.rb', line 227

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

  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 ? 1_000 : DEFAULT_BLOCK_PRIORITY_SIZE - 1_000)
  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
    outputs.each{|output| (min_fee = base_fee; break) if output.value < Bitcoin::CENT }
  end

  min_fee = Bitcoin::network[:max_money] unless min_fee.between?(0, Bitcoin::network[:max_money])
  min_fee
end

#hash_from_payload(payload) ⇒ Object Also known as: generate_hash

generate the tx hash for given payload in hex format



48
49
50
# File 'lib/bitcoin/protocol/tx.rb', line 48

def hash_from_payload(payload)
  Digest::SHA256.digest(Digest::SHA256.digest( payload )).reverse_hth
end

#is_coinbase?Boolean

Returns:

  • (Boolean)


253
254
255
# File 'lib/bitcoin/protocol/tx.rb', line 253

def is_coinbase?
  inputs.size == 1 and inputs.first.coinbase?
end

#minimum_block_feeObject



225
# File 'lib/bitcoin/protocol/tx.rb', line 225

def minimum_block_fee; calculate_minimum_fee(allow_free=true, :block); end

#minimum_relay_feeObject



224
# File 'lib/bitcoin/protocol/tx.rb', line 224

def minimum_relay_fee; calculate_minimum_fee(allow_free=true, :relay); end

#parse_data_from_io(data) ⇒ Object Also known as: parse_data

parse raw binary data



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/bitcoin/protocol/tx.rb', line 60

def parse_data_from_io(data)
  buf = data.is_a?(String) ? StringIO.new(data) : data
  payload_start = buf.pos

  @ver = buf.read(4).unpack("V")[0]

  in_size = Protocol.unpack_var_int_from_io(buf)
  @in = []
  in_size.times{ @in << TxIn.from_io(buf) }

  out_size = Protocol.unpack_var_int_from_io(buf)
  @out = []
  out_size.times{ @out << TxOut.from_io(buf) }

  @lock_time = buf.read(4).unpack("V")[0]

  payload_end = buf.pos;
  buf.seek(payload_start)
  @payload = buf.read( payload_end-payload_start )
  @hash = hash_from_payload(@payload)

  if buf.eof?
    true
  else
    data.is_a?(StringIO) ? buf : buf.read
  end
end

#signature_hash_for_input(input_idx, outpoint_tx, script_pubkey = nil, hash_type = nil, drop_sigs = nil, script = nil) ⇒ Object

generate a signature hash for input input_idx. either pass the outpoint_tx or the script_pubkey directly.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/bitcoin/protocol/tx.rb', line 105

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

  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 = 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 & 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
  }

  pout = @out.map(&:to_payload)
  in_size, out_size = Protocol.pack_var_int(@in.size), Protocol.pack_var_int(@out.size)

  case (hash_type & 0x1f)
  when SIGHASH_TYPE[:none]
    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)
  end

  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

#to_hash(options = {}) ⇒ Object

convert to ruby hash (see also #from_hash)



170
171
172
173
174
175
176
177
178
179
180
# File 'lib/bitcoin/protocol/tx.rb', line 170

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{|i| i.to_hash(options) },
    'out' => @out.map{|o| o.to_hash(options) }
  }
  h
end

#to_json(options = {:space => ''}, *a) ⇒ Object

generates rawblock json as seen in the block explorer.



183
184
185
# File 'lib/bitcoin/protocol/tx.rb', line 183

def to_json(options = {:space => ''}, *a)
  JSON.pretty_generate( to_hash(options), options )
end

#to_json_file(path) ⇒ Object

write json representation to a file (see also #to_json)



189
190
191
# File 'lib/bitcoin/protocol/tx.rb', line 189

def to_json_file(path)
  File.open(path, 'wb'){|f| f.print to_json; }
end

#to_payloadObject

output transaction in raw binary format



91
92
93
94
95
96
97
98
# File 'lib/bitcoin/protocol/tx.rb', line 91

def 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")
end

#validator(store, block = nil) ⇒ Object



218
219
220
# File 'lib/bitcoin/protocol/tx.rb', line 218

def validator(store, block = nil)
  @validator ||= Bitcoin::Validation::Tx.new(self, store, block)
end

#verify_input_signature(in_idx, outpoint_tx, block_timestamp = Time.now.to_i) ⇒ Object

verify input signature in_idx against the corresponding output in outpoint_tx



155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/bitcoin/protocol/tx.rb', line 155

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(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