lib/bitcoin/builder.rb in bitcoin-ruby-0.0.1 vs lib/bitcoin/builder.rb in bitcoin-ruby-0.0.2

- old
+ new

@@ -1,37 +1,41 @@ +# encoding: ascii-8bit + module Bitcoin # Optional DSL to help create blocks and transactions. # # see also BlockBuilder, TxBuilder, TxInBuilder, TxOutBuilder, ScriptBuilder module Builder # build a Bitcoin::Protocol::Block matching the given +target+. # see BlockBuilder for details. - def blk(target = "00".ljust(32, 'f')) + def build_block(target = "00".ljust(64, 'f')) c = BlockBuilder.new yield c c.block(target) end + alias :blk :build_block # build a Bitcoin::Protocol::Tx. # see TxBuilder for details. - def tx + def build_tx c = TxBuilder.new yield c c.tx end + alias :tx :build_tx # build a Bitcoin::Script. # see ScriptBuilder for details. def script c = ScriptBuilder.new yield c c.script end - # DSL to create a Bitcoin::Protocol::Block used by Builder#blk. + # DSL to create a Bitcoin::Protocol::Block used by Builder#create_block. # block = blk("00".ljust(32, 'f')) do |b| # b.prev_block "\x00"*32 # b.tx do |t| # t.input {|i| i.coinbase } # t.output do |o| @@ -44,11 +48,11 @@ # end # end class BlockBuilder def initialize - @block = Bitcoin::P::Block.new(nil) + @block = P::Block.new(nil) end # specify block version. this is usually not necessary. defaults to 1. def version v @version = v @@ -57,28 +61,34 @@ # set the hash of the previous block. def prev_block hash @prev_block = hash end + # set the block timestamp (defaults to current time). + def time time + @time = time + end + # add transactions to the block (see TxBuilder). def tx c = TxBuilder.new yield c @block.tx << c.tx end # create the block according to values specified via DSL. def block target @block.ver = @version || 1 - @block.prev_block = [@prev_block].pack("H*").reverse + @block.prev_block = @prev_block.htb.reverse @block.mrkl_root = @mrkl_root - @block.time = Time.now.to_i + @block.time = @time || Time.now.to_i @block.nonce = 0 - @block.mrkl_root = [Bitcoin.hash_mrkl_tree(@block.tx.map {|t| - t.hash }).last].pack("H*") + @block.mrkl_root = Bitcoin.hash_mrkl_tree(@block.tx.map(&:hash)).last.htb.reverse find_hash(target) - Bitcoin::P::Block.new(@block.to_payload) + block = P::Block.new(@block.to_payload) + raise "Payload Error" unless block.to_payload == @block.to_payload + block end private # increment nonce/time to find a block hash matching the +target+. @@ -102,26 +112,29 @@ end end end - # DSL to create Bitcoin::Protocol::Tx used by Builder#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 - # end - # t.output do |o| - # o.value 12345 # 0.00012345 BTC - # o.script {|s| s.type :address; s.recipient key.addr } - # end - # 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 + # end + # t.output do |o| + # o.value 12345 # 0.00012345 BTC + # o.script {|s| s.type :address; s.recipient 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. class TxBuilder def initialize - @tx = Bitcoin::P::Tx.new(nil) + @tx = P::Tx.new(nil) @tx.ver, @tx.lock_time = 1, 0 @ins, @outs = [], [] end # specify tx version. this is usually not necessary. defaults to 1. @@ -146,11 +159,14 @@ c = TxOutBuilder.new yield c @outs << c end - # create the transaction according to values specified via DSL and sign inputs. + # 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 @ins.each {|i| @tx.add_in(i.txin) } @outs.each {|o| @tx.add_out(o.txout) } @ins.each_with_index do |inc, i| if @tx.in[i].coinbase? @@ -158,30 +174,40 @@ @tx.in[i].script_sig_length = script_sig.bytesize @tx.in[i].script_sig = script_sig next end prev_tx = inc.instance_variable_get(:@prev_out) - sig_hash = @tx.signature_hash_for_input(i, prev_tx) - sig = inc.key.sign(sig_hash) - script_sig = Bitcoin::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) + @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 + @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 + end end - Bitcoin::P::Tx.new(@tx.to_payload) + 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 end # create a Bitcoin::Protocol::TxIn used by TxBuilder#input. # - # inputs can be either 'coinbase', in which case they only need to specify #coinbase, - # or they have to define a #prev_out, #prev_out_index and #signature key. + # inputs need a #prev_out tx and #prev_out_index of the output they spend. class TxInBuilder attr_reader :key, :coinbase_data def initialize - @txin = Bitcoin::P::TxIn.new + @txin = P::TxIn.new end # previous transaction that contains the output we want to use. def prev_out tx @prev_out = tx @@ -222,11 +248,11 @@ # create a Bitcoin::Script used by TxOutBuilder#script. class ScriptBuilder attr_reader :script def initialize - @type = nil + @type = :address @script = nil end # script type (:pubkey, :address/hash160, :multisig). def type type @@ -234,19 +260,19 @@ end # recipient(s) of the script. # depending on the #type, either an address, hash160 pubkey, etc. def recipient *data - @script = Bitcoin::Script.send("to_#{@type}_script", *data) + @script = Script.send("to_#{@type}_script", *data) end end # create a Bitcoin::Protocol::TxOut used by TxBuilder#output. class TxOutBuilder attr_reader :txout def initialize - @txout = Bitcoin::P::TxOut.new + @txout = P::TxOut.new end # set output value (in base units / "satoshis") def value value @txout.value = value