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,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