bitcoin-ruby 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. data/.gitignore +4 -1
  2. data/Gemfile +21 -0
  3. data/README.rdoc +85 -25
  4. data/Rakefile +7 -3
  5. data/bin/bitcoin_node +39 -42
  6. data/bin/bitcoin_shell +1 -0
  7. data/bin/bitcoin_wallet +129 -53
  8. data/bitcoin-ruby.gemspec +4 -7
  9. data/concept-examples/blockchain-pow.rb +1 -1
  10. data/doc/CONFIG.rdoc +5 -5
  11. data/doc/EXAMPLES.rdoc +9 -5
  12. data/doc/NAMECOIN.rdoc +34 -0
  13. data/doc/NODE.rdoc +147 -10
  14. data/examples/balance.rb +10 -4
  15. data/examples/bbe_verify_tx.rb +7 -2
  16. data/examples/forwarder.rb +73 -0
  17. data/examples/generate_tx.rb +34 -0
  18. data/examples/simple_network_monitor_and_util.rb +187 -0
  19. data/examples/verify_tx.rb +1 -1
  20. data/lib/bitcoin.rb +308 -18
  21. data/lib/bitcoin/builder.rb +62 -36
  22. data/lib/bitcoin/config.rb +2 -0
  23. data/lib/bitcoin/connection.rb +11 -8
  24. data/lib/bitcoin/electrum/mnemonic.rb +162 -0
  25. data/lib/bitcoin/ffi/openssl.rb +187 -21
  26. data/lib/bitcoin/gui/addr_view.rb +2 -0
  27. data/lib/bitcoin/gui/conn_view.rb +2 -0
  28. data/lib/bitcoin/gui/connection.rb +2 -0
  29. data/lib/bitcoin/gui/em_gtk.rb +2 -0
  30. data/lib/bitcoin/gui/gui.rb +2 -0
  31. data/lib/bitcoin/gui/helpers.rb +2 -0
  32. data/lib/bitcoin/gui/tree_view.rb +2 -0
  33. data/lib/bitcoin/gui/tx_view.rb +2 -0
  34. data/lib/bitcoin/key.rb +77 -11
  35. data/lib/bitcoin/litecoin.rb +81 -0
  36. data/lib/bitcoin/logger.rb +20 -1
  37. data/lib/bitcoin/namecoin.rb +279 -0
  38. data/lib/bitcoin/network/command_client.rb +7 -6
  39. data/lib/bitcoin/network/command_handler.rb +229 -43
  40. data/lib/bitcoin/network/connection_handler.rb +182 -70
  41. data/lib/bitcoin/network/node.rb +231 -106
  42. data/lib/bitcoin/protocol.rb +44 -23
  43. data/lib/bitcoin/protocol/address.rb +5 -3
  44. data/lib/bitcoin/protocol/alert.rb +3 -4
  45. data/lib/bitcoin/protocol/aux_pow.rb +123 -0
  46. data/lib/bitcoin/protocol/block.rb +98 -18
  47. data/lib/bitcoin/protocol/handler.rb +6 -5
  48. data/lib/bitcoin/protocol/parser.rb +44 -19
  49. data/lib/bitcoin/protocol/tx.rb +105 -52
  50. data/lib/bitcoin/protocol/txin.rb +39 -19
  51. data/lib/bitcoin/protocol/txout.rb +28 -13
  52. data/lib/bitcoin/protocol/version.rb +16 -7
  53. data/lib/bitcoin/script.rb +579 -122
  54. data/lib/bitcoin/storage/{dummy.rb → dummy/dummy_store.rb} +8 -14
  55. data/lib/bitcoin/storage/models.rb +20 -7
  56. data/lib/bitcoin/storage/{sequel_store/sequel_migrations.rb → sequel/migrations.rb} +22 -7
  57. data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +52 -0
  58. data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +50 -0
  59. data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +18 -0
  60. data/lib/bitcoin/storage/sequel/sequel_store.rb +436 -0
  61. data/lib/bitcoin/storage/storage.rb +233 -28
  62. data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +52 -0
  63. data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +18 -0
  64. data/lib/bitcoin/storage/utxo/utxo_store.rb +361 -0
  65. data/lib/bitcoin/validation.rb +369 -0
  66. data/lib/bitcoin/version.rb +1 -1
  67. data/lib/bitcoin/wallet/coinselector.rb +3 -0
  68. data/lib/bitcoin/wallet/keygenerator.rb +3 -1
  69. data/lib/bitcoin/wallet/keystore.rb +6 -2
  70. data/lib/bitcoin/wallet/txdp.rb +6 -4
  71. data/lib/bitcoin/wallet/wallet.rb +54 -16
  72. data/spec/bitcoin/bitcoin_spec.rb +48 -3
  73. data/spec/bitcoin/builder_spec.rb +40 -17
  74. data/spec/bitcoin/fixtures/000000000000056b1a3d84a1e2b33cde8915a4b61c0cae14fca6d3e1490b4f98.json +3697 -0
  75. data/spec/bitcoin/fixtures/03d7e1fa4d5fefa169431f24f7798552861b255cd55d377066fedcd088fb0e99.json +23 -0
  76. data/spec/bitcoin/fixtures/0961c660358478829505e16a1f028757e54b5bbf9758341a7546573738f31429.json +24 -0
  77. data/spec/bitcoin/fixtures/0f24294a1d23efbb49c1765cf443fba7930702752aba6d765870082fe4f13cae.json +37 -0
  78. data/spec/bitcoin/fixtures/315ac7d4c26d69668129cc352851d9389b4a6868f1509c6c8b66bead11e2619f.json +31 -0
  79. data/spec/bitcoin/fixtures/35e2001b428891fefa0bfb73167c7360669d3cbd7b3aa78e7cad125ddfc51131.json +27 -0
  80. data/spec/bitcoin/fixtures/3a17dace09ffb919ed627a93f1873220f4c975c1248558b18d16bce25d38c4b7.json +72 -0
  81. data/spec/bitcoin/fixtures/3e58b7eed0fdb599019af08578effea25c8666bbe8e200845453cacce6314477.json +27 -0
  82. data/spec/bitcoin/fixtures/514c46f0b61714092f15c8dfcb576c9f79b3f959989b98de3944b19d98832b58.json +24 -0
  83. data/spec/bitcoin/fixtures/51bf528ecf3c161e7c021224197dbe84f9a8564212f6207baa014c01a1668e1e.json +30 -0
  84. data/spec/bitcoin/fixtures/69216b8aaa35b76d6613e5f527f4858640d986e1046238583bdad79b35e938dc.json +28 -0
  85. data/spec/bitcoin/fixtures/7208e5edf525f04e705fb3390194e316205b8f995c8c9fcd8c6093abe04fa27d.json +27 -0
  86. data/spec/bitcoin/fixtures/761d8c5210fdfd505f6dff38f740ae3728eb93d7d0971fb433f685d40a4c04f6.json +27 -0
  87. data/spec/bitcoin/fixtures/aea682d68a3ea5e3583e088dcbd699a5d44d4b083f02ad0aaf2598fe1fa4dfd4.json +27 -0
  88. data/spec/bitcoin/fixtures/bd1715f1abfdc62bea3f605bdb461b3ba1f2cca6ec0d73a18a548b7717ca8531.json +34 -0
  89. data/spec/bitcoin/fixtures/block-testnet-0000000000ac85bb2530a05a4214a387e6be02b22d3348abc5e7a5d9c4ce8dab.bin +0 -0
  90. data/spec/bitcoin/fixtures/cd874fa8cb0e2ec2d385735d5e1fd482c4fe648533efb4c50ee53bda58e15ae2.json +24 -0
  91. data/spec/bitcoin/fixtures/ce5fad9b4ef094d8f4937b0707edaf0a6e6ceeaf67d5edbfd51f660eac8f398b.json +41 -0
  92. data/spec/bitcoin/fixtures/f003f0c1193019db2497a675fd05d9f2edddf9b67c59e677c48d3dbd4ed5f00b.json +23 -0
  93. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
  94. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +43 -0
  95. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
  96. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +67 -0
  97. data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.bin +0 -0
  98. data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.json +39 -0
  99. data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.bin +0 -0
  100. data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.json +39 -0
  101. data/spec/bitcoin/fixtures/rawblock-auxpow.bin +0 -0
  102. data/spec/bitcoin/fixtures/tx-313897799b1e37e9ecae15010e56156dddde4e683c96b0e713af95272c38aee0.json +30 -0
  103. data/spec/bitcoin/fixtures/tx-3da75972766f0ad13319b0b461fd16823a731e44f6e9de4eb3c52d6a6fb6c8ae.json +23 -0
  104. data/spec/bitcoin/fixtures/tx-44b833074e671120ba33106877b49e86ece510824b9af477a3853972bcd8d06a.json +30 -0
  105. data/spec/bitcoin/fixtures/tx-d3d77d63709e47d9ef58f0b557800115a6b676c6a423012fbb96f45d8fcef830.json +28 -0
  106. data/spec/bitcoin/key_spec.rb +128 -3
  107. data/spec/bitcoin/namecoin_spec.rb +182 -0
  108. data/spec/bitcoin/network_spec.rb +5 -3
  109. data/spec/bitcoin/node/command_api_spec.rb +376 -0
  110. data/spec/bitcoin/protocol/addr_spec.rb +2 -0
  111. data/spec/bitcoin/protocol/alert_spec.rb +2 -0
  112. data/spec/bitcoin/protocol/aux_pow_spec.rb +44 -0
  113. data/spec/bitcoin/protocol/block_spec.rb +134 -39
  114. data/spec/bitcoin/protocol/getblocks_spec.rb +32 -0
  115. data/spec/bitcoin/protocol/inv_spec.rb +10 -8
  116. data/spec/bitcoin/protocol/notfound_spec.rb +31 -0
  117. data/spec/bitcoin/protocol/ping_spec.rb +2 -0
  118. data/spec/bitcoin/protocol/tx_spec.rb +83 -17
  119. data/spec/bitcoin/protocol/version_spec.rb +7 -5
  120. data/spec/bitcoin/script/opcodes_spec.rb +412 -133
  121. data/spec/bitcoin/script/script_spec.rb +112 -13
  122. data/spec/bitcoin/spec_helper.rb +68 -0
  123. data/spec/bitcoin/storage/reorg_spec.rb +199 -0
  124. data/spec/bitcoin/storage/storage_spec.rb +337 -0
  125. data/spec/bitcoin/storage/validation_spec.rb +261 -0
  126. data/spec/bitcoin/wallet/coinselector_spec.rb +10 -7
  127. data/spec/bitcoin/wallet/keygenerator_spec.rb +2 -0
  128. data/spec/bitcoin/wallet/keystore_spec.rb +2 -0
  129. data/spec/bitcoin/wallet/txdp_spec.rb +2 -0
  130. data/spec/bitcoin/wallet/wallet_spec.rb +91 -58
  131. metadata +105 -51
  132. data/lib/bitcoin/storage/sequel.rb +0 -335
  133. data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +0 -27
  134. data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +0 -27
  135. data/spec/bitcoin/reorg_spec.rb +0 -129
  136. data/spec/bitcoin/storage_spec.rb +0 -229
@@ -0,0 +1,369 @@
1
+ # encoding: ascii-8bit
2
+
3
+ module Bitcoin::Validation
4
+
5
+ # maximum size of a block (in bytes)
6
+ MAX_BLOCK_SIZE = 1_000_000
7
+
8
+ # maximum number of signature operations in a block
9
+ MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE / 50
10
+
11
+ # maximum integer value
12
+ INT_MAX = 0xffffffff
13
+
14
+ # number of confirmations required before coinbase tx can be spent
15
+ COINBASE_MATURITY = 100
16
+
17
+ # interval (in blocks) for difficulty retarget
18
+ RETARGET = 2016
19
+
20
+ # interval (in blocks) for mining reward reduction
21
+ REWARD_DROP = 210_000
22
+
23
+ class ValidationError < StandardError
24
+ end
25
+
26
+
27
+ class Block
28
+ attr_accessor :block, :store, :prev_block, :error
29
+
30
+ RULES = {
31
+ syntax: [:hash, :tx_list, :bits, :max_timestamp, :coinbase, :coinbase_scriptsig, :mrkl_root, :transactions_syntax],
32
+ context: [:prev_hash, :difficulty, :coinbase_value, :min_timestamp, :transactions_context]
33
+ }
34
+
35
+ # TODO merged mining validations
36
+ if Bitcoin.namecoin?
37
+ RULES[:syntax] -= [:bits, :coinbase, :coinbase_scriptsig, :mrkl_root]
38
+ RULES[:context] -= [:difficulty, :coinbase_value]
39
+ end
40
+
41
+ # validate block rules. +opts+ are:
42
+ # rules:: which rulesets to validate (default: [:syntax, :context])
43
+ # raise_errors:: whether to raise ValidationError on failure (default: false)
44
+ def validate(opts = {})
45
+ return true if KNOWN_EXCEPTIONS.include?(block.hash)
46
+ opts[:rules] ||= [:syntax, :context]
47
+ opts[:rules].each do |name|
48
+ store.log.debug { "validating block #{name} #{block.hash} (#{block.to_payload.bytesize} bytes)" }
49
+ RULES[name].each.with_index do |rule, i|
50
+ unless (res = send(rule)) && res == true
51
+ raise ValidationError, "block error: #{name} check #{i} - #{rule} failed" if opts[:raise_errors]
52
+ @error = [rule, res]
53
+ return false
54
+ end
55
+ end
56
+ end
57
+ true
58
+ end
59
+
60
+ # setup new validator for given +block+, validating context with +store+,
61
+ # optionally passing the +prev_block+ for optimization.
62
+ def initialize block, store, prev_block = nil
63
+ @block, @store, @error = block, store, nil
64
+ @prev_block = prev_block || store.get_block(block.prev_block.reverse_hth)
65
+ end
66
+
67
+ # check that block hash matches header
68
+ def hash
69
+ claimed = block.hash; real = block.recalc_block_hash
70
+ claimed == real || [claimed, real]
71
+ end
72
+
73
+ # check that block has at least one tx (the coinbase)
74
+ def tx_list
75
+ block.tx.any? || block.tx.size
76
+ end
77
+
78
+ # check that block hash matches claimed bits
79
+ def bits
80
+ actual = block.hash.to_i(16)
81
+ expected = Bitcoin.decode_compact_bits(block.bits).to_i(16)
82
+ actual <= expected || [actual, expected]
83
+ end
84
+
85
+ # check that block time is not greater than max
86
+ def max_timestamp
87
+ time, max = block.time, Time.now.to_i + 2*60*60
88
+ time < max || [time, max]
89
+ end
90
+
91
+ # check that coinbase is present
92
+ def coinbase
93
+ coinbase, *rest = block.tx.map{|t| t.inputs.size == 1 && t.inputs.first.coinbase? }
94
+ (coinbase && rest.none?) || [coinbase ? 1 : 0, rest.select{|r| r}.size]
95
+ end
96
+
97
+ # check that coinbase scriptsig is valid
98
+ def coinbase_scriptsig
99
+ size = block.tx.first.in.first.script_sig.bytesize
100
+ size.between?(2,100) || [size, 2, 100]
101
+ end
102
+
103
+ # check that coinbase value is valid; no more than reward + fees
104
+ def coinbase_value
105
+ reward = ((50.0 / (2 ** (store.get_depth / REWARD_DROP.to_f).floor)) * 1e8).to_i
106
+ fees = 0
107
+ block.tx[1..-1].map.with_index do |t, idx|
108
+ val = tx_validators[idx]
109
+ fees += t.in.map.with_index {|i, idx|
110
+ val.prev_txs[idx].out[i.prev_out_index].value rescue 0
111
+ }.inject(:+)
112
+ val.clear_cache # memory optimization on large coinbases, see testnet3 block 4110
113
+ end
114
+ coinbase_output = block.tx[0].out.map(&:value).inject(:+)
115
+ coinbase_output <= reward + fees || [coinbase_output, reward, fees]
116
+ end
117
+
118
+ # check that merkle root matches transaction hashes
119
+ def mrkl_root
120
+ actual, expected = block.mrkl_root.reverse_hth, Bitcoin.hash_mrkl_tree(block.tx.map(&:hash))[-1]
121
+ actual == expected || [actual, expected]
122
+ end
123
+
124
+ def prev_hash
125
+ @prev_block && @prev_block.hash == block.prev_block.reverse_hth
126
+ end
127
+
128
+ # check that bits satisfy required difficulty
129
+ def difficulty
130
+ return true if Bitcoin.network_name == :testnet3
131
+ block.bits == next_bits_required || [block.bits, next_bits_required]
132
+ end
133
+
134
+ # check that timestamp is newer than the median of the last 11 blocks
135
+ def min_timestamp
136
+ return true if store.get_depth <= 11
137
+ d = store.get_depth
138
+ first = store.db[:blk][hash: block.prev_block.reverse.blob]
139
+ times = [first[:time]]
140
+ (10).times { first = store.db[:blk][hash: first[:prev_hash].blob]
141
+ times << first[:time] }
142
+ times.sort!
143
+ mid, rem = times.size.divmod(2)
144
+ min_time = (rem == 0 ? times[mid-1, 2].inject(:+) / 2.0 : times[mid])
145
+
146
+ block.time > min_time || [block.time, min_time]
147
+ end
148
+
149
+ # check transactions
150
+ def transactions_syntax
151
+ tx_validators.all?{|v|
152
+ begin
153
+ v.validate(rules: [:syntax], raise_errors: true)
154
+ rescue ValidationError
155
+ store.log.info { $!.message }
156
+ return false
157
+ end
158
+ }
159
+ end
160
+
161
+ def transactions_context
162
+ tx_validators.all?{|v|
163
+ begin
164
+ v.validate(rules: [:context], raise_errors: true)
165
+ rescue ValidationError
166
+ store.log.info { $!.message }
167
+ return false
168
+ end
169
+ }
170
+ end
171
+
172
+ def tx_validators
173
+ @tx_validators ||= block.tx[1..-1].map {|tx| tx.validator(store, block) }
174
+ end
175
+
176
+ def next_bits_required
177
+ index = (prev_block.depth + 1) / RETARGET
178
+ max_target = Bitcoin.decode_compact_bits(Bitcoin.network[:proof_of_work_limit]).to_i(16)
179
+ return Bitcoin.network[:proof_of_work_limit] if index == 0
180
+ return prev_block.bits if (prev_block.depth + 1) % RETARGET != 0
181
+ last = store.db[:blk][hash: prev_block.hash.htb.blob]
182
+ first = store.db[:blk][hash: last[:prev_hash].blob]
183
+ (RETARGET-2).times { first = store.db[:blk][hash: first[:prev_hash].blob] }
184
+
185
+ nActualTimespan = last[:time] - first[:time]
186
+ nTargetTimespan = RETARGET * 600
187
+
188
+ nActualTimespan = [nActualTimespan, nTargetTimespan/4].max
189
+ nActualTimespan = [nActualTimespan, nTargetTimespan*4].min
190
+
191
+ target = Bitcoin.decode_compact_bits(last[:bits]).to_i(16)
192
+ new_target = [max_target, (target * nActualTimespan)/nTargetTimespan].min
193
+ Bitcoin.encode_compact_bits new_target.to_s(16)
194
+ end
195
+
196
+ KNOWN_EXCEPTIONS = [
197
+ Bitcoin.network[:genesis_hash], # genesis block
198
+ "00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec", # BIP30 exception
199
+ "00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721", # BIP30 exception
200
+ ]
201
+
202
+ end
203
+
204
+ class Tx
205
+ attr_accessor :tx, :store, :error
206
+
207
+ RULES = {
208
+ syntax: [:hash, :lists, :max_size, :output_values, :inputs, :lock_time, :standard],
209
+ context: [:prev_out, :signatures, :spent, :input_values, :output_sum]
210
+ }
211
+
212
+ # validate tx rules. +opts+ are:
213
+ # rules:: which rulesets to validate (default: [:syntax, :context])
214
+ # raise_errors:: whether to raise ValidationError on failure (default: false)
215
+ def validate(opts = {})
216
+ return true if KNOWN_EXCEPTIONS.include?(tx.hash)
217
+ opts[:rules] ||= [:syntax, :context]
218
+ opts[:rules].each do |name|
219
+ store.log.debug { "validating tx #{name} #{tx.hash} (#{tx.to_payload.bytesize} bytes)" } if store
220
+ RULES[name].each.with_index do |rule, i|
221
+ unless (res = send(rule)) && res == true
222
+ raise ValidationError, "tx error: #{name} check #{i} - #{rule} failed" if opts[:raise_errors]
223
+ @error = [rule, res]
224
+ return false
225
+ end
226
+ end
227
+ end
228
+ clear_cache # memory optimizatons
229
+ true
230
+ end
231
+
232
+ KNOWN_EXCEPTIONS = [
233
+ # p2sh with invalid inner script, accepted by old miner before 4-2012 switchover
234
+ "6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192",
235
+ # p2sh with invalid inner script, accepted by old miner before 4-2012 switchover (testnet)
236
+ "b3c19d78b4953b694717a47d9852f8ea1ccd4cf93a45ba2e43a0f97d7cdb2655"
237
+ ]
238
+
239
+ # setup new validator for given +tx+, validating context with +store+.
240
+ # also needs the +block+ to find prev_outs for chains of tx inside one block.
241
+ def initialize tx, store, block = nil
242
+ @tx, @store, @block, @errors = tx, store, block, []
243
+ end
244
+
245
+ # check that tx hash matches data
246
+ def hash
247
+ generated_hash = tx.generate_hash(tx.to_payload)
248
+ tx.hash == generated_hash || [tx.hash, generated_hash]
249
+ end
250
+
251
+ # check that tx has at least one input and one output
252
+ def lists
253
+ (tx.in.any? && tx.out.any?) || [tx.in.size, tx.out.size]
254
+ end
255
+
256
+ # check that tx size doesn't exceed MAX_BLOCK_SIZE.
257
+ def max_size
258
+ tx.to_payload.bytesize <= MAX_BLOCK_SIZE || [tx.to_payload.bytesize, MAX_BLOCK_SIZE]
259
+ end
260
+
261
+ # check that total output value doesn't exceed MAX_MONEY.
262
+ def output_values
263
+ total = tx.out.inject(0) {|e, out| e + out.value }
264
+ total <= Bitcoin::network[:max_money] || [total, Bitcoin::network[:max_money]]
265
+ end
266
+
267
+ # check that none of the inputs is coinbase
268
+ # (coinbase tx do not get validated)
269
+ def inputs
270
+ tx.inputs.none?(&:coinbase?) || [tx.inputs.index(tx.inputs.find(&:coinbase?))]
271
+ end
272
+
273
+ # check that lock_time doesn't exceed INT_MAX
274
+ def lock_time
275
+ tx.lock_time <= INT_MAX || [tx.lock_time, INT_MAX]
276
+ end
277
+
278
+ # check that min_size is at least 86 bytes
279
+ # (smaller tx can't be valid / do anything useful)
280
+ def min_size
281
+ tx.to_payload.bytesize >= 86 || [tx.to_payload.bytesize, 86]
282
+ end
283
+
284
+ # check that tx matches "standard" rules.
285
+ # this is currently disabled since not all miners enforce it.
286
+ def standard
287
+ return true # not enforced by all miners
288
+ return false unless min_size
289
+ tx.out.all? {|o| Bitcoin::Script.new(o.pk_script).is_standard? }
290
+ end
291
+
292
+ # check that all prev_outs exist
293
+ # (and are in a block in the main chain, or the current block; see #prev_txs)
294
+ def prev_out
295
+ missing = tx.in.reject.with_index {|txin, idx|
296
+ prev_txs[idx].out[txin.prev_out_index] rescue false }
297
+ return true if prev_txs.size == tx.in.size && missing.empty?
298
+
299
+ missing.each {|i| store.log.warn { "prev out #{i.prev_out.reverse_hth}:#{i.prev_out_index} missing" } }
300
+ missing.map {|i| [i.prev_out.reverse_hth, i.prev_out_index] }
301
+ end
302
+
303
+ # TODO: validate coinbase maturity
304
+
305
+ # check that all input signatures are valid
306
+ def signatures
307
+ sigs = tx.in.map.with_index {|txin, idx| tx.verify_input_signature(idx, prev_txs[idx], (@block ? @block.time : 0)) }
308
+ sigs.all? || sigs.map.with_index {|s, i| s ? nil : i }.compact
309
+ end
310
+
311
+ # check that none of the prev_outs are already spent in the main chain
312
+ def spent
313
+ spent = tx.in.map.with_index {|txin, idx|
314
+ next false if @block && @block.tx.include?(prev_txs[idx])
315
+ next false unless next_in = prev_txs[idx].out[txin.prev_out_index].get_next_in
316
+ next false unless next_tx = next_in.get_tx
317
+ next false unless next_block = next_tx.get_block
318
+ next_block.chain == Bitcoin::Storage::Backends::StoreBase::MAIN
319
+ }
320
+ spent.none? || spent.map.with_index {|s, i| s ? i : nil }
321
+ end
322
+
323
+ # check that the total input value doesn't exceed MAX_MONEY
324
+ def input_values
325
+ total_in < Bitcoin::network[:max_money] || [total_in, Bitcoin::network[:max_money]]
326
+ end
327
+
328
+ # check that the total output value doesn't exceed the total input value
329
+ def output_sum
330
+ total_in >= total_out || [total_out, total_in]
331
+ end
332
+
333
+ # empty prev txs cache
334
+ def clear_cache
335
+ @prev_txs = nil
336
+ @total_in = nil
337
+ @total_out = nil
338
+ end
339
+
340
+ # collect prev_txs needed to verify the inputs of this tx.
341
+ # only returns tx that are in a block in the main chain or the current block.
342
+ def prev_txs
343
+ @prev_txs ||= tx.in.map {|i|
344
+ prev_tx = store.get_tx(i.prev_out.reverse_hth)
345
+ next prev_tx if store.class.name =~ /UtxoStore/ && prev_tx
346
+ next nil if !prev_tx && !@block
347
+
348
+ if store.class.name =~ /SequelStore/
349
+ block = store.db[:blk][id: prev_tx.blk_id] if prev_tx
350
+ next prev_tx if block && block[:chain] == 0
351
+ else
352
+ next prev_tx if prev_tx && prev_tx.get_block && prev_tx.get_block.chain == 0
353
+ end
354
+ next nil if !@block
355
+ @block.tx.find {|t| t.binary_hash == i.prev_out }
356
+ }.compact
357
+ end
358
+
359
+
360
+ def total_in
361
+ @total_in ||= tx.in.each_with_index.inject(0){|acc,(input,idx)| acc + prev_txs[idx].out[input.prev_out_index].value }
362
+ end
363
+
364
+ def total_out
365
+ @total_out ||= tx.out.inject(0){|acc,output| acc + output.value }
366
+ end
367
+
368
+ end
369
+ end
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -1,3 +1,5 @@
1
+ # encoding: ascii-8bit
2
+
1
3
  module Bitcoin::Wallet
2
4
 
3
5
  # select unspent txouts to be used by the Wallet when creating a new transaction
@@ -14,6 +16,7 @@ module Bitcoin::Wallet
14
16
  @txouts.each do |txout|
15
17
  begin
16
18
  next if txout.get_next_in
19
+ next if Bitcoin.namecoin? && txout.type.to_s =~ /^name_/
17
20
  next unless txout.get_address
18
21
  next unless txout.get_tx.get_block
19
22
  txouts << txout
@@ -1,8 +1,10 @@
1
+ # encoding: ascii-8bit
2
+
1
3
  module Bitcoin::Wallet
2
4
 
3
5
  # Deterministic key generator as described in
4
6
  # https://bitcointalk.org/index.php?topic=11665.0.
5
- #
7
+ #
6
8
  # Takes a seed and generates an arbitrary amount of keys.
7
9
  # Protects against brute-force attacks by requiring the
8
10
  # key hash to fit a difficulty target, much like the block chain.
@@ -1,3 +1,5 @@
1
+ # encoding: ascii-8bit
2
+
1
3
  require 'json'
2
4
  require 'stringio'
3
5
 
@@ -12,6 +14,7 @@ module Bitcoin::Wallet
12
14
  # [config] Hash of settings ({:file => "/foo/bar.json"})
13
15
  def initialize config
14
16
  @config = Hash[config.map{|k,v|[k.to_sym,v]}]
17
+ @config[:file].sub!("~", ENV["HOME"]) if @config[:file].is_a?(String)
15
18
  @keys = []
16
19
  load_keys
17
20
  end
@@ -92,9 +95,10 @@ module Bitcoin::Wallet
92
95
  def import(base58, label = nil)
93
96
  raise ArgumentError, "Label #{label} already in use" if label && find_key(label)
94
97
  key = Bitcoin::Key.from_base58(base58)
98
+ raise ArgumentError, "Address #{key.addr} already in use" if label && find_key(key.addr)
95
99
  @keys << {:label => label, :addr => key.addr, :key => key}
96
100
  save_keys
97
- key.addr
101
+ key
98
102
  end
99
103
 
100
104
  # Load keys from file.
@@ -139,7 +143,7 @@ module Bitcoin::Wallet
139
143
  @config[:file].reopen
140
144
  dumper.call(@config[:file])
141
145
  @config[:file].rewind
142
- elsif File.exists?(@config[:file])
146
+ else
143
147
  File.open(@config[:file], 'w'){|file| dumper.call(file) }
144
148
  end
145
149
  end
@@ -1,3 +1,5 @@
1
+ # encoding: ascii-8bit
2
+
1
3
  class Bitcoin::Wallet::TxDP
2
4
 
3
5
  attr_accessor :id, :tx, :inputs
@@ -7,7 +9,7 @@ class Bitcoin::Wallet::TxDP
7
9
  @inputs = []
8
10
  return unless tx.any?
9
11
  @tx[0].in.each_with_index do |input, i|
10
- prev_out_hash = input.prev_out.reverse.unpack("H*")[0]
12
+ prev_out_hash = input.prev_out.reverse_hth
11
13
  prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
12
14
  raise "prev tx #{prev_out_hash} not found" unless prev_tx
13
15
  prev_out = prev_tx.out[input.prev_out_index]
@@ -27,7 +29,7 @@ class Bitcoin::Wallet::TxDP
27
29
  def sign_inputs
28
30
  @inputs.each_with_index do |txin, i|
29
31
  input = @tx[0].in[i]
30
- prev_out_hash = input.prev_out.reverse.unpack("H*")[0]
32
+ prev_out_hash = input.prev_out.reverse_hth
31
33
  prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
32
34
  raise "prev tx #{prev_out_hash} not found" unless prev_tx
33
35
  prev_out = prev_tx.out[input.prev_out_index]
@@ -47,7 +49,7 @@ class Bitcoin::Wallet::TxDP
47
49
  def serialize
48
50
  lines = []
49
51
  lines << "-----BEGIN-TRANSACTION-#{@id}".ljust(80, '-')
50
- size = [@tx.first.to_payload.bytesize].pack("C").ljust(2, "\x00").reverse.unpack("H*")[0]
52
+ size = [@tx.first.to_payload.bytesize].pack("C").ljust(2, "\x00").reverse_hth
51
53
  lines << "_TXDIST_#{Bitcoin.network[:magic_head].unpack("H*")[0]}_#{@id}_#{size}"
52
54
  tx = @tx.map(&:to_payload).join.unpack("H*")[0]
53
55
  tx_str = ""; tx.split('').each_with_index{|c,i| tx_str << (i % 80 == 0 ? "\n#{c}" : c)}
@@ -57,7 +59,7 @@ class Bitcoin::Wallet::TxDP
57
59
  next unless input[1]
58
60
  input[1].each do |sig|
59
61
  size = [sig[1]].pack("H*").bytesize
60
- size = [size].pack("C").ljust(2, "\x00").reverse.unpack("H*")[0]
62
+ size = [size].pack("C").ljust(2, "\x00").reverse_hth
61
63
  lines << "_SIG_#{sig[0]}_#{idx.to_s.rjust(2, '0')}_#{size}"
62
64
  sig_str = ""; sig[1].split('').each_with_index{|c,i| sig_str << (i % 80 == 0 ? "\n#{c}" : c)}
63
65
  lines << sig_str.strip