Class: CoinOp::Bit::Transaction

Inherits:
Object
  • Object
show all
Includes:
Encodings
Defined in:
lib/coin-op/bit/transaction.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Encodings

#base58, #decode_base58, #decode_hex, #hex

Constructor Details

#initialize(options = {}) ⇒ Transaction

A new Transaction contains no inputs or outputs; these can be added with #add_input and #add_output. FIXME: version and locktime options are ignored here.



101
102
103
104
105
106
107
108
# File 'lib/coin-op/bit/transaction.rb', line 101

def initialize(options={})
  @native = Bitcoin::Protocol::Tx.new
  @inputs = []
  @outputs = []
  @network = options[:network]
  @fee_override = options[:fee]
  @confirmations = options[:confirmations]
end

Instance Attribute Details

#confirmationsObject (readonly)

Returns the value of attribute confirmations.



96
97
98
# File 'lib/coin-op/bit/transaction.rb', line 96

def confirmations
  @confirmations
end

#inputsObject (readonly)

Returns the value of attribute inputs.



96
97
98
# File 'lib/coin-op/bit/transaction.rb', line 96

def inputs
  @inputs
end

#nativeObject (readonly)

Returns the value of attribute native.



96
97
98
# File 'lib/coin-op/bit/transaction.rb', line 96

def native
  @native
end

#outputsObject (readonly)

Returns the value of attribute outputs.



96
97
98
# File 'lib/coin-op/bit/transaction.rb', line 96

def outputs
  @outputs
end

Class Method Details

.build {|transaction| ... } ⇒ Object

Deprecated. Easier to use Transaction.from_data

Yields:

  • (transaction)


7
8
9
10
11
# File 'lib/coin-op/bit/transaction.rb', line 7

def self.build(&block)
  transaction = self.new
  yield transaction
  transaction
end

.data(data, network:) ⇒ Object Also known as: from_data

Construct a Transaction from a data structure of nested Hashes and Arrays.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/coin-op/bit/transaction.rb', line 15

def self.data(data, network:)
  version, lock_time, fee, inputs, outputs, confirmations =
    data.values_at :version, :lock_time, :fee, :inputs, :outputs, :confirmations

  transaction = self.new(
    :fee => fee,
    :version => version, :lock_time => lock_time,
    :confirmations => confirmations,
    network: network
  )

  outputs.each do |output_hash|
    transaction.add_output(Output.new(output_hash, network: network))
  end

  #FIXME: we're not handling sig_scripts for already signed inputs.

  if inputs
    # TODO: use #each instead of #each_with_index
    inputs.each_with_index do |input_hash, index|
      transaction.add_input(input_hash, network: network)

      ## FIXME: verify that the supplied and computed sig_hashes match
      #puts :sig_hashes_match => (data[:sig_hash] == input.sig_hash)
    end
  end

  transaction
end

.hex(hex) ⇒ Object Also known as: from_hex

Construct a Transaction from a hex representation of the raw bytes.



51
52
53
# File 'lib/coin-op/bit/transaction.rb', line 51

def self.hex(hex)
  self.from_bytes CoinOp::Encodings.decode_hex(hex)
end

.native(tx) ⇒ Object Also known as: from_native

Construct a transaction from an instance of ::Bitcoin::Protocol::Tx



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/coin-op/bit/transaction.rb', line 56

def self.native(tx)
  transaction = self.new()
  # TODO: reconsider use of instance_eval
  transaction.instance_eval do
    @native = tx
    tx.inputs.each_with_index do |input, i|
      # We use SparseInput because it does not require the retrieval
      # of the previous output.  Its functionality should probably be
      # folded into the Input class.
      @inputs << SparseInput.new(input.prev_out, input.prev_out_index)
    end
    tx.outputs.each_with_index do |output, i|
      @outputs << Output.new(
        :transaction => transaction,
        :index => i,
        :value => output.value,
        :script => {:blob => output.pk_script}
      )
    end
  end

  report = transaction.validate_syntax
  unless report[:valid] == true
    raise "Invalid syntax:  #{report[:error].to_json}"
  end
  transaction
end

.raw(raw_tx) ⇒ Object Also known as: from_bytes

Construct a Transaction from raw bytes.



46
47
48
# File 'lib/coin-op/bit/transaction.rb', line 46

def self.raw(raw_tx)
  self.native ::Bitcoin::Protocol::Tx.new(raw_tx)
end

Instance Method Details

#add_change(address, metadata = {}) ⇒ Object

Add an output to receive change for this transaction. Takes a bitcoin address and optional metadata Hash.



362
363
364
365
366
367
368
# File 'lib/coin-op/bit/transaction.rb', line 362

def add_change(address, ={})
  add_output(
    :value => change_value,
    :address => address,
    :metadata => {:memo => "change"}.merge()
  )
end

#add_input(input, network:) ⇒ Object

Takes one of

  • an instance of Input

  • an instance of Output

  • a Hash describing an Output



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/coin-op/bit/transaction.rb', line 161

def add_input(input, network:)
  # TODO: allow specifying prev_tx and index with a Hash.
  # Possibly stop using SparseInput.

  unless input.is_a?(Input)
    input = Input.new input.merge(
      transaction: self,
      index: @inputs.size,
      network: network
    )
  end

  @inputs << input
  self.update_native do |native|
    native.add_in input.native
  end
  input
end

#add_output(output) ⇒ Object

Takes either an Output or a Hash describing an output.



181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/coin-op/bit/transaction.rb', line 181

def add_output(output)
  unless output.is_a? Output
    output = Output.new(output, network: @network)
  end

  index = @outputs.size
  # TODO: stop using set_transaction and just pass self to Output.new
  # Then remove output.set_transaction
  output.set_transaction self, index
  @outputs << output
  self.update_native do |native|
    native.add_out(output.native)
  end
end

#binary_hashObject

Returns the transaction hash as a string of bytes.



197
198
199
200
# File 'lib/coin-op/bit/transaction.rb', line 197

def binary_hash
  update_native
  @native.binary_hash
end

#change_valueObject

Returns the value that should be assigned to a change output.



356
357
358
# File 'lib/coin-op/bit/transaction.rb', line 356

def change_value
  input_value - (output_value + fee_override)
end

#estimate_fee(tx_size = nil) ⇒ Object

Estimate the fee in satoshis for this transaction. Takes an optional tx_size argument because it is impossible to determine programmatically the size of the scripts used to create P2SH outputs. Rough testing of the size of a 2of3 multisig p2sh input: 297



303
304
305
306
# File 'lib/coin-op/bit/transaction.rb', line 303

def estimate_fee(tx_size=nil)
  unspents = inputs.map(&:output)
  Fee.estimate(unspents, outputs, tx_size)
end

#feeObject

Returns the transaction fee computed from the actual input and output values, as opposed to the requested override fee or the estimated fee.



310
311
312
# File 'lib/coin-op/bit/transaction.rb', line 310

def fee
  input_value - output_value rescue nil
end

#fee_overrideObject



295
296
297
# File 'lib/coin-op/bit/transaction.rb', line 295

def fee_override
  @fee_override || self.estimate_fee(network: @network)
end

#funded?Boolean

Are the currently selected inputs sufficient to cover the current outputs and the desired fee?

Returns:

  • (Boolean)


326
327
328
# File 'lib/coin-op/bit/transaction.rb', line 326

def funded?
  input_value >= (output_value + fee_override)
end

#hex_hashObject

Returns the transaction hash encoded as hex



203
204
205
206
# File 'lib/coin-op/bit/transaction.rb', line 203

def hex_hash
  update_native
  @native.hash
end

#input_valueObject

Total value of all inputs.



331
332
333
# File 'lib/coin-op/bit/transaction.rb', line 331

def input_value
  inputs.inject(0) { |sum, input| sum += input.output.value }
end

#input_value_for(addresses) ⇒ Object

Takes a set of Bitcoin addresses and returns the value expressed in the inputs for this transaction.



343
344
345
346
# File 'lib/coin-op/bit/transaction.rb', line 343

def input_value_for(addresses)
  own = inputs.select { |input| addresses.include?(input.output.address) }
  own.inject(0) { |sum, input| input.output.value }
end

#lock_timeObject



212
213
214
# File 'lib/coin-op/bit/transaction.rb', line 212

def lock_time
  @native.lock_time
end

#output_valueObject

Total value of all outputs.



315
316
317
318
319
320
321
322
# File 'lib/coin-op/bit/transaction.rb', line 315

def output_value
  total = 0
  @outputs.each do |output|
    total += output.value
  end

  total
end

#output_value_for(addresses) ⇒ Object

Takes a set of Bitcoin addresses and returns the value expressed in the outputs for this transaction.



350
351
352
353
# File 'lib/coin-op/bit/transaction.rb', line 350

def output_value_for(addresses)
  own = outputs.select { |output| addresses.include?(output.address) }
  own.inject(0) { |sum, output| output.value }
end

#set_script_sigs(*input_args, &block) ⇒ Object

A convenience method for authorizing inputs in a generic manner. Rather than iterating over the inputs manually, the user can provide this method with an array of values and a block that knows what to do with the values.

For example, if you happen to have the script sigs precomputed for some strange reason, you could do this:

tx.set_script_sigs sig_array do |input, sig|
  sig
end

More realistically, if you have an array of the keypairs corresponding to the inputs:

tx.set_script_sigs keys do |input, key|
  sig_hash = tx.sig_hash(input)
  key.sign(sig_hash)
end

Each element of the array may be an array, which allows for easy handling of multisig situations.



281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/coin-op/bit/transaction.rb', line 281

def set_script_sigs(*input_args, &block)
  # No sense trying to authorize when the transaction isn't usable.
  report = validate_syntax
  unless report[:valid] == true
    raise "Invalid syntax:  #{report[:errors].to_json}"
  end

  # Array#zip here allows us to iterate over the inputs in lockstep with any
  # number of sets of values.
  self.inputs.zip(*input_args) do |input, *input_arg|
    input.script_sig = yield input, *input_arg
  end
end

#sig_hash(input, script = nil) ⇒ Object

Compute the digest for a given input. This is the value that is actually signed in a transaction. e.g. I want to spend UTXO0 - I provide the UTXO0 as an input.

I provide as a script the script to which UTXO0 was paid.

If UTX0 was paid to a P2SH address, the script here needs to be the correct m-of-n structure, which may well not be part of the input.output object

  • This works here because we default to 2-of-3 and have consistent ordering

When we support multiple m-of-n’s, this may become a prollem.



249
250
251
252
253
254
255
256
257
# File 'lib/coin-op/bit/transaction.rb', line 249

def sig_hash(input, script=nil)
  # We only allow SIGHASH_ALL at this time
  # https://en.bitcoin.it/wiki/OP_CHECKSIG#Hashtype_SIGHASH_ALL_.28default.29

  prev_out = input.output
  script ||= prev_out.script

  @native.signature_hash_for_input(input.index, script.to_blob)
end

#to_hashObject

Returns a custom data structure representing the full transaction. Typically used only by #to_json.



225
226
227
228
229
230
231
232
233
234
235
# File 'lib/coin-op/bit/transaction.rb', line 225

def to_hash
  {
    :confirmations => self.confirmations.nil? ? 0 : self.confirmations,
    :version => self.version,
    :lock_time => self.lock_time,
    :hash => self.hex_hash,
    :fee => self.fee,
    :inputs => self.inputs,
    :outputs => self.outputs
  }
end

#to_hexObject

Returns the transaction payload encoded as hex. This value can be used by other bitcoin tools for publishing to the network.



218
219
220
221
# File 'lib/coin-op/bit/transaction.rb', line 218

def to_hex
  payload = self.native.to_payload
  CoinOp::Encodings.hex(payload)
end

#to_json(*a) ⇒ Object



237
238
239
# File 'lib/coin-op/bit/transaction.rb', line 237

def to_json(*a)
  self.to_hash.to_json(*a)
end

#update_native {|@native| ... } ⇒ Object

Update the “native” bitcoin-ruby instances for the transaction and all its inputs. Will be removed when we rework the wrapper classes to be lazy, rather than eager.

Yields:



113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/coin-op/bit/transaction.rb', line 113

def update_native
  yield @native if block_given?
  @native = Bitcoin::Protocol::Tx.new(@native.to_payload)
  @inputs.each_with_index do |input, i|
    native = @native.inputs[i]
    # Using instance_eval here because I really don't want to expose
    # Input#native=.  As we consume more and more of the native
    # functionality, we can dispense with such ugliness.
    input.instance_eval do
      @native = native
    end
    # TODO: is this re-nativization necessary for outputs, too?
  end
end

#validate_script_sigsObject

Verify that the script_sigs for all inputs are valid.



141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/coin-op/bit/transaction.rb', line 141

def validate_script_sigs
  bad_inputs = []
  valid = true
  @inputs.each_with_index do |input, index|
    # TODO: confirm whether we need to mess with the block_timestamp arg
    unless self.native.verify_input_signature(index, input.output.transaction.native)
      valid = false
      bad_inputs << index
    end

  end
  {:valid => valid, :inputs => bad_inputs}
end

#validate_syntaxObject

Validate that the transaction is plausibly signable.



133
134
135
136
137
138
# File 'lib/coin-op/bit/transaction.rb', line 133

def validate_syntax
  update_native
  validator = Bitcoin::Validation::Tx.new(@native, nil)
  valid = validator.validate :rules => [:syntax]
  {:valid => valid, :error => validator.error}
end

#value_for(addresses) ⇒ Object

Takes a set of Bitcoin addresses and returns the net change in value expressed in this transaction.



337
338
339
# File 'lib/coin-op/bit/transaction.rb', line 337

def value_for(addresses)
  output_value_for(addresses) - input_value_for(addresses)
end

#versionObject



208
209
210
# File 'lib/coin-op/bit/transaction.rb', line 208

def version
  @native.ver
end