bitcoin-ruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +12 -0
- data/COPYING +18 -0
- data/Gemfile +4 -0
- data/README.rdoc +189 -0
- data/Rakefile +104 -0
- data/bin/bitcoin_dns_seed +130 -0
- data/bin/bitcoin_gui +80 -0
- data/bin/bitcoin_node +174 -0
- data/bin/bitcoin_shell +12 -0
- data/bin/bitcoin_wallet +323 -0
- data/bitcoin-ruby.gemspec +27 -0
- data/concept-examples/blockchain-pow.rb +151 -0
- data/doc/CONFIG.rdoc +66 -0
- data/doc/EXAMPLES.rdoc +9 -0
- data/doc/NODE.rdoc +35 -0
- data/doc/STORAGE.rdoc +21 -0
- data/doc/WALLET.rdoc +102 -0
- data/examples/balance.rb +60 -0
- data/examples/bbe_verify_tx.rb +55 -0
- data/examples/connect.rb +36 -0
- data/examples/relay_tx.rb +22 -0
- data/examples/verify_tx.rb +57 -0
- data/lib/bitcoin.rb +370 -0
- data/lib/bitcoin/builder.rb +266 -0
- data/lib/bitcoin/config.rb +56 -0
- data/lib/bitcoin/connection.rb +126 -0
- data/lib/bitcoin/ffi/openssl.rb +121 -0
- data/lib/bitcoin/gui/addr_view.rb +42 -0
- data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
- data/lib/bitcoin/gui/bitcoin-ruby.svg +80 -0
- data/lib/bitcoin/gui/conn_view.rb +36 -0
- data/lib/bitcoin/gui/connection.rb +68 -0
- data/lib/bitcoin/gui/em_gtk.rb +28 -0
- data/lib/bitcoin/gui/gui.builder +1643 -0
- data/lib/bitcoin/gui/gui.rb +290 -0
- data/lib/bitcoin/gui/helpers.rb +113 -0
- data/lib/bitcoin/gui/tree_view.rb +82 -0
- data/lib/bitcoin/gui/tx_view.rb +67 -0
- data/lib/bitcoin/key.rb +125 -0
- data/lib/bitcoin/logger.rb +65 -0
- data/lib/bitcoin/network/command_client.rb +93 -0
- data/lib/bitcoin/network/command_handler.rb +179 -0
- data/lib/bitcoin/network/connection_handler.rb +274 -0
- data/lib/bitcoin/network/node.rb +399 -0
- data/lib/bitcoin/protocol.rb +140 -0
- data/lib/bitcoin/protocol/address.rb +48 -0
- data/lib/bitcoin/protocol/alert.rb +47 -0
- data/lib/bitcoin/protocol/block.rb +154 -0
- data/lib/bitcoin/protocol/handler.rb +38 -0
- data/lib/bitcoin/protocol/parser.rb +148 -0
- data/lib/bitcoin/protocol/tx.rb +205 -0
- data/lib/bitcoin/protocol/txin.rb +97 -0
- data/lib/bitcoin/protocol/txout.rb +73 -0
- data/lib/bitcoin/protocol/version.rb +70 -0
- data/lib/bitcoin/script.rb +634 -0
- data/lib/bitcoin/storage/dummy.rb +164 -0
- data/lib/bitcoin/storage/models.rb +133 -0
- data/lib/bitcoin/storage/sequel.rb +335 -0
- data/lib/bitcoin/storage/sequel_store/sequel_migrations.rb +84 -0
- data/lib/bitcoin/storage/storage.rb +243 -0
- data/lib/bitcoin/version.rb +3 -0
- data/lib/bitcoin/wallet/coinselector.rb +30 -0
- data/lib/bitcoin/wallet/keygenerator.rb +75 -0
- data/lib/bitcoin/wallet/keystore.rb +203 -0
- data/lib/bitcoin/wallet/txdp.rb +116 -0
- data/lib/bitcoin/wallet/wallet.rb +243 -0
- data/spec/bitcoin/bitcoin_spec.rb +472 -0
- data/spec/bitcoin/builder_spec.rb +90 -0
- data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +27 -0
- data/spec/bitcoin/fixtures/23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63.json +23 -0
- data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +27 -0
- data/spec/bitcoin/fixtures/60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1.json +45 -0
- data/spec/bitcoin/fixtures/bc179baab547b7d7c1d5d8d6f8b0cc6318eaa4b0dd0a093ad6ac7f5a1cb6b3ba.json +34 -0
- data/spec/bitcoin/fixtures/rawblock-0.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-0.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-1.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-1.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-131025.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-131025.json +5063 -0
- data/spec/bitcoin/fixtures/rawblock-170.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-170.json +68 -0
- data/spec/bitcoin/fixtures/rawblock-9.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-9.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-26478.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-26478.json +64 -0
- data/spec/bitcoin/fixtures/rawtx-01.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-01.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-02.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-02.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-03.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-03.json +48 -0
- data/spec/bitcoin/fixtures/rawtx-04.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-05.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-14be6fff8c6014f7c9493b4a6e4a741699173f39d74431b6b844fcb41ebb9984.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-406b2b06bcd34d3c8733e6b79f7a394c8a431fbf4ff5ac705c93f4076bb77602.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-52250a162c7d03d2e1fbc5ebd1801a88612463314b55102171c5b5d817d2d7b2.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-b5d4e8883533f99e5903ea2cf001a133a322fa6b1370b18a16c57c946a40823d.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-ba1ff5cd66713133c062a871a8adab92416f1e38d17786b2bf56ac5f6ffdfdf5.json +37 -0
- data/spec/bitcoin/fixtures/rawtx-c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73.json +24 -0
- data/spec/bitcoin/fixtures/rawtx-de35d060663750b3975b7997bde7fb76307cec5b270d12fcd9c4ad98b279c28c.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-a220adf1902c46a39db25a24bc4178b6a88440f977a7e2cabfdd8b5c1dd35cfb.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.json +41 -0
- data/spec/bitcoin/fixtures/reorg/blk_0_to_4.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_3A.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_4A.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_5A.dat +0 -0
- data/spec/bitcoin/fixtures/testnet/block_0.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_1.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_2.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_3.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_4.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_5.bin +0 -0
- data/spec/bitcoin/fixtures/txdp-1.txt +32 -0
- data/spec/bitcoin/fixtures/txdp-2-signed.txt +19 -0
- data/spec/bitcoin/fixtures/txdp-2-unsigned.txt +14 -0
- data/spec/bitcoin/key_spec.rb +123 -0
- data/spec/bitcoin/network_spec.rb +48 -0
- data/spec/bitcoin/protocol/addr_spec.rb +68 -0
- data/spec/bitcoin/protocol/alert_spec.rb +20 -0
- data/spec/bitcoin/protocol/block_spec.rb +101 -0
- data/spec/bitcoin/protocol/inv_spec.rb +124 -0
- data/spec/bitcoin/protocol/ping_spec.rb +49 -0
- data/spec/bitcoin/protocol/tx_spec.rb +226 -0
- data/spec/bitcoin/protocol/version_spec.rb +77 -0
- data/spec/bitcoin/reorg_spec.rb +129 -0
- data/spec/bitcoin/script/opcodes_spec.rb +417 -0
- data/spec/bitcoin/script/script_spec.rb +246 -0
- data/spec/bitcoin/spec_helper.rb +36 -0
- data/spec/bitcoin/storage_spec.rb +229 -0
- data/spec/bitcoin/wallet/coinselector_spec.rb +35 -0
- data/spec/bitcoin/wallet/keygenerator_spec.rb +64 -0
- data/spec/bitcoin/wallet/keystore_spec.rb +188 -0
- data/spec/bitcoin/wallet/txdp_spec.rb +74 -0
- data/spec/bitcoin/wallet/wallet_spec.rb +207 -0
- 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,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
|