Class: Bitcoin::Builder::TxBuilder

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

Overview

DSL to create Bitcoin::Protocol::Tx used by Builder#build_tx.

tx = tx do |t|
  t.input do |i|
    i.prev_out prev_tx, 0
    i.signature_key key
  end
  t.output do |o|
    o.value 12345 # 0.00012345 BTC
    o.to key.addr
  end
end

Signs every input that has a signature key and where the previous outputs pk_script is known. If unable to sign, the resulting txin will include the #sig_hash that needs to be signed.

See TxInBuilder and TxOutBuilder for details on how to build in/outputs.

Instance Method Summary collapse

Constructor Details

#initializeTxBuilder

Returns a new instance of TxBuilder.



135
136
137
138
139
# File 'lib/bitcoin/builder.rb', line 135

def initialize
  @tx = P::Tx.new(nil)
  @tx.ver, @tx.lock_time = 1, 0
  @ins, @outs = [], []
end

Instance Method Details

#add_empty_script_sig_to_input(i) ⇒ Object



229
230
231
232
233
234
235
236
# File 'lib/bitcoin/builder.rb', line 229

def add_empty_script_sig_to_input(i)
  @tx.in[i].script_sig_length = 0
  @tx.in[i].script_sig = ""
  # add the sig_hash that needs to be signed, so it can be passed on to a signing device
  @tx.in[i].sig_hash = @sig_hash
  # add the address the sig_hash needs to be signed with as a convenience for the signing device
  @tx.in[i].sig_address = Script.new(@prev_script).get_address  if @prev_script
end

#get_script_sig(inc) ⇒ Object



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/bitcoin/builder.rb', line 238

def get_script_sig(inc)
  if inc.has_multiple_keys?
    # multiple keys given, generate signature for each one
    sigs = inc.sign(@sig_hash)
    if redeem_script = inc.instance_eval { @redeem_script }
      # when a redeem_script was specified, assume we spend a p2sh multisig script
      script_sig = Script.to_p2sh_multisig_script_sig(redeem_script, sigs)
    else
      # when no redeem_script is given, do a regular multisig spend
      script_sig = Script.to_multisig_script_sig(*sigs)
    end
  else
    # only one key given, generate signature and script_sig
    sig = inc.sign(@sig_hash)
    script_sig = Script.to_signature_pubkey_script(sig, [inc.key.pub].pack("H*"))
  end
  return script_sig
end

#include_coinbase_data(i, inc) ⇒ Object

coinbase inputs don’t need to be signed, they only include the given coinbase_data



213
214
215
216
217
# File 'lib/bitcoin/builder.rb', line 213

def include_coinbase_data i, inc
  script_sig = [inc.coinbase_data].pack("H*")
  @tx.in[i].script_sig_length = script_sig.bytesize
  @tx.in[i].script_sig = script_sig
end

#input {|c| ... } ⇒ Object

add an input to the transaction (see TxInBuilder).

Yields:

  • (c)


152
153
154
155
156
# File 'lib/bitcoin/builder.rb', line 152

def input
  c = TxInBuilder.new
  yield c
  @ins << c
end

#lock_time(n) ⇒ Object

specify tx lock_time. this is usually not necessary. defaults to 0.



147
148
149
# File 'lib/bitcoin/builder.rb', line 147

def lock_time n
  @tx.lock_time = n
end

#output {|c| ... } ⇒ Object

add an output to the transaction (see TxOutBuilder).

Yields:

  • (c)


159
160
161
162
163
# File 'lib/bitcoin/builder.rb', line 159

def output
  c = TxOutBuilder.new
  yield c
  @outs << c
end

#randomize_outputsObject

Randomize the outputs using SecureRandom



289
290
291
# File 'lib/bitcoin/builder.rb', line 289

def randomize_outputs
  @outs.sort_by!{ SecureRandom.random_bytes(4).unpack("I")[0] }
end

#sig_hash_and_all_keys_exist?(inc, sig_script) ⇒ Boolean

Returns:

  • (Boolean)


219
220
221
222
223
224
225
226
227
# File 'lib/bitcoin/builder.rb', line 219

def sig_hash_and_all_keys_exist?(inc, sig_script)
  return false unless @sig_hash && inc.has_keys?
  script = Bitcoin::Script.new(sig_script)
  return true if script.is_hash160? || script.is_pubkey?
  if script.is_multisig?
    return inc.has_multiple_keys? && inc.key.size >= script.get_signatures_required
  end
  raise "Script type must be hash160, pubkey or multisig"
end

#sign_input(i, inc) ⇒ Object

Sign input number i with data from given inc object (a TxInBuilder).



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/bitcoin/builder.rb', line 258

def sign_input i, inc
  if @tx.in[i].coinbase?
    include_coinbase_data(i, inc)
  else
    @prev_script = inc.instance_variable_get(:@prev_out_script)

    # get the signature script; use +redeem_script+ if given
    # (indicates spending a p2sh output), otherwise use the prev_script
    sig_script = inc.instance_eval { @redeem_script }
    sig_script ||= @prev_script

    # when a sig_script was found, generate the sig_hash to be signed
    @sig_hash = @tx.signature_hash_for_input(i, sig_script)  if sig_script

    # when there is a sig_hash and one or more signature_keys were specified
    if sig_hash_and_all_keys_exist?(inc, sig_script)
      # add the script_sig to the txin
      @tx.in[i].script_sig = get_script_sig(inc)

      # double-check that the script_sig is valid to spend the given prev_script
      raise "Signature error"  if @prev_script && !@tx.verify_input_signature(i, @prev_script)
    elsif inc.has_multiple_keys?
      raise "Keys missing for multisig signing"
    else
      # no sig_hash, add an empty script_sig.
      add_empty_script_sig_to_input(i)
    end
  end
end

#tx(opts = {}) ⇒ Object

Create the transaction according to values specified via DSL. Sign each input that has a signature key specified. If there is no key, store the sig_hash in the input, so it can easily be signed later.

When :change_address and :input_value options are given, it will automatically create a change output sending the remaining funds to the given address. The :leave_fee option can be used in this case to specify a tx fee that should be left unclaimed by the change output.



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/bitcoin/builder.rb', line 175

def tx opts = {}
  if opts[:change_address] && !opts[:input_value]
    raise "Must give 'input_value' when auto-generating change output!"
  end
  @ins.each {|i| @tx.add_in(i.txin) }
  @outs.each {|o| @tx.add_out(o.txout) }

  if opts[:change_address]
    output_value = @tx.out.map(&:value).inject(:+)
    change_value = opts[:input_value] - output_value
    if opts[:leave_fee]
      if change_value >= @tx.minimum_block_fee
        change_value -= @tx.minimum_block_fee
      else
        change_value = 0
      end
    end
    if change_value > 0
      script = Script.to_address_script(opts[:change_address])
      @tx.add_out(P::TxOut.new(change_value, script))
    end
  end

  @ins.each_with_index do |inc, i|
    sign_input(i, inc)
  end

  # run our tx through an encode/decode cycle to make sure that the binary format is sane
  raise "Payload Error"  unless P::Tx.new(@tx.to_payload).to_payload == @tx.to_payload
  @tx.instance_eval do
    @payload = to_payload
    @hash = hash_from_payload(@payload)
  end

  @tx
end

#version(n) ⇒ Object

specify tx version. this is usually not necessary. defaults to 1.



142
143
144
# File 'lib/bitcoin/builder.rb', line 142

def version n
  @tx.ver = n
end