bitcoin-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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