Class: Bitcoin::Builder::TxBuilder
- Inherits:
-
Object
- Object
- Bitcoin::Builder::TxBuilder
- 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
- #add_empty_script_sig_to_input(i) ⇒ Object
- #get_script_sig(inc, hash_type) ⇒ Object
-
#include_coinbase_data(i, inc) ⇒ Object
coinbase inputs don’t need to be signed, they only include the given
coinbase_data
. -
#initialize ⇒ TxBuilder
constructor
A new instance of TxBuilder.
-
#input {|c| ... } ⇒ Object
add an input to the transaction (see TxInBuilder).
-
#lock_time(n) ⇒ Object
specify tx lock_time.
-
#output(value = nil, recipient = nil, type = :address) {|c| ... } ⇒ Object
add an output to the transaction (see TxOutBuilder).
-
#randomize_outputs ⇒ Object
Randomize the outputs using SecureRandom.
- #sig_hash_and_all_keys_exist?(inc, sig_script) ⇒ Boolean
-
#sign_input(i, inc) ⇒ Object
Sign input number
i
with data from giveninc
object (a TxInBuilder). -
#tx(opts = {}) ⇒ Object
Create the transaction according to values specified via DSL.
-
#version(n) ⇒ Object
specify tx version.
Constructor Details
Instance Method Details
#add_empty_script_sig_to_input(i) ⇒ Object
242 243 244 245 246 247 248 249 250 |
# File 'lib/bitcoin/builder.rb', line 242 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, hash_type) ⇒ Object
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/bitcoin/builder.rb', line 252 def get_script_sig(inc, hash_type) if inc.multiple_keys? # multiple keys given, generate signature for each one sigs = inc.sign(@sig_hash) redeem_script = inc.instance_eval { @redeem_script } if 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*'), hash_type) end script_sig end |
#include_coinbase_data(i, inc) ⇒ Object
coinbase inputs don’t need to be signed, they only include the given coinbase_data
223 224 225 226 227 |
# File 'lib/bitcoin/builder.rb', line 223 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).
156 157 158 159 160 |
# File 'lib/bitcoin/builder.rb', line 156 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.
151 152 153 |
# File 'lib/bitcoin/builder.rb', line 151 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).
163 164 165 166 167 168 169 |
# File 'lib/bitcoin/builder.rb', line 163 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_outputs ⇒ Object
Randomize the outputs using SecureRandom
339 340 341 |
# File 'lib/bitcoin/builder.rb', line 339 def randomize_outputs @outs.sort_by! { SecureRandom.random_bytes(4).unpack('I')[0] } end |
#sig_hash_and_all_keys_exist?(inc, sig_script) ⇒ Boolean
229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/bitcoin/builder.rb', line 229 def sig_hash_and_all_keys_exist?(inc, sig_script) return false unless @sig_hash && inc.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.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). rubocop:disable CyclomaticComplexity,PerceivedComplexity
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 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/bitcoin/builder.rb', line 274 def sign_input(i, inc) return include_coinbase_data(i, inc) if @tx.in[i].coinbase? @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 hash_type = if inc.prev_out_forkid Script::SIGHASH_TYPE[:all] | Script::SIGHASH_TYPE[:forkid] else Script::SIGHASH_TYPE[:all] end # when a sig_script was found, generate the sig_hash to be signed if sig_script script = Script.new(sig_script) @sig_hash = if script.is_witness_v0_keyhash? @tx.signature_hash_for_witness_input(i, sig_script, inc.value) elsif inc.prev_out_forkid @tx.signature_hash_for_input( i, sig_script, hash_type, inc.value, inc.prev_out_forkid ) else @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 redeem_script = inc.instance_eval { @redeem_script } @tx.in[i].script_sig = Bitcoin::Script.pack_pushdata(redeem_script) if redeem_script else @tx.in[i].script_sig = get_script_sig(inc, hash_type) end # double-check that the script_sig is valid to spend the given prev_script if @prev_script && !inc.prev_out_forkid verified = if script.is_witness_v0_keyhash? @tx.verify_witness_input_signature(i, @prev_script, inc.value) else @tx.verify_input_signature(i, @prev_script) end raise 'Signature error' unless verified end elsif inc.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 |
#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. rubocop:disable CyclomaticComplexity,PerceivedComplexity
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 213 214 215 216 217 218 219 |
# File 'lib/bitcoin/builder.rb', line 182 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.
146 147 148 |
# File 'lib/bitcoin/builder.rb', line 146 def version(n) @tx.ver = n end |