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,84 @@
1
+ module Bitcoin::Storage::Backends::SequelMigrations
2
+
3
+ def migrate
4
+ unless @db.tables.include?(:blk)
5
+ @db.create_table :blk do
6
+ primary_key :id
7
+ column :hash, :bytea, :null => false, :unique => true, :index => true
8
+ column :depth, :int, :null => false, :index => true
9
+ column :version, :bigint, :null => false
10
+ column :prev_hash, :bytea, :null => false, :index => true
11
+ column :mrkl_root, :bytea, :null => false
12
+ column :time, :bigint, :null => false
13
+ column :bits, :bigint, :null => false
14
+ column :nonce, :bigint, :null => false
15
+ column :blk_size, :int, :null => false
16
+ column :chain, :int, :null => false
17
+ end
18
+ end
19
+
20
+ unless @db.tables.include?(:tx)
21
+ @db.create_table :tx do
22
+ primary_key :id
23
+ column :hash, :bytea, :null => false, :unique => true, :index => true
24
+ column :version, :bigint, :null => false
25
+ column :lock_time, :bigint, :null => false
26
+ column :coinbase, :bool, :null => false
27
+ column :tx_size, :int, :null => false
28
+ end
29
+ end
30
+
31
+ unless @db.tables.include?(:blk_tx)
32
+ @db.create_table :blk_tx do
33
+ column :blk_id, :int, :null => false, :index => true
34
+ column :tx_id, :int, :null => false, :index => true
35
+ column :idx, :int, :null => false
36
+ end
37
+ end
38
+
39
+ unless @db.tables.include?(:txin)
40
+ @db.create_table :txin do
41
+ primary_key :id
42
+ column :tx_id, :int, :null => false, :index => true
43
+ column :tx_idx, :int, :null => false
44
+ column :script_sig, :bytea, :null => false
45
+ column :prev_out, :bytea, :null => false, :index => true
46
+ column :prev_out_index, :bigint, :null => false
47
+ column :sequence, :bigint, :null => false
48
+ end
49
+ end
50
+
51
+ unless @db.tables.include?(:txout)
52
+ @db.create_table :txout do
53
+ primary_key :id
54
+ column :tx_id, :int, :null => false, :index => true
55
+ column :tx_idx, :int, :null => false
56
+ column :pk_script, :bytea, :null => false, :index => true
57
+ column :value, :bigint
58
+ column :type, :int, :null => false, :index => true
59
+ end
60
+ end
61
+
62
+ unless @db.tables.include?(:addr)
63
+ @db.create_table :addr do
64
+ primary_key :id
65
+ column :hash160, String, :null => false, :index => true
66
+ end
67
+ end
68
+
69
+ unless @db.tables.include?(:addr_txout)
70
+ @db.create_table :addr_txout do
71
+ column :addr_id, :int, :null => false, :index => true
72
+ column :txout_id, :int, :null => false, :index => true
73
+ end
74
+ end
75
+
76
+ unless @db.views.include?(:unconfirmed)
77
+ @db.create_view(:unconfirmed,
78
+ "SELECT * FROM tx WHERE NOT EXISTS " +
79
+ "(SELECT 1 FROM blk_tx WHERE blk_tx.tx_id = tx.id)" +
80
+ "ORDER BY tx.id DESC")
81
+ end
82
+ end
83
+
84
+ end
@@ -0,0 +1,243 @@
1
+ # The storage implementation supports different backends, which inherit from
2
+ # Storage::StoreBase and implement the same interface.
3
+ # Each backend returns Storage::Models objects to easily access helper methods and metadata.
4
+ #
5
+ # The most stable backend is Backends::SequelStore, which uses sequel and can use all
6
+ # kinds of SQL database backends.
7
+ module Bitcoin::Storage
8
+
9
+ autoload :Models, 'bitcoin/storage/models'
10
+
11
+ @log = Bitcoin::Logger.create(:storage)
12
+ def self.log; @log; end
13
+
14
+ BACKENDS = [:dummy, :sequel]
15
+ BACKENDS.each do |name|
16
+ module_eval <<-EOS
17
+ def self.#{name} config, *args
18
+ Backends.const_get("#{name.capitalize}Store").new(config, *args)
19
+ end
20
+ EOS
21
+ end
22
+
23
+ module Backends
24
+
25
+ BACKENDS.each {|b| autoload("#{b.to_s.capitalize}Store", "bitcoin/storage/#{b}") }
26
+
27
+ # Base class for storage backends.
28
+ # Every backend must overwrite the "Not implemented" methods
29
+ # and provide an implementation specific to the storage.
30
+ # Also, before returning the objects, they should be wrapped
31
+ # inside the appropriate Bitcoin::Storage::Models class.
32
+ class StoreBase
33
+
34
+ # main branch (longest valid chain)
35
+ MAIN = 0
36
+
37
+ # side branch (connected, valid, but too short)
38
+ SIDE = 1
39
+
40
+ # orphan branch (not connected to main branch / genesis block)
41
+ ORPHAN = 2
42
+
43
+ attr_reader :log
44
+
45
+ def initialize(config = {}, getblocks_callback = nil)
46
+ @config = config
47
+ @getblocks_callback = getblocks_callback
48
+ @log = config[:log] || Bitcoin::Storage.log
49
+ end
50
+
51
+ # reset the store; delete all data
52
+ def reset
53
+ raise "Not implemented"
54
+ end
55
+
56
+ # store given block +blk+.
57
+ # determine branch/chain and dept of block. trigger reorg if side branch becomes longer
58
+ # than current main chain and connect orpans.
59
+ def store_block blk
60
+ log.debug { "new block #{blk.hash}" }
61
+
62
+ existing = get_block(blk.hash)
63
+ return [existing.depth, existing.chain] if existing && existing.chain == MAIN
64
+
65
+ prev_block = get_block(hth(blk.prev_block.reverse))
66
+ if !prev_block || prev_block.chain == ORPHAN
67
+ if blk.hash == Bitcoin.network[:genesis_hash]
68
+ log.debug { "=> genesis (0)" }
69
+ return persist_block(blk, MAIN, 0)
70
+ else
71
+ depth = prev_block ? prev_block.depth + 1 : 0
72
+ log.debug { "=> orphan (#{depth})" }
73
+ return persist_block(blk, ORPHAN, depth)
74
+ end
75
+ end
76
+ depth = prev_block.depth + 1
77
+ if prev_block.chain == MAIN
78
+ next_block = prev_block.get_next_block
79
+ if next_block && next_block.chain == MAIN
80
+ log.debug { "=> side (#{depth})" }
81
+ return persist_block(blk, SIDE, depth)
82
+ else
83
+ log.debug { "=> main (#{depth})" }
84
+ return persist_block(blk, MAIN, depth)
85
+ end
86
+ else
87
+ head = get_head
88
+ if prev_block.depth + 1 <= head.depth
89
+ log.debug { "=> side (#{depth})" }
90
+ return persist_block(blk, SIDE, depth)
91
+ else
92
+ log.debug { "=> reorg" }
93
+ new_main, new_side = [], []
94
+ fork_block = prev_block
95
+ while fork_block.chain != MAIN
96
+ new_main << fork_block.hash
97
+ fork_block = fork_block.get_prev_block
98
+ end
99
+ b = fork_block
100
+ while b = b.get_next_block
101
+ new_side << b.hash
102
+ end
103
+ log.debug { "new main: #{new_main.inspect}" }
104
+ log.debug { "new side: #{new_side.inspect}" }
105
+ update_blocks([[new_main, {:chain => MAIN}], [new_side, {:chain => SIDE}]])
106
+ return persist_block(blk, MAIN, depth)
107
+ end
108
+ end
109
+ end
110
+
111
+ # persist given block +blk+ to storage.
112
+ def persist_block(blk)
113
+ raise "Not implemented"
114
+ end
115
+
116
+ # update +attrs+ for block with given +hash+.
117
+ # typically used to update chain.
118
+ def update_block(hash, attrs)
119
+ raise "Not implemented"
120
+ end
121
+
122
+ # store given +tx+
123
+ def store_tx(tx)
124
+ raise "Not implemented"
125
+ end
126
+
127
+ # check if block with given +blk_hash+ is already stored
128
+ def has_block(blk_hash)
129
+ raise "Not implemented"
130
+ end
131
+
132
+ # check if tx with given +tx_hash+ is already stored
133
+ def has_tx(tx_hash)
134
+ raise "Not implemented"
135
+ end
136
+
137
+ # get the hash of the leading block
138
+ def get_head
139
+ raise "Not implemented"
140
+ end
141
+
142
+ # return depth of the head block
143
+ def get_depth
144
+ raise "Not implemented"
145
+ end
146
+
147
+ # compute blockchain locator
148
+ def get_locator pointer = get_head
149
+ return [Bitcoin::hth("\x00"*32)] if get_depth == -1
150
+ locator = []
151
+ step = 1
152
+ while pointer && pointer.hash != Bitcoin::network[:genesis_hash]
153
+ locator << pointer.hash
154
+ depth = pointer.depth - step
155
+ break unless depth > 0
156
+ prev_block = get_block_by_depth(depth) # TODO
157
+ break unless prev_block
158
+ pointer = prev_block
159
+ step *= 2 if locator.size > 10
160
+ end
161
+ locator << Bitcoin::network[:genesis_hash]
162
+ locator
163
+ end
164
+
165
+ # get block with given +blk_hash+
166
+ def get_block(blk_hash)
167
+ raise "Not implemented"
168
+ end
169
+
170
+ # get block with given +depth+ from main chain
171
+ def get_block_by_depth(depth)
172
+ raise "Not implemented"
173
+ end
174
+
175
+ # get block with given +prev_hash+
176
+ def get_block_by_prev_hash(prev_hash)
177
+ raise "Not implemented"
178
+ end
179
+
180
+ # get block that includes tx with given +tx_hash+
181
+ def get_block_by_tx(tx_hash)
182
+ raise "Not implemented"
183
+ end
184
+
185
+ # get block by given +block_id+
186
+ def get_block_by_id(block_id)
187
+ raise "Not implemented"
188
+ end
189
+
190
+ # get corresponding txin for the txout in
191
+ # transaction +tx_hash+ with index +txout_idx+
192
+ def get_txin_for_txout(tx_hash, txout_idx)
193
+ raise "Not implemented"
194
+ end
195
+
196
+ # get tx with given +tx_hash+
197
+ def get_tx(tx_hash)
198
+ raise "Not implemented"
199
+ end
200
+
201
+ # get tx with given +tx_id+
202
+ def get_tx_by_id(tx_id)
203
+ raise "Not implemented"
204
+ end
205
+
206
+ # collect all txouts containing the
207
+ # given +script+
208
+ def get_txouts_for_pk_script(script)
209
+ raise "Not implemented"
210
+ end
211
+
212
+ # collect all txouts containing a
213
+ # standard tx to given +address+
214
+ def get_txouts_for_address(address, unconfirmed = false)
215
+ hash160 = Bitcoin.hash160_from_address(address)
216
+ get_txouts_for_hash160(hash160, unconfirmed)
217
+ end
218
+
219
+ # get balance for given +hash160+
220
+ def get_balance(hash160, unconfirmed = false)
221
+ txouts = get_txouts_for_hash160(hash160, unconfirmed)
222
+ unspent = txouts.select {|o| o.get_next_in.nil?}
223
+ unspent.map(&:value).inject {|a,b| a+=b; a} || 0
224
+ rescue
225
+ nil
226
+ end
227
+
228
+ # import satoshi bitcoind blk0001.dat blockchain file
229
+ def import filename, max_depth = nil
230
+ File.open(filename) do |file|
231
+ until file.eof?
232
+ magic = file.read(4)
233
+ raise "invalid network magic" unless Bitcoin.network[:magic_head] == magic
234
+ size = file.read(4).unpack("L")[0]
235
+ blk = Bitcoin::P::Block.new(file.read(size))
236
+ depth, chain = store_block(blk)
237
+ break if max_depth && depth >= max_depth
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,3 @@
1
+ module Bitcoin
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,30 @@
1
+ module Bitcoin::Wallet
2
+
3
+ # select unspent txouts to be used by the Wallet when creating a new transaction
4
+ class SimpleCoinSelector
5
+
6
+ # create coinselector with given +txouts+
7
+ def initialize txouts
8
+ @txouts = txouts
9
+ end
10
+
11
+ # select txouts needed to spend +value+ btc (base units)
12
+ def select(value)
13
+ txouts = []
14
+ @txouts.each do |txout|
15
+ begin
16
+ next if txout.get_next_in
17
+ next unless txout.get_address
18
+ next unless txout.get_tx.get_block
19
+ txouts << txout
20
+ return txouts if txouts.map(&:value).inject(:+) >= value
21
+ rescue
22
+ p $!
23
+ end
24
+ end
25
+ nil
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,75 @@
1
+ module Bitcoin::Wallet
2
+
3
+ # Deterministic key generator as described in
4
+ # https://bitcointalk.org/index.php?topic=11665.0.
5
+ #
6
+ # Takes a seed and generates an arbitrary amount of keys.
7
+ # Protects against brute-force attacks by requiring the
8
+ # key hash to fit a difficulty target, much like the block chain.
9
+ class KeyGenerator
10
+
11
+ # difficulty target (0x0000FFFF00000000000000000000000000000000000000000000000000000000)
12
+ DEFAULT_TARGET = 0x0000FFFF00000000000000000000000000000000000000000000000000000000
13
+
14
+ attr_accessor :seed, :nonce, :target
15
+
16
+ # Initialize key generator with optional +seed+ and +nonce+ and +target+.
17
+ # [seed] the seed data for the keygenerator (default: random)
18
+ # [nonce] the nonce required to satisfy the target (default: computed)
19
+ # [target] custom difficulty target (default: DEFAULT_TARGET)
20
+ #
21
+ # Example:
22
+ # g = KeyGenerator.new # random seed, computed nonce, default target
23
+ # KeyGenerator.new(g.seed)
24
+ # KeyGenerator.new(g.seed, g.nonce)
25
+ # g.get_key(0) #=> <Bitcoin::Key>
26
+ #
27
+ # Note: When initializing without seed, you should obviously save the
28
+ # seed once it is generated. Saving the nonce is optional; it only saves time.
29
+ def initialize seed = nil, nonce = nil, target = nil
30
+ @seed = seed || OpenSSL::Random.random_bytes(64)
31
+ @target = target || DEFAULT_TARGET
32
+ @nonce = check_nonce(nonce)
33
+ end
34
+
35
+ # get key number +n+ from chain
36
+ def get_key(n = 0)
37
+ key = get_hash(@seed, @nonce)
38
+ (n + 1).times { key = sha256(key) }
39
+ key
40
+ Bitcoin::Key.new(key.unpack("H*")[0])
41
+ end
42
+
43
+ # find a nonce that leads to the privkey satisfying the target
44
+ def find_nonce
45
+ n = 0
46
+ n += 1 while !check_target(get_hash(@seed, n))
47
+ n
48
+ end
49
+
50
+ protected
51
+
52
+ # check the nonce; compute if missing, raise if invalid.
53
+ def check_nonce(nonce)
54
+ return find_nonce unless nonce
55
+ # check_target(get_hash(@seed, nonce)) ? nonce : find_nonce
56
+ raise ArgumentError, "Nonce invalid." unless check_target(get_hash(@seed, nonce))
57
+ nonce
58
+ end
59
+
60
+ # check if given +hash+ satisfies the difficulty target
61
+ def check_target(hash)
62
+ hash.unpack("H*")[0].to_i(16) < @target
63
+ end
64
+
65
+ # compute a single SHA256 hash for +d+.
66
+ def sha256(d); Digest::SHA256.digest(d); end
67
+
68
+ # get the hash corresponding to +seed+ and +n+.
69
+ def get_hash(seed, n)
70
+ sha256( sha256(seed) + sha256(n.to_s) )
71
+ end
72
+
73
+ end
74
+
75
+ end
@@ -0,0 +1,203 @@
1
+ require 'json'
2
+ require 'stringio'
3
+
4
+ module Bitcoin::Wallet
5
+
6
+ # JSON-file-based keystore used by the Wallet.
7
+ class SimpleKeyStore
8
+
9
+ attr_reader :config
10
+
11
+ # Initialize keystore.
12
+ # [config] Hash of settings ({:file => "/foo/bar.json"})
13
+ def initialize config
14
+ @config = Hash[config.map{|k,v|[k.to_sym,v]}]
15
+ @keys = []
16
+ load_keys
17
+ end
18
+
19
+ # List all stored keys.
20
+ def keys(need = nil)
21
+ @keys.select do |key|
22
+ next !(key[:hidden] && key[:hidden] == "true") unless need
23
+ case need
24
+ when :label
25
+ !!key[:label]
26
+ when :pub
27
+ !!key[:key].pub
28
+ when :priv
29
+ !!key[:key].priv
30
+ when :hidden
31
+ !!key[:hidden]
32
+ when :mine
33
+ !!key[:mine]
34
+ end
35
+ end
36
+ end
37
+
38
+ # Get key for given +label+, +addr+ or +pubkey+.
39
+ def key(name)
40
+ find_key(name)
41
+ end
42
+
43
+ # Generate and store a new key.
44
+ def new_key(label = nil)
45
+ raise ArgumentError, "Label #{label} already in use" if label && find_key(label)
46
+ key = Bitcoin::Key.generate
47
+ @keys << {:label => label, :addr => key.addr, :key => key}
48
+ save_keys
49
+ key
50
+ end
51
+
52
+ # Add a key which can consist only of +addr+ and +label+.
53
+ def add_key key
54
+ label = key[:label]
55
+ raise ArgumentError, "Label #{label} already in use" if label && find_key(label)
56
+ addr = key[:addr]
57
+ raise ArgumentError, "Address #{addr} is invalid" if addr && !Bitcoin.valid_address?(addr)
58
+ @keys << key
59
+ save_keys
60
+ key
61
+ end
62
+
63
+ def label_key(name, label)
64
+ find_key(name) do |key|
65
+ key[:label] = label
66
+ end
67
+ save_keys
68
+ end
69
+
70
+ def flag_key(name, flag, value)
71
+ find_key(name, true) do |key|
72
+ key[flag.to_sym] = value
73
+ end
74
+ save_keys
75
+ end
76
+
77
+ # Delete key for given +label+, +addr+ or +pubkey+.
78
+ def delete(name)
79
+ key = find_key(name)
80
+ @keys.delete(key)
81
+ save_keys
82
+ end
83
+
84
+ # Export key for given +name+ to base58 format.
85
+ # (See Bitcoin::Key#to_base58)
86
+ def export(name)
87
+ find_key(name)[:key].to_base58 rescue nil
88
+ end
89
+
90
+ # Import key from given +base58+ string.
91
+ # (See Bitcoin::Key.from_base58)
92
+ def import(base58, label = nil)
93
+ raise ArgumentError, "Label #{label} already in use" if label && find_key(label)
94
+ key = Bitcoin::Key.from_base58(base58)
95
+ @keys << {:label => label, :addr => key.addr, :key => key}
96
+ save_keys
97
+ key.addr
98
+ end
99
+
100
+ # Load keys from file.
101
+ # If file is empty this will generate a new key
102
+ # and store it, creating the file.
103
+ def load_keys
104
+ loader = proc{|keys|
105
+ keys.map!{|k| Hash[k.map{|k,v| [k.to_sym, v] }]}
106
+ keys.map do |key|
107
+ key[:key] = Bitcoin::Key.new(key[:priv], key[:pub])
108
+ key[:priv], key[:pub] = nil
109
+ @keys << key
110
+ end
111
+ }
112
+ if @config[:file].is_a?(StringIO)
113
+ json = JSON.load(@config[:file].read)
114
+ loader.call(json)
115
+ elsif File.exist?(@config[:file])
116
+ json = JSON.load(File.read(@config[:file]))
117
+ loader.call(json)
118
+ else
119
+ new_key; save_keys
120
+ end
121
+ end
122
+
123
+ # Save keys to file.
124
+ def save_keys
125
+ dumper = proc{|file|
126
+ keys = @keys.map do |key|
127
+ key = key.dup
128
+ if key[:key]
129
+ key[:priv] = key[:key].priv
130
+ key[:pub] = key[:key].pub
131
+ key.delete(:key)
132
+ end
133
+ key
134
+ end
135
+ file.write(JSON.pretty_generate(keys))
136
+ }
137
+
138
+ if @config[:file].is_a?(StringIO)
139
+ @config[:file].reopen
140
+ dumper.call(@config[:file])
141
+ @config[:file].rewind
142
+ elsif File.exists?(@config[:file])
143
+ File.open(@config[:file], 'w'){|file| dumper.call(file) }
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def find_key(name, hidden = false)
150
+ key = if Bitcoin.valid_address?(name)
151
+ @keys.find{|k| k[:addr] == name }
152
+ elsif name.size == 130
153
+ @keys.find{|k| k[:key].pub == name }
154
+ else
155
+ @keys.find{|k| k[:label] == name }
156
+ end
157
+ return nil if !key || (!hidden && key[:hidden] == "true")
158
+ block_given? ? yield(key) : key
159
+ end
160
+
161
+ end
162
+
163
+ # Deterministic keystore.
164
+ class DeterministicKeyStore
165
+
166
+ attr_reader :generator
167
+
168
+ # Initialize keystore.
169
+ # [config] Hash of settings ({:keys => 1, :seed => ..., :nonce => ...})
170
+ def initialize config
171
+ @config = Hash[config.map{|k,v|[k.to_sym,v]}]
172
+ @config[:keys] = (@config[:keys] || 1).to_i
173
+ @generator = Bitcoin::Wallet::KeyGenerator.new(@config[:seed], @config[:nonce])
174
+ end
175
+
176
+ # List all keys upto configured limit.
177
+ def keys
178
+ 1.upto(@config[:keys].to_i).map {|i| @generator.get_key(i) }
179
+ end
180
+
181
+ # Get key for given +addr+.
182
+ def key(addr)
183
+ 1.upto(@config[:keys].to_i).map do |i|
184
+ key = @generator.get_key(i)
185
+ return key if key.addr == addr
186
+ end
187
+ end
188
+
189
+ # Get new key (actually just increase the key limit).
190
+ def new_key
191
+ @config[:keys] += 1
192
+ @generator.get_key(@config[:keys])
193
+ end
194
+
195
+ # Export key for given +addr+ to base58.
196
+ # (See Bitcoin::Key.to_base58)
197
+ def export(addr)
198
+ key(addr).to_base58 rescue nil
199
+ end
200
+
201
+ end
202
+
203
+ end