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.



133
134
135
136
137
# File 'lib/bitcoin/builder.rb', line 133

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



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

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



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

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



215
216
217
218
219
# File 'lib/bitcoin/builder.rb', line 215

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)


150
151
152
153
154
# File 'lib/bitcoin/builder.rb', line 150

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.



145
146
147
# File 'lib/bitcoin/builder.rb', line 145

def lock_time n
  @tx.lock_time = n
end

#output(value = nil, recipient = nil, type = :address) {|c| ... } ⇒ Object

add an output to the transaction (see TxOutBuilder).

Yields:

  • (c)


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

def output value = nil, recipient = nil, type = :address
  c = TxOutBuilder.new
  c.value(value)  if value
  c.to(recipient, type)  if recipient
  yield c  if block_given?
  @outs << c
end

#randomize_outputsObject

Randomize the outputs using SecureRandom



302
303
304
# File 'lib/bitcoin/builder.rb', line 302

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)


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

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? || script.is_witness_v0_keyhash? || (Bitcoin.namecoin? && script.is_namecoin?)
  if script.is_multisig?
    return inc.has_multiple_keys? && inc.key.size >= script.get_signatures_required
  end
  raise "Script type must be hash160, pubkey, p2wpkh or multisig"
end

#sign_input(i, inc) ⇒ Object

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



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
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/bitcoin/builder.rb', line 260

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
    if sig_script
      script = Script.new(sig_script)
      if script.is_witness_v0_keyhash?
        @sig_hash = @tx.signature_hash_for_witness_input(i, sig_script, inc.value)
      else
        @sig_hash = @tx.signature_hash_for_input(i, sig_script)
      end
    end

    # 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
      if script.is_witness_v0_keyhash? # for p2wpkh
        @tx.in[i].script_witness.stack << inc.sign(@sig_hash) + [Script::SIGHASH_TYPE[:all]].pack("C")
        @tx.in[i].script_witness.stack << inc.key.pub.htb
      else
        @tx.in[i].script_sig = get_script_sig(inc)
      end
      # 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
211
212
# File 'lib/bitcoin/builder.rb', line 175

def tx opts = {}
  return @tx  if @tx.hash

  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(:+) || 0
    change_value = opts[:input_value] - output_value
    if opts[:leave_fee]
      fee = @tx.minimum_block_fee + (opts[:extra_fee] || 0)
      if change_value >= fee
        change_value -= 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_witness_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.



140
141
142
# File 'lib/bitcoin/builder.rb', line 140

def version n
  @tx.ver = n
end