bitcoin-ruby 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. data/.gitignore +12 -0
  2. data/COPYING +18 -0
  3. data/Gemfile +4 -0
  4. data/README.rdoc +189 -0
  5. data/Rakefile +104 -0
  6. data/bin/bitcoin_dns_seed +130 -0
  7. data/bin/bitcoin_gui +80 -0
  8. data/bin/bitcoin_node +174 -0
  9. data/bin/bitcoin_shell +12 -0
  10. data/bin/bitcoin_wallet +323 -0
  11. data/bitcoin-ruby.gemspec +27 -0
  12. data/concept-examples/blockchain-pow.rb +151 -0
  13. data/doc/CONFIG.rdoc +66 -0
  14. data/doc/EXAMPLES.rdoc +9 -0
  15. data/doc/NODE.rdoc +35 -0
  16. data/doc/STORAGE.rdoc +21 -0
  17. data/doc/WALLET.rdoc +102 -0
  18. data/examples/balance.rb +60 -0
  19. data/examples/bbe_verify_tx.rb +55 -0
  20. data/examples/connect.rb +36 -0
  21. data/examples/relay_tx.rb +22 -0
  22. data/examples/verify_tx.rb +57 -0
  23. data/lib/bitcoin.rb +370 -0
  24. data/lib/bitcoin/builder.rb +266 -0
  25. data/lib/bitcoin/config.rb +56 -0
  26. data/lib/bitcoin/connection.rb +126 -0
  27. data/lib/bitcoin/ffi/openssl.rb +121 -0
  28. data/lib/bitcoin/gui/addr_view.rb +42 -0
  29. data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
  30. data/lib/bitcoin/gui/bitcoin-ruby.svg +80 -0
  31. data/lib/bitcoin/gui/conn_view.rb +36 -0
  32. data/lib/bitcoin/gui/connection.rb +68 -0
  33. data/lib/bitcoin/gui/em_gtk.rb +28 -0
  34. data/lib/bitcoin/gui/gui.builder +1643 -0
  35. data/lib/bitcoin/gui/gui.rb +290 -0
  36. data/lib/bitcoin/gui/helpers.rb +113 -0
  37. data/lib/bitcoin/gui/tree_view.rb +82 -0
  38. data/lib/bitcoin/gui/tx_view.rb +67 -0
  39. data/lib/bitcoin/key.rb +125 -0
  40. data/lib/bitcoin/logger.rb +65 -0
  41. data/lib/bitcoin/network/command_client.rb +93 -0
  42. data/lib/bitcoin/network/command_handler.rb +179 -0
  43. data/lib/bitcoin/network/connection_handler.rb +274 -0
  44. data/lib/bitcoin/network/node.rb +399 -0
  45. data/lib/bitcoin/protocol.rb +140 -0
  46. data/lib/bitcoin/protocol/address.rb +48 -0
  47. data/lib/bitcoin/protocol/alert.rb +47 -0
  48. data/lib/bitcoin/protocol/block.rb +154 -0
  49. data/lib/bitcoin/protocol/handler.rb +38 -0
  50. data/lib/bitcoin/protocol/parser.rb +148 -0
  51. data/lib/bitcoin/protocol/tx.rb +205 -0
  52. data/lib/bitcoin/protocol/txin.rb +97 -0
  53. data/lib/bitcoin/protocol/txout.rb +73 -0
  54. data/lib/bitcoin/protocol/version.rb +70 -0
  55. data/lib/bitcoin/script.rb +634 -0
  56. data/lib/bitcoin/storage/dummy.rb +164 -0
  57. data/lib/bitcoin/storage/models.rb +133 -0
  58. data/lib/bitcoin/storage/sequel.rb +335 -0
  59. data/lib/bitcoin/storage/sequel_store/sequel_migrations.rb +84 -0
  60. data/lib/bitcoin/storage/storage.rb +243 -0
  61. data/lib/bitcoin/version.rb +3 -0
  62. data/lib/bitcoin/wallet/coinselector.rb +30 -0
  63. data/lib/bitcoin/wallet/keygenerator.rb +75 -0
  64. data/lib/bitcoin/wallet/keystore.rb +203 -0
  65. data/lib/bitcoin/wallet/txdp.rb +116 -0
  66. data/lib/bitcoin/wallet/wallet.rb +243 -0
  67. data/spec/bitcoin/bitcoin_spec.rb +472 -0
  68. data/spec/bitcoin/builder_spec.rb +90 -0
  69. data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +27 -0
  70. data/spec/bitcoin/fixtures/23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63.json +23 -0
  71. data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +27 -0
  72. data/spec/bitcoin/fixtures/60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1.json +45 -0
  73. data/spec/bitcoin/fixtures/bc179baab547b7d7c1d5d8d6f8b0cc6318eaa4b0dd0a093ad6ac7f5a1cb6b3ba.json +34 -0
  74. data/spec/bitcoin/fixtures/rawblock-0.bin +0 -0
  75. data/spec/bitcoin/fixtures/rawblock-0.json +39 -0
  76. data/spec/bitcoin/fixtures/rawblock-1.bin +0 -0
  77. data/spec/bitcoin/fixtures/rawblock-1.json +39 -0
  78. data/spec/bitcoin/fixtures/rawblock-131025.bin +0 -0
  79. data/spec/bitcoin/fixtures/rawblock-131025.json +5063 -0
  80. data/spec/bitcoin/fixtures/rawblock-170.bin +0 -0
  81. data/spec/bitcoin/fixtures/rawblock-170.json +68 -0
  82. data/spec/bitcoin/fixtures/rawblock-9.bin +0 -0
  83. data/spec/bitcoin/fixtures/rawblock-9.json +39 -0
  84. data/spec/bitcoin/fixtures/rawblock-testnet-26478.bin +0 -0
  85. data/spec/bitcoin/fixtures/rawblock-testnet-26478.json +64 -0
  86. data/spec/bitcoin/fixtures/rawtx-01.bin +0 -0
  87. data/spec/bitcoin/fixtures/rawtx-01.json +27 -0
  88. data/spec/bitcoin/fixtures/rawtx-02.bin +0 -0
  89. data/spec/bitcoin/fixtures/rawtx-02.json +27 -0
  90. data/spec/bitcoin/fixtures/rawtx-03.bin +0 -0
  91. data/spec/bitcoin/fixtures/rawtx-03.json +48 -0
  92. data/spec/bitcoin/fixtures/rawtx-04.json +27 -0
  93. data/spec/bitcoin/fixtures/rawtx-0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9.bin +0 -0
  94. data/spec/bitcoin/fixtures/rawtx-05.json +23 -0
  95. data/spec/bitcoin/fixtures/rawtx-14be6fff8c6014f7c9493b4a6e4a741699173f39d74431b6b844fcb41ebb9984.bin +0 -0
  96. data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.bin +0 -0
  97. data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.json +27 -0
  98. data/spec/bitcoin/fixtures/rawtx-406b2b06bcd34d3c8733e6b79f7a394c8a431fbf4ff5ac705c93f4076bb77602.json +23 -0
  99. data/spec/bitcoin/fixtures/rawtx-52250a162c7d03d2e1fbc5ebd1801a88612463314b55102171c5b5d817d2d7b2.bin +0 -0
  100. data/spec/bitcoin/fixtures/rawtx-b5d4e8883533f99e5903ea2cf001a133a322fa6b1370b18a16c57c946a40823d.bin +0 -0
  101. data/spec/bitcoin/fixtures/rawtx-ba1ff5cd66713133c062a871a8adab92416f1e38d17786b2bf56ac5f6ffdfdf5.json +37 -0
  102. data/spec/bitcoin/fixtures/rawtx-c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73.json +24 -0
  103. data/spec/bitcoin/fixtures/rawtx-de35d060663750b3975b7997bde7fb76307cec5b270d12fcd9c4ad98b279c28c.json +23 -0
  104. data/spec/bitcoin/fixtures/rawtx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16.bin +0 -0
  105. data/spec/bitcoin/fixtures/rawtx-testnet-a220adf1902c46a39db25a24bc4178b6a88440f977a7e2cabfdd8b5c1dd35cfb.json +27 -0
  106. data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.bin +0 -0
  107. data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.json +41 -0
  108. data/spec/bitcoin/fixtures/reorg/blk_0_to_4.dat +0 -0
  109. data/spec/bitcoin/fixtures/reorg/blk_3A.dat +0 -0
  110. data/spec/bitcoin/fixtures/reorg/blk_4A.dat +0 -0
  111. data/spec/bitcoin/fixtures/reorg/blk_5A.dat +0 -0
  112. data/spec/bitcoin/fixtures/testnet/block_0.bin +0 -0
  113. data/spec/bitcoin/fixtures/testnet/block_1.bin +0 -0
  114. data/spec/bitcoin/fixtures/testnet/block_2.bin +0 -0
  115. data/spec/bitcoin/fixtures/testnet/block_3.bin +0 -0
  116. data/spec/bitcoin/fixtures/testnet/block_4.bin +0 -0
  117. data/spec/bitcoin/fixtures/testnet/block_5.bin +0 -0
  118. data/spec/bitcoin/fixtures/txdp-1.txt +32 -0
  119. data/spec/bitcoin/fixtures/txdp-2-signed.txt +19 -0
  120. data/spec/bitcoin/fixtures/txdp-2-unsigned.txt +14 -0
  121. data/spec/bitcoin/key_spec.rb +123 -0
  122. data/spec/bitcoin/network_spec.rb +48 -0
  123. data/spec/bitcoin/protocol/addr_spec.rb +68 -0
  124. data/spec/bitcoin/protocol/alert_spec.rb +20 -0
  125. data/spec/bitcoin/protocol/block_spec.rb +101 -0
  126. data/spec/bitcoin/protocol/inv_spec.rb +124 -0
  127. data/spec/bitcoin/protocol/ping_spec.rb +49 -0
  128. data/spec/bitcoin/protocol/tx_spec.rb +226 -0
  129. data/spec/bitcoin/protocol/version_spec.rb +77 -0
  130. data/spec/bitcoin/reorg_spec.rb +129 -0
  131. data/spec/bitcoin/script/opcodes_spec.rb +417 -0
  132. data/spec/bitcoin/script/script_spec.rb +246 -0
  133. data/spec/bitcoin/spec_helper.rb +36 -0
  134. data/spec/bitcoin/storage_spec.rb +229 -0
  135. data/spec/bitcoin/wallet/coinselector_spec.rb +35 -0
  136. data/spec/bitcoin/wallet/keygenerator_spec.rb +64 -0
  137. data/spec/bitcoin/wallet/keystore_spec.rb +188 -0
  138. data/spec/bitcoin/wallet/txdp_spec.rb +74 -0
  139. data/spec/bitcoin/wallet/wallet_spec.rb +207 -0
  140. metadata +295 -0
@@ -0,0 +1,116 @@
1
+ class Bitcoin::Wallet::TxDP
2
+
3
+ attr_accessor :id, :tx, :inputs
4
+ def initialize tx = []
5
+ @id = Bitcoin.int_to_base58(rand(1e14))
6
+ @tx = tx
7
+ @inputs = []
8
+ return unless tx.any?
9
+ @tx[0].in.each_with_index do |input, i|
10
+ prev_out_hash = input.prev_out.reverse.unpack("H*")[0]
11
+ prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
12
+ raise "prev tx #{prev_out_hash} not found" unless prev_tx
13
+ prev_out = prev_tx.out[input.prev_out_index]
14
+ raise "prev out ##{input.prev_out_index} not found in tx #{@tx.hash}" unless prev_out
15
+ out_script = Bitcoin::Script.new(prev_out.pk_script)
16
+ out_script.get_addresses.each do |addr|
17
+ add_sig(i, prev_out.value, addr, input.script_sig)
18
+ end
19
+ end
20
+ end
21
+
22
+ def add_sig(in_idx, value, addr, sig)
23
+ sig = sig ? [[addr, sig.unpack("H*")[0]]] : []
24
+ @inputs[in_idx] = [value, sig]
25
+ end
26
+
27
+ def sign_inputs
28
+ @inputs.each_with_index do |txin, i|
29
+ input = @tx[0].in[i]
30
+ prev_out_hash = input.prev_out.reverse.unpack("H*")[0]
31
+ prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
32
+ raise "prev tx #{prev_out_hash} not found" unless prev_tx
33
+ prev_out = prev_tx.out[input.prev_out_index]
34
+ raise "prev out ##{input.prev_out_index} not found in tx #{@tx.hash}" unless prev_out
35
+ out_script = Bitcoin::Script.new(prev_out.pk_script)
36
+ out_script.get_addresses.each do |addr|
37
+ sig = yield(@tx[0], prev_tx, i, addr)
38
+ if sig
39
+ @inputs[i][1] ||= []
40
+ @inputs[i][1] << [addr, sig]
41
+ break
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def serialize
48
+ lines = []
49
+ lines << "-----BEGIN-TRANSACTION-#{@id}".ljust(80, '-')
50
+ size = [@tx.first.to_payload.bytesize].pack("C").ljust(2, "\x00").reverse.unpack("H*")[0]
51
+ lines << "_TXDIST_#{Bitcoin.network[:magic_head].unpack("H*")[0]}_#{@id}_#{size}"
52
+ tx = @tx.map(&:to_payload).join.unpack("H*")[0]
53
+ tx_str = ""; tx.split('').each_with_index{|c,i| tx_str << (i % 80 == 0 ? "\n#{c}" : c)}
54
+ lines << tx_str.strip
55
+ @inputs.each_with_index do |input, idx|
56
+ lines << "_TXINPUT_#{idx.to_s.rjust(2, '0')}_#{"%.8f" % (input[0].to_f / 1e8)}"
57
+ next unless input[1]
58
+ input[1].each do |sig|
59
+ size = [sig[1]].pack("H*").bytesize
60
+ size = [size].pack("C").ljust(2, "\x00").reverse.unpack("H*")[0]
61
+ lines << "_SIG_#{sig[0]}_#{idx.to_s.rjust(2, '0')}_#{size}"
62
+ sig_str = ""; sig[1].split('').each_with_index{|c,i| sig_str << (i % 80 == 0 ? "\n#{c}" : c)}
63
+ lines << sig_str.strip
64
+ end
65
+ end
66
+ lines << "-------END-TRANSACTION-#{@id}".ljust(80, '-')
67
+ lines.join("\n")
68
+ end
69
+
70
+ def parse str
71
+ str.match(/-+BEGIN-TRANSACTION-(.*?)-+$(.*?)END-TRANSACTION-#{$1}/m) do |m|
72
+ _, id, content = *m
73
+ txdist, *inputs = content.split(/_TXINPUT_/)
74
+ @id = id
75
+ @txdist = parse_txdist(txdist)
76
+ inputs.each {|input| parse_input(input) }
77
+ end
78
+ self
79
+ end
80
+
81
+ def parse_txdist txdist
82
+ _, magic, txdp_id, size, serialized_tx = *txdist.match(/_TXDIST_(.*?)_(.*?)_(.*?)$(.*)/m)
83
+ raise "Wrong network magic" unless [magic].pack("H*") == Bitcoin.network[:magic_head]
84
+ tx = Bitcoin::P::Tx.new(nil)
85
+ rest = [serialized_tx.gsub!("\n", '')].pack("H*")
86
+ while rest = tx.parse_data(rest)
87
+ @tx << tx
88
+ break if rest == true
89
+ tx = Bitcoin::P::Tx.new(nil)
90
+ end
91
+ end
92
+
93
+ def parse_input input
94
+ m = input.match(/(\d+)_(\d+\.\d+)\n(.*)/m)
95
+ _, idx, value, sigs = *m
96
+ value = (value.sub('.','').to_i)
97
+ sigs = parse_sigs(sigs)
98
+ @inputs[idx.to_i] = [value, sigs]
99
+ end
100
+
101
+ def parse_sigs sigs
102
+ return nil unless sigs["_SIG_"]
103
+ sigs = sigs.split("_SIG_").map do |s|
104
+ if s == ""
105
+ nil
106
+ else
107
+ m = s.match(/(.*?)_(\d+)_(.*?)\n(.*)/m)
108
+ [$1, $4.gsub("\n", '').gsub('-', '')]
109
+ end
110
+ end.compact
111
+ end
112
+
113
+ def self.parse str
114
+ new.parse str
115
+ end
116
+ end
@@ -0,0 +1,243 @@
1
+ Bitcoin.require_dependency :eventmachine, exit: false
2
+
3
+ # The wallet implementation consists of several concepts:
4
+ # Wallet:: the high-level API used to manage a wallet
5
+ # SimpleKeyStore:: key store to manage keys/addresses/labels
6
+ # SimpleCoinSelector:: coin selector to find unspent outputs to use when creating tx
7
+ module Bitcoin::Wallet
8
+
9
+ # A wallet manages a set of keys (through a +keystore+), can
10
+ # list transactions/balances for those keys (using a Storage backend for
11
+ # blockchain data).
12
+ # It can also create transactions with various kinds of outputs and
13
+ # connect with a CommandClient to relay those transactions through a node.
14
+ #
15
+ # TODO: new tx notification, keygenerators, keystore cleanup
16
+ class Wallet
17
+
18
+ include Bitcoin::Builder
19
+
20
+ # the keystore (SimpleKeyStore) managing keys/addresses/labels
21
+ attr_reader :keystore
22
+
23
+ # the Storage which holds the blockchain
24
+ attr_reader :storage
25
+
26
+ # open wallet with given +storage+ Storage backend, +keystore+ SimpleKeyStore
27
+ # and +selector+ SimpleCoinSelector
28
+ def initialize storage, keystore, selector
29
+ @storage = storage
30
+ @keystore = keystore
31
+ @selector = selector
32
+ @callbacks = {}
33
+ connect_node if defined?(EM)
34
+ end
35
+
36
+ def connect_node
37
+ return unless EM.reactor_running?
38
+ host, port = "127.0.0.1", 9999
39
+ @node = Bitcoin::Network::CommandClient.connect(host, port, self, @storage) do
40
+ on_connected { request :monitor, "block", "tx" }
41
+ on_block do |block, depth|
42
+ EM.defer do
43
+ block['tx'].each do |tx|
44
+ relevant, tx = @args[0].check_tx(tx['hash'])
45
+ @args[0].callback(:tx, :confirmed, tx) if relevant
46
+ end
47
+ end
48
+ end
49
+
50
+ on_tx do |response|
51
+ EM.defer do
52
+ relevant, tx = @args[0].check_tx(response['hash'])
53
+ @args[0].callback(:tx, relevant, tx) if relevant
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def check_tx tx_hash
60
+ relevant = false
61
+ addrs = addrs
62
+ tx = @storage.get_tx(tx_hash)
63
+ unless tx
64
+ log.warn { "Received tx #{response['hash']} but not found in storage" }
65
+ binding.pry
66
+ return false
67
+ end
68
+ addrs = @keystore.keys.map {|k| k[:addr] }
69
+ tx.out.each do |txout|
70
+ return :incoming, tx if (txout.get_addresses & addrs).any?
71
+ end
72
+ tx.in.each do |txin|
73
+ next unless prev_out = txin.get_prev_out
74
+ return :outgoing, tx if (prev_out.get_addresses & addrs).any?
75
+ end
76
+ return false
77
+ end
78
+
79
+ def log
80
+ return @log if @log
81
+ @log = Bitcoin::Logger.create("wallet")
82
+ @log.level = :debug
83
+ @log
84
+ end
85
+
86
+ # call the callback specified by +name+ passing in +args+
87
+ def callback name, *args
88
+ cb = @callbacks[name.to_sym]
89
+ return unless cb
90
+ log.debug { "callback: #{name}" }
91
+ cb.call(*args)
92
+ end
93
+
94
+ # register callback methods
95
+ def method_missing(name, *args, &block)
96
+ if name =~ /^on_/
97
+ @callbacks[name.to_s.split("on_")[1].to_sym] = block
98
+ log.debug { "callback #{name} registered" }
99
+ else
100
+ super(name, *args)
101
+ end
102
+ end
103
+
104
+ # get all Storage::Models::TxOut concerning any address from this wallet
105
+ def get_txouts(unconfirmed = false)
106
+ txouts = @keystore.keys.map {|k|
107
+ @storage.get_txouts_for_address(k[:addr])}.flatten.uniq
108
+ unconfirmed ? txouts : txouts.select {|o| !!o.get_tx.get_block}
109
+ end
110
+
111
+ # get total balance for all addresses in this wallet
112
+ def get_balance(unconfirmed = false)
113
+ values = get_txouts(unconfirmed).select{|o| !o.get_next_in}.map(&:value)
114
+
115
+ ([0] + values).inject(:+)
116
+ end
117
+
118
+ # list all addresses in this wallet
119
+ def addrs
120
+ @keystore.keys.map{|k| k[:addr]}
121
+ end
122
+
123
+ # add +key+ to wallet
124
+ def add_key key
125
+ @keystore.add_key(key)
126
+ end
127
+
128
+ # set label for key +old+ to +new+
129
+ def label old, new
130
+ @keystore.label_key(old, new)
131
+ end
132
+
133
+ # set +flag+ for key +name+ to +value+
134
+ def flag name, flag, value
135
+ @keystore.flag_key(name, flag, value)
136
+ end
137
+
138
+ # list all keys along with their balances
139
+ def list
140
+ @keystore.keys.map do |key|
141
+ [key, @storage.get_balance(Bitcoin.hash160_from_address(key[:addr]))]
142
+ end
143
+ end
144
+
145
+ # create new key and return its address
146
+ def get_new_addr
147
+ @keystore.new_key.addr
148
+ end
149
+
150
+ # get SimpleCoinSelector with txouts for this wallet
151
+ def get_selector
152
+ @selector.new(get_txouts)
153
+ end
154
+
155
+ # create a transaction with given +outputs+, +fee+ and +change_policy+.
156
+ #
157
+ # outputs are of the form
158
+ # [<type>, <recipients>, <value>]
159
+ # examples:
160
+ # [:address, <addr>, <value>]
161
+ # [:multisig, 2, 3, <addr>, <addr>, <addr>, <value>]
162
+ #
163
+ # inputs are selected automatically by the SimpleCoinSelector.
164
+ #
165
+ # change_policy controls where the change_output is spent to.
166
+ # see #get_change_addr
167
+ def new_tx outputs, fee = 0, change_policy = :back
168
+ output_value = outputs.map{|o|o[-1]}.inject(:+)
169
+
170
+ prev_outs = get_selector.select(output_value)
171
+ return nil if !prev_outs
172
+
173
+ input_value = prev_outs.map(&:value).inject(:+)
174
+ return nil unless input_value >= (output_value + fee)
175
+
176
+ tx = tx do |t|
177
+ outputs.each do |type, *addrs, value|
178
+ t.output do |o|
179
+ o.value value
180
+ o.script do |s|
181
+ s.type type
182
+ s.recipient *addrs
183
+ end
184
+ end
185
+ end
186
+
187
+ change_value = input_value - output_value - fee
188
+ if change_value > 0
189
+ change_addr = get_change_addr(change_policy, prev_outs.sample.get_address)
190
+ t.output do |o|
191
+ o.value change_value
192
+ o.script do |s|
193
+ s.type :address
194
+ s.recipient change_addr
195
+ end
196
+ end
197
+ end
198
+
199
+ prev_outs.each_with_index do |prev_out, idx|
200
+ t.input do |i|
201
+ prev_tx = prev_out.get_tx
202
+ i.prev_out prev_tx
203
+ i.prev_out_index prev_tx.out.index(prev_out)
204
+ pk_script = Bitcoin::Script.new(prev_out.pk_script)
205
+ if pk_script.is_pubkey? || pk_script.is_hash160?
206
+ i.signature_key @keystore.key(prev_out.get_address)[:key]
207
+ elsif pk_script.is_multisig?
208
+ raise "multisig not implemented"
209
+ end
210
+ end
211
+ end
212
+ end
213
+ # TODO: spend multisig outputs again
214
+ # TODO: verify signatures
215
+ Bitcoin::Protocol::Tx.new(tx.to_payload)
216
+ end
217
+
218
+ protected
219
+
220
+ # get address to send change output to.
221
+ # +policy+ controls which address is chosen:
222
+ # first:: send to the first key in the wallets keystore
223
+ # random:: send to a random key from the wallets keystore
224
+ # new:: send to a new key generated in the wallets keystore
225
+ # back:: send to the address given as +in_addr+
226
+ def get_change_addr(policy, in_addr)
227
+ case policy
228
+ when :first
229
+ @keystore.keys[0].addr
230
+ when :random
231
+ @keystore.keys.sample.addr
232
+ when :new
233
+ @keystore.new_key.addr
234
+ when :back
235
+ in_addr
236
+ else
237
+ policy
238
+ end
239
+ end
240
+
241
+ end
242
+
243
+ end
@@ -0,0 +1,472 @@
1
+ require_relative 'spec_helper.rb'
2
+ require 'bitcoin'
3
+
4
+
5
+ describe 'Bitcoin Address/Hash160/PubKey' do
6
+
7
+ it 'bitcoin-hash160 from public key' do
8
+ # 65 bytes (8 bit version + 512 bits) pubkey in hex (130 bytes)
9
+ pubkey = "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"
10
+ Bitcoin.hash160(pubkey).should == "62e907b15cbf27d5425399ebf6f0fb50ebb88f18"
11
+ end
12
+
13
+ it 'bitcoin-address from bitcoin-hash160' do
14
+ # 20 bytes (160 bit) hash160 in hex (40 bytes)
15
+
16
+ Bitcoin::network = :testnet
17
+ Bitcoin.hash160_to_address("62e907b15cbf27d5425399ebf6f0fb50ebb88f18")
18
+ .should == "mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt"
19
+
20
+ Bitcoin::network = :bitcoin
21
+ Bitcoin.hash160_to_address("62e907b15cbf27d5425399ebf6f0fb50ebb88f18")
22
+ .should == "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
23
+ end
24
+
25
+ it 'bitcoin-hash160 from bitcoin-address' do
26
+ Bitcoin::network = :testnet
27
+ Bitcoin.hash160_from_address("mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt")
28
+ .should == "62e907b15cbf27d5425399ebf6f0fb50ebb88f18"
29
+ Bitcoin.hash160_from_address("totally-invalid").should == nil
30
+
31
+ Bitcoin::network = :bitcoin
32
+ Bitcoin.hash160_from_address("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa")
33
+ .should == "62e907b15cbf27d5425399ebf6f0fb50ebb88f18"
34
+
35
+ Bitcoin.hash160_from_address("11ofrrzv87Ls97jN4TUetfQp4gEsUSL7A")
36
+ .should == "0026f5494b39ea04b7bcb05e583acf3b0102d61f"
37
+ Bitcoin.hash160_from_address("11122RGUQSszAsTpptd2h8sdyYGR6nKs6f")
38
+ .should == "0000daec8d6f05e949710f202c4f73258aa7791e"
39
+ Bitcoin.hash160_from_address("11119uLoMQCBHmKevdsFKHMaUoyrwLa9Y")
40
+ .should == "00000090c66372823859c935149e2e32d276a1e6"
41
+ Bitcoin.hash160_from_address("1111136sgL8UNSTVL9ize2uGFPxFDGwFp")
42
+ .should == "0000000096d3ad65d030a36e2c23f7fdd5dfcadb"
43
+ end
44
+
45
+ it 'should survive rounds of hash160 <-> address' do
46
+ hex = "62e907b15cbf27d5425399ebf6f0fb50ebb88f18"
47
+
48
+ Bitcoin::network = :testnet
49
+ addr = "mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt"
50
+ Bitcoin.hash160_from_address(Bitcoin.hash160_to_address(hex)).should == hex
51
+ Bitcoin.hash160_to_address(Bitcoin.hash160_from_address(addr)).should == addr
52
+
53
+ Bitcoin::network = :bitcoin
54
+ addr = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
55
+ Bitcoin.hash160_from_address(Bitcoin.hash160_to_address(hex)).should == hex
56
+ Bitcoin.hash160_to_address(Bitcoin.hash160_from_address(addr)).should == addr
57
+ end
58
+
59
+ it '#address_checksum?' do
60
+ Bitcoin::network = :testnet
61
+ Bitcoin.address_checksum?("mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt").should == true
62
+ Bitcoin.address_checksum?("1D3KpY5kXnYhTbdCbZ9kXb2ZY7ZapD85cW").should == true
63
+ Bitcoin.address_checksum?("f0f0f0").should == false
64
+
65
+ Bitcoin::network = :bitcoin
66
+ Bitcoin.address_checksum?("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa").should == true
67
+ Bitcoin.address_checksum?("mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt").should == true
68
+ Bitcoin.address_checksum?("f0f0f0").should == false
69
+ end
70
+
71
+ it 'validate bitcoin-address' do
72
+
73
+ Bitcoin::network = :testnet
74
+
75
+ Bitcoin.valid_address?("mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt").should == true
76
+ Bitcoin.valid_address?("1D3KpY5kXnYhTbdCbZ9kXb2ZY7ZapD85cW").should == false
77
+ Bitcoin.valid_address?("1moYFpRM4LkTV4Ho5eCxiEPB2bSm3AsJNGj").should == false
78
+ Bitcoin.valid_address?("f0f0f0").should == false
79
+
80
+ Bitcoin::network = :bitcoin
81
+
82
+ Bitcoin.valid_address?("1D3KpY5kXnYhTbdCbZ9kXb2ZY7ZapD85cW").should == true
83
+ Bitcoin.valid_address?("mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt").should == false
84
+ Bitcoin.valid_address?("2D3KpY5kXnYhTbdCbZ9kXb2ZY7ZapD85cW").should == false
85
+ Bitcoin.valid_address?("1D3KpY5kXnYhTbdCbZ9kXb2ZY7ZapD85cX").should == false
86
+ Bitcoin.valid_address?("1moYFpRM4LkTV4Ho5eCxiEPB2bSm3AsJNGj").should == false
87
+
88
+ Bitcoin.valid_address?("1ZQxJYBRmbb2rDNYPhd96x3eMbNnPD98q").should == true
89
+ Bitcoin.valid_address?("12KhCL8nGK3Luy7ehU3AxPs1mTocdessLM").should == true
90
+ Bitcoin.valid_address?("1AnNQgfaGgSKejzR6km74tyQPDGwZBBVT").should == true
91
+ Bitcoin.valid_address?("f0f0f0").should == false
92
+
93
+
94
+ Bitcoin.base58_to_int("114EpVhtPpJQKti8HiH2fvXZFPiPkgDZrE").should \
95
+ == 15016857106811133404017207799481956647721349092596212439
96
+
97
+ Bitcoin.network, success = :testnet, true
98
+ 400.times{
99
+ addr = Bitcoin.generate_address
100
+ success = false if Bitcoin.hash160_from_address(addr[0]) != addr[-1]
101
+ success = false if Bitcoin.hash160_to_address(addr[-1]) != addr[0]
102
+ success = false if Bitcoin.valid_address?(addr[0]) != true
103
+ }
104
+ success.should == true
105
+
106
+ Bitcoin.network, success = :bitcoin, true
107
+ 400.times{
108
+ addr = Bitcoin.generate_address
109
+ success = false if Bitcoin.hash160_from_address(addr[0]) != addr[-1]
110
+ success = false if Bitcoin.hash160_to_address(addr[-1]) != addr[0]
111
+ success = false if Bitcoin.valid_address?(addr[0]) != true
112
+ }
113
+ success.should == true
114
+ end
115
+
116
+ it 'validate p2sh address' do
117
+ Bitcoin.network = :testnet
118
+ Bitcoin.valid_address?("2MyLngQnhzjzatKsB7XfHYoP9e2XUXSiBMM").should == true
119
+ Bitcoin.network = :bitcoin
120
+ Bitcoin.valid_address?("3CkxTG25waxsmd13FFgRChPuGYba3ar36B").should == true
121
+ end
122
+
123
+ it '#address_type' do
124
+ Bitcoin.network = :testnet
125
+ Bitcoin.address_type("2MyLngQnhzjzatKsB7XfHYoP9e2XUXSiBMM").should == :p2sh
126
+ Bitcoin.address_type("mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt").should == :hash160
127
+ Bitcoin.address_type("1D3KpY5kXnYhTbdCbZ9kXb2ZY7ZapD85cW").should == nil
128
+ Bitcoin.network = :bitcoin
129
+ Bitcoin.address_type("3CkxTG25waxsmd13FFgRChPuGYba3ar36B").should == :p2sh
130
+ Bitcoin.address_type("1D3KpY5kXnYhTbdCbZ9kXb2ZY7ZapD85cW").should == :hash160
131
+ Bitcoin.address_type("mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt").should == nil
132
+ end
133
+
134
+ it 'Bitcoin#checksum' do
135
+ Bitcoin.checksum("0062e907b15cbf27d5425399ebf6f0fb50ebb88f18").should == "c29b7d93"
136
+ end
137
+
138
+ it '#bitcoin_mrkl' do
139
+ # block 170 is the first block that has a transaction.
140
+ # hash 00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee
141
+ a = "b1fea52486ce0c62bb442b530a3f0132b826c74e473d1f2c220bfa78111c5082"
142
+ b = "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"
143
+ c = "7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff"
144
+
145
+ Bitcoin.bitcoin_hash(b + a) .should == c
146
+ Bitcoin.bitcoin_mrkl(a , b) .should == c
147
+ end
148
+
149
+ it 'mrkl_tree from transaction-hashes' do
150
+
151
+ # mrkl tree for block 170
152
+ mrkl_tree = [
153
+ "b1fea52486ce0c62bb442b530a3f0132b826c74e473d1f2c220bfa78111c5082",# tx 1
154
+ "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16",# tx 2
155
+ "7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff"
156
+ ]
157
+ Bitcoin.hash_mrkl_tree(mrkl_tree[0...2]).should == mrkl_tree
158
+
159
+
160
+ mrkl_tree = [
161
+ "4fa598026d9be1ca0c2bff1b531e566b7ea7f90b72e75fda0c1795bc2dfa375c",
162
+ "186640daf908156e2616790d7c816235b0c43f668c3c38351b348c08ca44d457",
163
+ "ef3928700309b4deceac9a992a19a7481b4e520cbc0b1ab74e2645eee39c8da0",
164
+
165
+ "688c53517f62f7a65c0e87519c18a4de98f2ccafbf389b269d0bb867f88d166a",
166
+ "01889506f7fe9210045f588361881e2d16a034a62bc48ebd7b6b0a3edeaf5a6d",
167
+ "74f3a7df861d6a58957b84a3e425a8cf57e1e2e3a3def046dd200baeb8714f00"
168
+ ]
169
+
170
+ Bitcoin.hash_mrkl_tree( mrkl_tree[0...3] ).should == mrkl_tree
171
+
172
+ Bitcoin.bitcoin_mrkl( mrkl_tree[0], mrkl_tree[1] ).should == mrkl_tree[3]
173
+ Bitcoin.bitcoin_mrkl( mrkl_tree[2], mrkl_tree[2] ).should == mrkl_tree[4]
174
+ Bitcoin.bitcoin_mrkl( mrkl_tree[3], mrkl_tree[4] ).should == mrkl_tree[5]
175
+
176
+
177
+ mrkl_tree = [
178
+ "349f717b6630e1f305f95964a2d94117dacca76e0b715d4d7a5657698ec96c6c", # 0
179
+ "7f44a84349200473455bcfc05ee68036e23993a6f58dce3f6a7faab46a754440", # 1
180
+ "6b3ba3fdfb7eeb6c2e5fd0e36e5bb4634da294521f7b1b808286c214981f9b17", # 2
181
+ "0467b41043d654ba3dc3940cbefec0eb38feed6e7085a8e825e4f782eccb48e3", # 3
182
+ "83d0231624f7a7d5c37557461ac0d09a8ad7a1f4ab673dd697acb275d4b114de", # 4
183
+ "074970aaa98db7d00e6f97a719fe85e9c7f51b75fa5a9a92218d568ccc2b21fe", # 5
184
+
185
+ "c1b6d2a416de6b63e42c1b50f229911bfb07f816e0795bb86ff7dcf0463ab0df", # 6
186
+ "751bcfd8acc10792f42050dca5b852f7a2fcd5300897d05907a98473f59a5650", # 7
187
+ "7abf551000d942efb93afc2d6174dc1bb7d41e8ea5fd76724685000734f1d77b", # 8
188
+
189
+ "e268927aa50d44de5365c11a2e402478767b7b98856a21a0715f9db65709aabb", # 9
190
+ "ed5ecc6b0e2fdd81be806599d6509d166e26849049e60e8d8b398641282b1e5a", # 10
191
+
192
+ "72e0e61880cc5fc9c7b5990c2d40b22eba783391b72807d2d5349fb55875c015" # 11
193
+ ]
194
+
195
+ Bitcoin.bitcoin_mrkl( mrkl_tree[0], mrkl_tree[1] ).should == mrkl_tree[6]
196
+ Bitcoin.bitcoin_mrkl( mrkl_tree[2], mrkl_tree[3] ).should == mrkl_tree[7]
197
+ Bitcoin.bitcoin_mrkl( mrkl_tree[4], mrkl_tree[5] ).should == mrkl_tree[8]
198
+
199
+ Bitcoin.bitcoin_mrkl( mrkl_tree[6], mrkl_tree[7] ).should == mrkl_tree[9]
200
+ Bitcoin.bitcoin_mrkl( mrkl_tree[8], mrkl_tree[8] ).should == mrkl_tree[10]
201
+
202
+ Bitcoin.bitcoin_mrkl( mrkl_tree[9], mrkl_tree[10] ).should == mrkl_tree[11]
203
+
204
+ Bitcoin.hash_mrkl_tree(mrkl_tree[0...6]).should == mrkl_tree
205
+
206
+
207
+ mrkl_tree = [
208
+ "627c859b5af6d537930fd16148eb0597542bea543f65fc2b0e5f188b5a458529", # 0
209
+ "d00b90525820a74f30ce26488db7f77c6ee9577e650568a051edd8560bbf83a1", # 1
210
+ "706b8ac1a433bc28385450626e12c1c7806032dc8b7e12221f417c5f22059d70", # 2
211
+ "10107ad569400a5f9621498e410845e6db0551671a2cafcf4358bd7867c6bc14", # 3
212
+ "ac6b08a363aedd5e58177c7f68bb213403ef78d24be0012c06b3483a9e2461fe", # 4
213
+ "d3074f5b33a44d9961f40eadf250cdc1425f7975012fccb6b06abc5202c53f4b", # 5
214
+ "3270c13599266d3a8da90a85a07fc003c58a8ff2938988356783b6261be335a6", # 6
215
+ "e097c3e2e3a07385628ac5a5a775e8a5e22dda3732bee32ae65b1430d080fc32", # 7
216
+ "c335a3963e8d89a9f46b94158d33f9b0dee25e776cba91be5eda44898bd31a78", # 8
217
+
218
+ "eb52315c6b26f72fa58ed95cd4886f1aa047ecd8f34ed8a367f59854f20733d8", # 9
219
+ "f97bf49e42e1732c0b515ecbac7cffc29c8c75c20c6783ad48b09d348fe0b6cf", # 10
220
+ "fc655dfc41eed4e2ea4cfe33ebb6bf593eb256bf86c17802fd03567668d0bf71", # 11
221
+ "06c4abf5dae15d5fa3632e7e5f82f05e7afbbfd495ea8015c6094764d868654c", # 12
222
+ "31bbd6e4523aeaaaf75b6ea4ef63d0fe3dba66fb719f4a4232891a3b58ad5cec", # 13
223
+
224
+ "0c728622b795e14ac40c4aa13cb732a0407b2b85c9108c6f06c083220bb6d65d", # 14
225
+ "f83f6ea46edcbeaa738ce4701bf48412a10c4b1a9f109efe44bc8fe5cb6b0017", # 15
226
+ "5e6168778c8407ace3ae9901a2f5197d6f21a6634ae0af639e52d14c39b13e02", # 16
227
+
228
+ "b92a7980b8a8a64f896027c0de732298d04ca56bea66c18cf97983037486e456", # 17
229
+ "2a2fd5e450b6ec31ccbe9d827f2e903714eb69c351300b1e76c587aef60e000c", # 18
230
+
231
+ "9ed561bb49c47648e0250cb074721d94cba84ed1e083f1e57c29eca78e36d73d" # 19
232
+ ]
233
+
234
+ Bitcoin.bitcoin_mrkl( mrkl_tree[0], mrkl_tree[1] ).should == mrkl_tree[9]
235
+ Bitcoin.bitcoin_mrkl( mrkl_tree[2], mrkl_tree[3] ).should == mrkl_tree[10]
236
+ Bitcoin.bitcoin_mrkl( mrkl_tree[4], mrkl_tree[5] ).should == mrkl_tree[11]
237
+ Bitcoin.bitcoin_mrkl( mrkl_tree[6], mrkl_tree[7] ).should == mrkl_tree[12]
238
+ Bitcoin.bitcoin_mrkl( mrkl_tree[8], mrkl_tree[8] ).should == mrkl_tree[13]
239
+
240
+ Bitcoin.bitcoin_mrkl( mrkl_tree[9], mrkl_tree[10] ).should == mrkl_tree[14]
241
+ Bitcoin.bitcoin_mrkl( mrkl_tree[11], mrkl_tree[12] ).should == mrkl_tree[15]
242
+ Bitcoin.bitcoin_mrkl( mrkl_tree[13], mrkl_tree[13] ).should == mrkl_tree[16]
243
+
244
+ Bitcoin.bitcoin_mrkl( mrkl_tree[14], mrkl_tree[15] ).should == mrkl_tree[17]
245
+ Bitcoin.bitcoin_mrkl( mrkl_tree[16], mrkl_tree[16] ).should == mrkl_tree[18]
246
+
247
+ Bitcoin.bitcoin_mrkl( mrkl_tree[17], mrkl_tree[18] ).should == mrkl_tree[19]
248
+
249
+ Bitcoin.hash_mrkl_tree(mrkl_tree[0...9]).should == mrkl_tree
250
+ end
251
+
252
+ it 'nonce compact bits to bignum hex' do
253
+ Bitcoin.decode_compact_bits( "1b00b5ac".to_i(16) ).index(/[^0]/).should == 12
254
+ Bitcoin.decode_compact_bits( "1b00b5ac".to_i(16) ).to_i(16).should ==
255
+ "000000000000b5ac000000000000000000000000000000000000000000000000".to_i(16)
256
+
257
+
258
+ target = 453031340
259
+ Bitcoin.decode_compact_bits( target ).should ==
260
+ "000000000000b5ac000000000000000000000000000000000000000000000000"
261
+ Bitcoin.encode_compact_bits( Bitcoin.decode_compact_bits( target ) ).should == target
262
+
263
+ target = 486604799
264
+ Bitcoin.decode_compact_bits( target ).should ==
265
+ "00000000ffff0000000000000000000000000000000000000000000000000000"
266
+ Bitcoin.encode_compact_bits( Bitcoin.decode_compact_bits( target ) ).should == target
267
+
268
+ target = 476399191 # from block 40,320
269
+ Bitcoin.decode_compact_bits(target).should ==
270
+ "0000000065465700000000000000000000000000000000000000000000000000"
271
+ Bitcoin.encode_compact_bits( Bitcoin.decode_compact_bits( target ) ).should == target
272
+ end
273
+
274
+ it '#block_hash' do
275
+ # block 0 n_tx: 1
276
+ prev_block="0000000000000000000000000000000000000000000000000000000000000000"
277
+ mrkl_root ="4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
278
+ time, bits, nonce, ver = 1231006505, 486604799, 2083236893, 1
279
+
280
+ Bitcoin.block_hash(prev_block, mrkl_root, time, bits, nonce, ver).should ==
281
+ "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
282
+
283
+
284
+ # block 1 n_tx: 1
285
+ prev_block="000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
286
+ mrkl_root ="0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"
287
+ time, bits, nonce, ver = 1231469665, 486604799, 2573394689, 1
288
+
289
+ Bitcoin.block_hash(prev_block, mrkl_root, time, bits, nonce, ver).should ==
290
+ "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"
291
+
292
+ # .. only n_tx: 1
293
+
294
+ # block 169 n_tx: 1
295
+ prev_block="00000000567e95797f93675ac23683ae3787b183bb36859c18d9220f3fa66a69"
296
+ mrkl_root ="d7b9a9da6becbf47494c27e913241e5a2b85c5cceba4b2f0d8305e0a87b92d98"
297
+ time, bits, nonce, ver = 1231730523, 486604799, 3718213931, 1
298
+
299
+ Bitcoin.block_hash(prev_block, mrkl_root, time, bits, nonce, ver).should ==
300
+ "000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55"
301
+
302
+
303
+ # block 170 n_tx: 2
304
+ prev_block="000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55"
305
+ mrkl_root ="7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff"
306
+ time, bits, nonce, ver = 1231731025, 486604799, 1889418792, 1
307
+
308
+ Bitcoin.block_hash(prev_block, mrkl_root, time, bits, nonce, ver).should ==
309
+ "00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee"
310
+
311
+
312
+ # block 171 n_tx: 1
313
+ prev_block="00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee"
314
+ mrkl_root ="d5f2d21453a6f0e67b5c42959c9700853e4c4d46fa7519d1cc58e77369c893f2"
315
+ time, bits, nonce, ver = 1231731401, 486604799, 653436935, 1
316
+ Bitcoin.block_hash(prev_block, mrkl_root, time, bits, nonce, ver).should ==
317
+ "00000000c9ec538cab7f38ef9c67a95742f56ab07b0a37c5be6b02808dbfb4e0"
318
+ end
319
+
320
+ it 'generates openssl-secp256k1 private/public keypair' do
321
+ private_key, public_key = Bitcoin.generate_key
322
+
323
+ private_key.size .should == 64 # bytes in hex
324
+ public_key.size .should == 130 # bytes in hex
325
+
326
+ key = Bitcoin.open_key(private_key, public_key)
327
+ Bitcoin.inspect_key( key ).should == [ private_key, public_key ]
328
+ end
329
+
330
+ begin
331
+ Bitcoin::OpenSSL_EC
332
+ it 'opens key from private key and resolves public key' do
333
+ private_key, public_key = Bitcoin.generate_key
334
+ key = Bitcoin.open_key(private_key)
335
+ [ key.private_key_hex, key.public_key_hex ].should == [ private_key, public_key ]
336
+ end
337
+
338
+ it 'extract private key from uncompressed DER format' do
339
+ der = "308201130201010420a29fe0f28b2936dbc89f889f74cd1f0662d18a873ac15d6cd417b808db1ccd0aa081a53081a2020101302c06072a8648ce3d0101022100fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f300604010004010704410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8022100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141020101a14403420004768cfc6c44b927b0e69e9dd343e96132f7cd1d360d8cb8d65c83d89d7beaceadfd19918e076606a099344156acdb026b1065a958e39f098cfd0a34dd976291d6"
340
+
341
+ Bitcoin::OpenSSL_EC.der_to_private_key(der).should == "a29fe0f28b2936dbc89f889f74cd1f0662d18a873ac15d6cd417b808db1ccd0a"
342
+ end
343
+ rescue LoadError
344
+ end
345
+
346
+ it 'generates new bitcoin-address' do
347
+ address, private_key, public_key, hash160 = Bitcoin.generate_address
348
+
349
+ private_key.size .should == 64 # bytes in hex
350
+ public_key.size .should == 130 # bytes in hex
351
+ #Bitcoin.valid_address?(address).should == true # fix/extend
352
+ Bitcoin.hash160_to_address(Bitcoin.hash160(public_key)).should == address
353
+ end
354
+
355
+ it 'encodes and decodes base58' do
356
+ # fixtures from: https://github.com/bitcoin/bitcoin/blob/master/src/test/base58_tests.cpp
357
+ bin = [
358
+ "",
359
+ "\x61",
360
+ "\x62\x62\x62",
361
+ "\x63\x63\x63",
362
+ "\x73\x69\x6d\x70\x6c\x79\x20\x61\x20\x6c\x6f\x6e\x67\x20\x73\x74\x72\x69\x6e\x67",
363
+ "\x00\xeb\x15\x23\x1d\xfc\xeb\x60\x92\x58\x86\xb6\x7d\x06\x52\x99\x92\x59\x15\xae\xb1\x72\xc0\x66\x47",
364
+ "\x51\x6b\x6f\xcd\x0f",
365
+ "\xbf\x4f\x89\x00\x1e\x67\x02\x74\xdd",
366
+ "\x57\x2e\x47\x94",
367
+ "\xec\xac\x89\xca\xd9\x39\x23\xc0\x23\x21",
368
+ "\x10\xc8\x51\x1e",
369
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
370
+ ]
371
+ out = [
372
+ "",
373
+ "2g",
374
+ "a3gV",
375
+ "aPEr",
376
+ "2cFupjhnEsSn59qHXstmK2ffpLv2",
377
+ "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L",
378
+ "ABnLTmg",
379
+ "3SEo3LWLoPntC",
380
+ "3EFU7m",
381
+ "EJDM8drfXA6uyA",
382
+ "Rt5zm",
383
+ "1111111111"
384
+ ]
385
+
386
+ fixtures = bin.zip(out).map{|b,out| [ b.unpack("H*")[0], out ] }
387
+ #fixtures.each{|hex,out| p [hex, out, Bitcoin.encode_base58(hex), Bitcoin.decode_base58(out)] }
388
+ fixtures.all?{|hex,out| Bitcoin.encode_base58(hex) == out }.should == true
389
+ fixtures.all?{|hex,out| Bitcoin.decode_base58(out) == hex }.should == true
390
+ end
391
+
392
+ it '#block_next_retarget' do
393
+ Bitcoin.block_next_retarget(189408).should == 189503
394
+ Bitcoin.block_next_retarget(189503).should == 189503
395
+ Bitcoin.block_next_retarget(189504).should == 191519
396
+ end
397
+
398
+ it '#block_difficulty' do
399
+ Bitcoin.block_difficulty(436835377).should == "1751454.5353407"
400
+ end
401
+
402
+ it '#block_hashes_to_win' do
403
+ Bitcoin.block_hashes_to_win(436835377).should == 7522554734795001
404
+ end
405
+
406
+ it '#block_probability' do
407
+ Bitcoin.block_probability(436835377).should ==
408
+ "0.0000000000000001329335625003267087884673003372881794348"
409
+ end
410
+
411
+ it '#block_average_hashing_time' do
412
+ Bitcoin.block_average_hashing_time(436835377, 630_000_000).should == 11940563
413
+ end
414
+
415
+ it '#blockchain_total_btc' do
416
+ # 0.step(6930000, 210000){|height|
417
+ # p total_btc(height-1) unless height == 0
418
+ # p total_btc(height)
419
+ # }
420
+ [0, 209999, 210000, 419999, 420000, 1680000].map{|height|
421
+ Bitcoin.blockchain_total_btc(height)
422
+ }.should == [
423
+ [5000000000, 1, 5000000000, 0],
424
+ [1050000000000000, 1, 5000000000, 209999],
425
+ [1050005000000000, 2, 2500000000, 210000],
426
+ [1575002500000000, 2, 2500000000, 419999],
427
+ [1575005000000000, 3, 1250000000, 420000],
428
+ [2091801875000000, 9, 19531250, 1680000]
429
+ ]
430
+ end
431
+
432
+ it '#block_creation_reward' do
433
+ [0, 209999, 210000, 419999, 420000, 1680000].map{|height|
434
+ Bitcoin.block_creation_reward(height)
435
+ }.should == [ 5000000000, 5000000000, 2500000000, 2500000000, 1250000000, 19531250 ]
436
+ end
437
+
438
+ end
439
+
440
+
441
+ __END__
442
+ describe 'Bitcoin-Wiki - Common Standards - Hashes' do
443
+ # https://en.bitcoin.it/wiki/Protocol_specification
444
+ # Hashes
445
+ # Usually, when a hash is computed within bitcoin, it is computed twice.
446
+ # Most of the time SHA-256 hashes are used, however RIPEMD-160 is also
447
+ # used when a shorter hash is desirable.
448
+
449
+ require 'digest/sha2'
450
+ require 'digest/rmd160'
451
+
452
+
453
+ it 'double-SHA-256 encoding of string "hello"' do
454
+ # first round sha256
455
+ Digest::SHA256.hexdigest("hello").should ==
456
+ "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
457
+
458
+ # second round sha256
459
+ Digest::SHA256.hexdigest( Digest::SHA256.digest("hello") ).should ==
460
+ "9595c9df90075148eb06860365df33584b75bff782a510c6cd4883a419833d50"
461
+ end
462
+
463
+ it 'RIPEMD-160 encoding of string "hello"' do
464
+ # first round sha256
465
+ Digest::SHA256.hexdigest("hello").should ==
466
+ "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
467
+
468
+ # second round rmd160
469
+ Digest::RMD160.hexdigest( Digest::SHA256.digest("hello") ).should ==
470
+ "b6a9c8c230722b7c748331a8b450f05566dc7d0f"
471
+ end
472
+ end