lib/bitcoin/builder.rb in bitcoin-ruby-0.0.5 vs lib/bitcoin/builder.rb in bitcoin-ruby-0.0.6
- old
+ new
@@ -16,14 +16,14 @@
end
alias :blk :build_block
# build a Bitcoin::Protocol::Tx.
# see TxBuilder for details.
- def build_tx
+ def build_tx opts = {}
c = TxBuilder.new
yield c
- c.tx
+ c.tx opts
end
alias :tx :build_tx
# build a Bitcoin::Script.
# see ScriptBuilder for details.
@@ -38,17 +38,16 @@
# b.prev_block "\x00"*32
# b.tx do |t|
# t.input {|i| i.coinbase }
# t.output do |o|
# o.value 5000000000;
- # o.script do |s|
- # s.type :address
- # s.recipient Bitcoin::Key.generate.addr
- # end
+ # o.to Bitcoin::Key.generate.addr
# end
# end
# end
+ #
+ # See Bitcoin::Builder::TxBuilder for details on building transactions.
class BlockBuilder
def initialize
@block = P::Block.new(nil)
end
@@ -67,14 +66,14 @@
def time time
@time = time
end
# add transactions to the block (see TxBuilder).
- def tx
- c = TxBuilder.new
- yield c
- @block.tx << c.tx
+ def tx tx = nil
+ tx ||= ( c = TxBuilder.new; yield c; c.tx )
+ @block.tx << tx
+ tx
end
# create the block according to values specified via DSL.
def block target
@block.ver = @version || 1
@@ -115,22 +114,24 @@
end
# DSL to create Bitcoin::Protocol::Tx used by Builder#build_tx.
# tx = tx do |t|
# t.input do |i|
- # i.prev_out prev_tx # previous transaction
- # i.prev_out_index 0 # index of previous output
- # i.signature_key key # Bitcoin::Key used to sign the input
+ # i.prev_out prev_tx, 0
+ # i.signature_key key
# end
# t.output do |o|
# o.value 12345 # 0.00012345 BTC
- # o.script {|s| s.type :address; s.recipient key.addr }
+ # o.to key.addr
# end
# end
#
- # signs every input that has a signature key. if the signature key is
- # not specified, the input will include the #sig_hash that needs to be signed.
+ # 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.
class TxBuilder
def initialize
@tx = P::Tx.new(nil)
@tx.ver, @tx.lock_time = 1, 0
@@ -159,132 +160,301 @@
c = TxOutBuilder.new
yield c
@outs << c
end
- # create the transaction according to values specified via DSL.
- # sign each input that has a signature key specified. if there is
+ # 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.
- def tx
+ #
+ # 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.
+ 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|
- if @tx.in[i].coinbase?
- script_sig = [inc.coinbase_data].pack("H*")
- @tx.in[i].script_sig_length = script_sig.bytesize
- @tx.in[i].script_sig = script_sig
- next
+ 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
+
+ # coinbase inputs don't need to be signed, they only include the given +coinbase_data+
+ 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
+
+ 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
+
+ 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
+
+ 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
- prev_tx = inc.instance_variable_get(:@prev_out)
- @sig_hash = @tx.signature_hash_for_input(i, prev_tx)
- if inc.key && inc.key.priv
- sig = inc.key.sign(@sig_hash)
- script_sig = Script.to_signature_pubkey_script(sig, [inc.key.pub].pack("H*"))
- @tx.in[i].script_sig_length = script_sig.bytesize
- @tx.in[i].script_sig = script_sig
- raise "Signature error" unless @tx.verify_input_signature(i, prev_tx)
+ 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
+
+ # Sign input number +i+ with data from given +inc+ object (a TxInBuilder).
+ 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
- @tx.in[i].script_sig_length = 0
- @tx.in[i].script_sig = ""
- @tx.in[i].sig_hash = @sig_hash
- @tx.in[i].sig_address = Script.new(prev_tx.out[@tx.in[i].prev_out_index].pk_script).get_address
+ # no sig_hash, add an empty script_sig.
+ add_empty_script_sig_to_input(i)
end
end
- data = @tx.in.map {|i| [i.sig_hash, i.sig_address] }
- tx = P::Tx.new(@tx.to_payload)
- data.each.with_index {|d, i| i = tx.in[i]; i.sig_hash = d[0]; i.sig_address = d[1] }
- raise "Payload Error" unless tx.to_payload == @tx.to_payload
- tx
end
+
+ # Randomize the outputs using SecureRandom
+ def randomize_outputs
+ @outs.sort_by!{ SecureRandom.random_bytes(4).unpack("I")[0] }
+ end
end
- # create a Bitcoin::Protocol::TxIn used by TxBuilder#input.
+ # Create a Bitcoin::Protocol::TxIn used by TxBuilder#input.
#
- # inputs need a #prev_out tx and #prev_out_index of the output they spend.
+ # Inputs need the transaction hash and the index of the output they spend.
+ # You can pass either the transaction, or just its hash (in hex form).
+ # To sign the input, builder also needs the pk_script of the previous output.
+ # If you specify a tx hash instead of the whole tx, you need to specify the
+ # output script separately.
+ #
+ # t.input do |i|
+ # i.prev_out prev_tx # previous transaction
+ # i.prev_out_index 0 # index of previous output
+ # i.signature_key key # Bitcoin::Key used to sign the input
+ # end
+ #
+ # t.input {|i| i.prev_out prev_tx, 0 }
+ #
+ # If you want to spend a p2sh output, you also need to specify the +redeem_script+.
+ #
+ # t.input do |i|
+ # i.prev_out prev_tx, 0
+ # i.redeem_script prev_out.redeem_script
+ # end
+ #
+ # If you want to spend a multisig output, just provide an array of keys to #signature_key.
class TxInBuilder
- attr_reader :key, :coinbase_data
+ attr_reader :prev_tx, :prev_script, :redeem_script, :key, :coinbase_data
def initialize
@txin = P::TxIn.new
+ @prev_out_hash = "\x00" * 32
+ @prev_out_index = 0
end
- # previous transaction that contains the output we want to use.
- def prev_out tx, idx = nil
- @prev_out, @prev_out_index = tx, idx
+ # Previous transaction that contains the output we want to use.
+ # You can either pass the transaction, or just the tx hash.
+ # If you pass only the hash, you need to pass the previous outputs
+ # +script+ separately if you want the txin to be signed.
+ def prev_out tx, idx = nil, script = nil
+ if tx.is_a?(Bitcoin::P::Tx)
+ @prev_tx = tx
+ @prev_out_hash = tx.binary_hash
+ @prev_out_script = tx.out[idx].pk_script if idx
+ else
+ @prev_out_hash = tx.htb.reverse
+ end
+ @prev_out_script = script if script
+ @prev_out_index = idx if idx
end
- # index of the output in the #prev_out transaction.
+ # Index of the output in the #prev_out transaction.
def prev_out_index i
@prev_out_index = i
+ @prev_out_script = @prev_tx.out[i].pk_script if @prev_tx
end
- # specify sequence. this is usually not needed.
+ # Previous output's +pk_script+. Needed when only the tx hash is specified as #prev_out.
+ def prev_out_script script
+ @prev_out_script = script
+ end
+
+ # Redeem script for P2SH output. To spend from a P2SH output, you need to provide
+ # the script with a hash matching the P2SH address.
+ def redeem_script script
+ @redeem_script = script
+ end
+
+ # Specify sequence. This is usually not needed.
def sequence s
@sequence = s
end
# Bitcoin::Key used to sign the signature_hash for the input.
# see Bitcoin::Script.signature_hash_for_input and Bitcoin::Key.sign.
def signature_key key
@key = key
end
- # specify that this is a coinbase input. optionally set +data+.
+ # Specify that this is a coinbase input. Optionally set +data+.
+ # If this is set, no other options need to be given.
def coinbase data = nil
@coinbase_data = data || OpenSSL::Random.random_bytes(32)
- @prev_out = nil
+ @prev_out_hash = "\x00" * 32
@prev_out_index = 4294967295
end
- # create the txin according to values specified via DSL
+ # Create the txin according to specified values
def txin
- @txin.prev_out = (@prev_out ? @prev_out.binary_hash : "\x00"*32)
+ @txin.prev_out = @prev_out_hash
@txin.prev_out_index = @prev_out_index
@txin.sequence = @sequence || "\xff\xff\xff\xff"
@txin
end
+
+ def has_multiple_keys?
+ @key.is_a?(Array)
+ end
+
+ def has_keys?
+ @key && (has_multiple_keys? ? @key.all?(&:priv) : @key.priv)
+ end
+
+ def sign(sig_hash)
+ if has_multiple_keys?
+ @key.map {|k| k.sign(sig_hash) }
+ else
+ @key.sign(sig_hash)
+ end
+ end
end
- # create a Bitcoin::Script used by TxOutBuilder#script.
+ # Create a Bitcoin::Script used by TxOutBuilder#script.
class ScriptBuilder
- attr_reader :script
+ attr_reader :script, :redeem_script
def initialize
@type = :address
@script = nil
end
- # script type (:pubkey, :address/hash160, :multisig).
+ # Script type (:pubkey, :address/hash160, :multisig).
+ # Defaults to :address.
def type type
@type = type.to_sym
end
- # recipient(s) of the script.
- # depending on the #type, either an address, hash160 pubkey, etc.
+ # Recipient(s) of the script.
+ # Depending on the #type, this should be an address, a hash160 pubkey,
+ # or an array of multisig pubkeys.
def recipient *data
- @script = Script.send("to_#{@type}_script", *data)
+ @script, @redeem_script = *Script.send("to_#{@type}_script", *data)
end
end
- # create a Bitcoin::Protocol::TxOut used by TxBuilder#output.
+ # Create a Bitcoin::Protocol::TxOut used by TxBuilder#output.
+ #
+ # t.output {|o| o.value 12345; o.to address }
+ #
+ # t.output do |o|
+ # o.value 12345
+ # o.script {|s| s.recipient address }
+ # end
class TxOutBuilder
attr_reader :txout
def initialize
@txout = P::TxOut.new
end
- # set output value (in base units / "satoshis")
+ # Set output value (in base units / "satoshis")
def value value
@txout.value = value
end
- # add a script to the output (see ScriptBuilder).
+ # Set recipient address and script type (defaults to :address).
+ def to recipient, type = :address
+ @txout.pk_script, @txout.redeem_script = *Bitcoin::Script.send("to_#{type}_script", *recipient)
+ end
+
+ # Add a script to the output (see ScriptBuilder).
def script &block
c = ScriptBuilder.new
yield c
- @txout.pk_script = c.script
+ @txout.pk_script, @txout.redeem_script = c.script, c.redeem_script
end
end
end