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