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, #int_to_byte_array

Constructor Details

#initialize(options = {}) ⇒ Transaction

A new Transaction contains no inputs or outputs; these can be added with #add_input and #add_output.



97
98
99
100
101
102
103
104
105
106
# File 'lib/coin-op/bit/transaction.rb', line 97

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

Instance Attribute Details

#confirmationsObject (readonly)

Returns the value of attribute confirmations.



93
94
95
# File 'lib/coin-op/bit/transaction.rb', line 93

def confirmations
  @confirmations
end

#inputsObject (readonly)

Returns the value of attribute inputs.



93
94
95
# File 'lib/coin-op/bit/transaction.rb', line 93

def inputs
  @inputs
end

#lock_timeObject (readonly)

Returns the value of attribute lock_time.



93
94
95
# File 'lib/coin-op/bit/transaction.rb', line 93

def lock_time
  @lock_time
end

#nativeObject (readonly)

Returns the value of attribute native.



93
94
95
# File 'lib/coin-op/bit/transaction.rb', line 93

def native
  @native
end

#outputsObject (readonly)

Returns the value of attribute outputs.



93
94
95
# File 'lib/coin-op/bit/transaction.rb', line 93

def outputs
  @outputs
end

#versionObject (readonly)

Returns the value of attribute version.



93
94
95
# File 'lib/coin-op/bit/transaction.rb', line 93

def version
  @version
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.



14
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
# File 'lib/coin-op/bit/transaction.rb', line 14

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_hash, network: network)
  end

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

  inputs.each do |input_hash|
    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 if inputs

  transaction
end

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

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



48
49
50
# File 'lib/coin-op/bit/transaction.rb', line 48

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



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

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.



43
44
45
# File 'lib/coin-op/bit/transaction.rb', line 43

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.



358
359
360
361
362
363
364
# File 'lib/coin-op/bit/transaction.rb', line 358

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

#add_input(input, network: @network) ⇒ Object

Takes one of

  • an instance of Input

  • an instance of Output

  • a Hash describing an Output



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/coin-op/bit/transaction.rb', line 159

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

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

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

#add_output(output, network: @network) ⇒ Object

Takes either an Output or a Hash describing an output.



176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/coin-op/bit/transaction.rb', line 176

def add_output(output, network: @network)
  if output.is_a?(Output)
    output.set_transaction(self, @outputs.size)
  else
    output = Output.new(output.merge(transaction: self,
                                     index: @outputs.size),
                        network: network)
  end

  @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.



192
193
194
195
# File 'lib/coin-op/bit/transaction.rb', line 192

def binary_hash
  update_native
  @native.binary_hash
end

#change_valueObject

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



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

def change_value
  input_value - (output_value + fee_override)
end

#estimate_fee(tx_size: nil, network: @network, fee_per_kb: 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 +/- 40 bytes



298
299
300
301
302
# File 'lib/coin-op/bit/transaction.rb', line 298

def estimate_fee(tx_size: nil, network: @network, fee_per_kb: nil)
  unspents = inputs.map(&:output)
  Fee.estimate(unspents, outputs,
               network: network, tx_size: tx_size, fee_per_kb: fee_per_kb)
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.



306
307
308
# File 'lib/coin-op/bit/transaction.rb', line 306

def fee
  input_value - output_value rescue nil
end

#fee_overrideObject



290
291
292
# File 'lib/coin-op/bit/transaction.rb', line 290

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)


322
323
324
# File 'lib/coin-op/bit/transaction.rb', line 322

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

#hex_hashObject

Returns the transaction hash encoded as hex



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

def hex_hash
  update_native
  @native.hash
end

#input_valueObject

Total value of all inputs.



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

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.



339
340
341
342
# File 'lib/coin-op/bit/transaction.rb', line 339

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

#output_valueObject

Total value of all outputs.



311
312
313
314
315
316
317
318
# File 'lib/coin-op/bit/transaction.rb', line 311

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.



346
347
348
349
# File 'lib/coin-op/bit/transaction.rb', line 346

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.



276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/coin-op/bit/transaction.rb', line 276

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.



244
245
246
247
248
249
250
251
252
# File 'lib/coin-op/bit/transaction.rb', line 244

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.



220
221
222
223
224
225
226
227
228
229
230
# File 'lib/coin-op/bit/transaction.rb', line 220

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.



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

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

#to_json(*a) ⇒ Object



232
233
234
# File 'lib/coin-op/bit/transaction.rb', line 232

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:



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

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.



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

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.



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

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.



333
334
335
# File 'lib/coin-op/bit/transaction.rb', line 333

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