bitcoin-ruby 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/.travis.yml +2 -7
- data/COPYING +1 -1
- data/Gemfile +2 -6
- data/Gemfile.lock +34 -0
- data/README.rdoc +16 -68
- data/Rakefile +3 -6
- data/bin/bitcoin_shell +0 -1
- data/{concept-examples/blockchain-pow.rb → examples/concept-blockchain-pow.rb} +0 -0
- data/lib/bitcoin.rb +350 -296
- data/lib/bitcoin/builder.rb +3 -1
- data/lib/bitcoin/connection.rb +2 -1
- data/lib/bitcoin/contracthash.rb +76 -0
- data/lib/bitcoin/dogecoin.rb +97 -0
- data/lib/bitcoin/ffi/bitcoinconsensus.rb +74 -0
- data/lib/bitcoin/ffi/openssl.rb +98 -2
- data/lib/bitcoin/ffi/secp256k1.rb +144 -0
- data/lib/bitcoin/key.rb +12 -2
- data/lib/bitcoin/logger.rb +3 -12
- data/lib/bitcoin/protocol/block.rb +3 -9
- data/lib/bitcoin/protocol/parser.rb +6 -2
- data/lib/bitcoin/protocol/tx.rb +44 -13
- data/lib/bitcoin/protocol/txin.rb +4 -2
- data/lib/bitcoin/protocol/txout.rb +2 -2
- data/lib/bitcoin/script.rb +212 -37
- data/lib/bitcoin/trezor/mnemonic.rb +130 -0
- data/lib/bitcoin/version.rb +1 -1
- data/spec/bitcoin/bitcoin_spec.rb +32 -3
- data/spec/bitcoin/builder_spec.rb +18 -0
- data/spec/bitcoin/contracthash_spec.rb +45 -0
- data/spec/bitcoin/dogecoin_spec.rb +176 -0
- data/spec/bitcoin/ffi_openssl.rb +45 -0
- data/spec/bitcoin/fixtures/156e6e1b84c5c3bd3a0927b25e4119fadce6e6d5186f363317511d1d680fae9a.json +24 -0
- data/spec/bitcoin/fixtures/8d0b238a06b5a70be75d543902d02d7a514d68d3252a949a513865ac3538874c.json +24 -0
- data/spec/bitcoin/fixtures/coinbase-toshi.json +33 -0
- data/spec/bitcoin/fixtures/coinbase.json +24 -0
- data/spec/bitcoin/fixtures/dogecoin-block-60323982f9c5ff1b5a954eac9dc1269352835f47c2c5222691d80f0d50dcf053.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-01-toshi.json +46 -0
- data/spec/bitcoin/fixtures/rawtx-02-toshi.json +46 -0
- data/spec/bitcoin/fixtures/rawtx-03-toshi.json +73 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-04fdc38d6722ab4b12d79113fc4b2896bdcc5169710690ee4e78541b98e467b4.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-0b294c7d11dd21bcccb8393e6744fed7d4d1981a08c00e3e88838cc421f33c9f.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-3bc52ac063291ad92d95ddda5fd776a342083b95607ad32ed8bc6f8f7d30449e.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-6f0bbdd4e71a8af4305018d738184df32dbb6f27284fdebd5b56d16947f7c181.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-a7c9b06e275e8674cc19a5f7d3e557c72c6d93576e635b33212dbe08ab7cdb60.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-f80acbd2f594d04ddb0e1cacba662132104909157dff526935a3c88abe9201a5.bin +0 -0
- data/spec/bitcoin/protocol/block_spec.rb +0 -22
- data/spec/bitcoin/protocol/tx_spec.rb +145 -2
- data/spec/bitcoin/script/script_spec.rb +282 -0
- data/spec/bitcoin/secp256k1_spec.rb +48 -0
- data/spec/bitcoin/spec_helper.rb +0 -51
- data/spec/bitcoin/trezor/mnemonic_spec.rb +161 -0
- metadata +48 -98
- data/bin/bitcoin_dns_seed +0 -130
- data/bin/bitcoin_gui +0 -80
- data/bin/bitcoin_node +0 -153
- data/bin/bitcoin_node_cli +0 -81
- data/bin/bitcoin_wallet +0 -402
- data/doc/CONFIG.rdoc +0 -66
- data/doc/EXAMPLES.rdoc +0 -13
- data/doc/NAMECOIN.rdoc +0 -34
- data/doc/NODE.rdoc +0 -225
- data/doc/STORAGE.rdoc +0 -33
- data/doc/WALLET.rdoc +0 -102
- data/examples/balance.rb +0 -66
- data/examples/forwarder.rb +0 -73
- data/examples/index_nhash.rb +0 -24
- data/examples/reindex_p2sh_addrs.rb +0 -44
- data/examples/relay_tx.rb +0 -22
- data/examples/verify_tx.rb +0 -57
- data/lib/bitcoin/config.rb +0 -58
- data/lib/bitcoin/gui/addr_view.rb +0 -44
- data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
- data/lib/bitcoin/gui/bitcoin-ruby.svg +0 -80
- data/lib/bitcoin/gui/conn_view.rb +0 -38
- data/lib/bitcoin/gui/connection.rb +0 -70
- data/lib/bitcoin/gui/em_gtk.rb +0 -30
- data/lib/bitcoin/gui/gui.builder +0 -1643
- data/lib/bitcoin/gui/gui.rb +0 -292
- data/lib/bitcoin/gui/helpers.rb +0 -115
- data/lib/bitcoin/gui/tree_view.rb +0 -84
- data/lib/bitcoin/gui/tx_view.rb +0 -69
- data/lib/bitcoin/namecoin.rb +0 -280
- data/lib/bitcoin/network/command_client.rb +0 -104
- data/lib/bitcoin/network/command_handler.rb +0 -570
- data/lib/bitcoin/network/connection_handler.rb +0 -387
- data/lib/bitcoin/network/node.rb +0 -565
- data/lib/bitcoin/storage/dummy/dummy_store.rb +0 -179
- data/lib/bitcoin/storage/models.rb +0 -171
- data/lib/bitcoin/storage/sequel/migrations.rb +0 -99
- data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +0 -52
- data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +0 -45
- data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +0 -18
- data/lib/bitcoin/storage/sequel/migrations/004_change_txin_prev_out_to_blob.rb +0 -18
- data/lib/bitcoin/storage/sequel/migrations/005_change_tx_hash_to_bytea.rb +0 -14
- data/lib/bitcoin/storage/sequel/migrations/006_add_tx_nhash.rb +0 -31
- data/lib/bitcoin/storage/sequel/migrations/007_add_prev_out_index_index.rb +0 -16
- data/lib/bitcoin/storage/sequel/migrations/008_add_txin_p2sh_type.rb +0 -31
- data/lib/bitcoin/storage/sequel/migrations/009_add_addrs_type.rb +0 -56
- data/lib/bitcoin/storage/sequel/sequel_store.rb +0 -551
- data/lib/bitcoin/storage/storage.rb +0 -517
- data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +0 -52
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +0 -18
- data/lib/bitcoin/storage/utxo/migrations/003_update_indices.rb +0 -14
- data/lib/bitcoin/storage/utxo/migrations/004_add_addrs_type.rb +0 -14
- data/lib/bitcoin/storage/utxo/utxo_store.rb +0 -374
- data/lib/bitcoin/validation.rb +0 -400
- data/lib/bitcoin/wallet/coinselector.rb +0 -33
- data/lib/bitcoin/wallet/keygenerator.rb +0 -77
- data/lib/bitcoin/wallet/keystore.rb +0 -207
- data/lib/bitcoin/wallet/txdp.rb +0 -118
- data/lib/bitcoin/wallet/wallet.rb +0 -281
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +0 -43
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +0 -67
- data/spec/bitcoin/namecoin_spec.rb +0 -182
- data/spec/bitcoin/node/command_api_spec.rb +0 -663
- data/spec/bitcoin/storage/models_spec.rb +0 -104
- data/spec/bitcoin/storage/reorg_spec.rb +0 -236
- data/spec/bitcoin/storage/storage_spec.rb +0 -387
- data/spec/bitcoin/storage/validation_spec.rb +0 -300
- data/spec/bitcoin/wallet/coinselector_spec.rb +0 -38
- data/spec/bitcoin/wallet/keygenerator_spec.rb +0 -69
- data/spec/bitcoin/wallet/keystore_spec.rb +0 -190
- data/spec/bitcoin/wallet/txdp_spec.rb +0 -76
- data/spec/bitcoin/wallet/wallet_spec.rb +0 -238
@@ -1,207 +0,0 @@
|
|
1
|
-
# encoding: ascii-8bit
|
2
|
-
|
3
|
-
require 'json'
|
4
|
-
require 'stringio'
|
5
|
-
|
6
|
-
module Bitcoin::Wallet
|
7
|
-
|
8
|
-
# JSON-file-based keystore used by the Wallet.
|
9
|
-
class SimpleKeyStore
|
10
|
-
|
11
|
-
attr_reader :config
|
12
|
-
|
13
|
-
# Initialize keystore.
|
14
|
-
# [config] Hash of settings ({:file => "/foo/bar.json"})
|
15
|
-
def initialize config
|
16
|
-
@config = Hash[config.map{|k,v|[k.to_sym,v]}]
|
17
|
-
@config[:file].sub!("~", ENV["HOME"]) if @config[:file].is_a?(String)
|
18
|
-
@keys = []
|
19
|
-
load_keys
|
20
|
-
end
|
21
|
-
|
22
|
-
# List all stored keys.
|
23
|
-
def keys(need = nil)
|
24
|
-
@keys.select do |key|
|
25
|
-
next !(key[:hidden] && key[:hidden] == "true") unless need
|
26
|
-
case need
|
27
|
-
when :label
|
28
|
-
!!key[:label]
|
29
|
-
when :pub
|
30
|
-
!!key[:key].pub
|
31
|
-
when :priv
|
32
|
-
!!key[:key].priv
|
33
|
-
when :hidden
|
34
|
-
!!key[:hidden]
|
35
|
-
when :mine
|
36
|
-
!!key[:mine]
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# Get key for given +label+, +addr+ or +pubkey+.
|
42
|
-
def key(name)
|
43
|
-
find_key(name)
|
44
|
-
end
|
45
|
-
|
46
|
-
# Generate and store a new key.
|
47
|
-
def new_key(label = nil)
|
48
|
-
raise ArgumentError, "Label #{label} already in use" if label && find_key(label)
|
49
|
-
key = Bitcoin::Key.generate
|
50
|
-
@keys << {:label => label, :addr => key.addr, :key => key}
|
51
|
-
save_keys
|
52
|
-
key
|
53
|
-
end
|
54
|
-
|
55
|
-
# Add a key which can consist only of +addr+ and +label+.
|
56
|
-
def add_key key
|
57
|
-
label = key[:label]
|
58
|
-
raise ArgumentError, "Label #{label} already in use" if label && find_key(label)
|
59
|
-
addr = key[:addr]
|
60
|
-
raise ArgumentError, "Address #{addr} is invalid" if addr && !Bitcoin.valid_address?(addr)
|
61
|
-
@keys << key
|
62
|
-
save_keys
|
63
|
-
key
|
64
|
-
end
|
65
|
-
|
66
|
-
def label_key(name, label)
|
67
|
-
find_key(name) do |key|
|
68
|
-
key[:label] = label
|
69
|
-
end
|
70
|
-
save_keys
|
71
|
-
end
|
72
|
-
|
73
|
-
def flag_key(name, flag, value)
|
74
|
-
find_key(name, true) do |key|
|
75
|
-
key[flag.to_sym] = value
|
76
|
-
end
|
77
|
-
save_keys
|
78
|
-
end
|
79
|
-
|
80
|
-
# Delete key for given +label+, +addr+ or +pubkey+.
|
81
|
-
def delete(name)
|
82
|
-
key = find_key(name)
|
83
|
-
@keys.delete(key)
|
84
|
-
save_keys
|
85
|
-
end
|
86
|
-
|
87
|
-
# Export key for given +name+ to base58 format.
|
88
|
-
# (See Bitcoin::Key#to_base58)
|
89
|
-
def export(name)
|
90
|
-
find_key(name)[:key].to_base58 rescue nil
|
91
|
-
end
|
92
|
-
|
93
|
-
# Import key from given +base58+ string.
|
94
|
-
# (See Bitcoin::Key.from_base58)
|
95
|
-
def import(base58, label = nil)
|
96
|
-
raise ArgumentError, "Label #{label} already in use" if label && find_key(label)
|
97
|
-
key = Bitcoin::Key.from_base58(base58)
|
98
|
-
raise ArgumentError, "Address #{key.addr} already in use" if label && find_key(key.addr)
|
99
|
-
@keys << {:label => label, :addr => key.addr, :key => key}
|
100
|
-
save_keys
|
101
|
-
key
|
102
|
-
end
|
103
|
-
|
104
|
-
# Load keys from file.
|
105
|
-
# If file is empty this will generate a new key
|
106
|
-
# and store it, creating the file.
|
107
|
-
def load_keys
|
108
|
-
loader = proc{|keys|
|
109
|
-
keys.map!{|k| Hash[k.map{|k,v| [k.to_sym, v] }]}
|
110
|
-
keys.map do |key|
|
111
|
-
key[:key] = Bitcoin::Key.new(key[:priv], key[:pub])
|
112
|
-
key[:priv], key[:pub] = nil
|
113
|
-
@keys << key
|
114
|
-
end
|
115
|
-
}
|
116
|
-
if @config[:file].is_a?(StringIO)
|
117
|
-
json = JSON.load(@config[:file].read)
|
118
|
-
loader.call(json)
|
119
|
-
elsif File.exist?(@config[:file])
|
120
|
-
json = JSON.load(File.read(@config[:file]))
|
121
|
-
loader.call(json)
|
122
|
-
else
|
123
|
-
new_key; save_keys
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
# Save keys to file.
|
128
|
-
def save_keys
|
129
|
-
dumper = proc{|file|
|
130
|
-
keys = @keys.map do |key|
|
131
|
-
key = key.dup
|
132
|
-
if key[:key]
|
133
|
-
key[:priv] = key[:key].priv
|
134
|
-
key[:pub] = key[:key].pub
|
135
|
-
key.delete(:key)
|
136
|
-
end
|
137
|
-
key
|
138
|
-
end
|
139
|
-
file.write(JSON.pretty_generate(keys))
|
140
|
-
}
|
141
|
-
|
142
|
-
if @config[:file].is_a?(StringIO)
|
143
|
-
@config[:file].reopen
|
144
|
-
dumper.call(@config[:file])
|
145
|
-
@config[:file].rewind
|
146
|
-
else
|
147
|
-
File.open(@config[:file], 'w'){|file| dumper.call(file) }
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
private
|
152
|
-
|
153
|
-
def find_key(name, hidden = false)
|
154
|
-
key = if Bitcoin.valid_address?(name)
|
155
|
-
@keys.find{|k| k[:addr] == name }
|
156
|
-
elsif name.size == 130
|
157
|
-
@keys.find{|k| k[:key].pub == name }
|
158
|
-
else
|
159
|
-
@keys.find{|k| k[:label] == name }
|
160
|
-
end
|
161
|
-
return nil if !key || (!hidden && key[:hidden] == "true")
|
162
|
-
block_given? ? yield(key) : key
|
163
|
-
end
|
164
|
-
|
165
|
-
end
|
166
|
-
|
167
|
-
# Deterministic keystore.
|
168
|
-
class DeterministicKeyStore
|
169
|
-
|
170
|
-
attr_reader :generator
|
171
|
-
|
172
|
-
# Initialize keystore.
|
173
|
-
# [config] Hash of settings ({:keys => 1, :seed => ..., :nonce => ...})
|
174
|
-
def initialize config
|
175
|
-
@config = Hash[config.map{|k,v|[k.to_sym,v]}]
|
176
|
-
@config[:keys] = (@config[:keys] || 1).to_i
|
177
|
-
@generator = Bitcoin::Wallet::KeyGenerator.new(@config[:seed], @config[:nonce])
|
178
|
-
end
|
179
|
-
|
180
|
-
# List all keys upto configured limit.
|
181
|
-
def keys
|
182
|
-
1.upto(@config[:keys].to_i).map {|i| @generator.get_key(i) }
|
183
|
-
end
|
184
|
-
|
185
|
-
# Get key for given +addr+.
|
186
|
-
def key(addr)
|
187
|
-
1.upto(@config[:keys].to_i).map do |i|
|
188
|
-
key = @generator.get_key(i)
|
189
|
-
return key if key.addr == addr
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
# Get new key (actually just increase the key limit).
|
194
|
-
def new_key
|
195
|
-
@config[:keys] += 1
|
196
|
-
@generator.get_key(@config[:keys])
|
197
|
-
end
|
198
|
-
|
199
|
-
# Export key for given +addr+ to base58.
|
200
|
-
# (See Bitcoin::Key.to_base58)
|
201
|
-
def export(addr)
|
202
|
-
key(addr).to_base58 rescue nil
|
203
|
-
end
|
204
|
-
|
205
|
-
end
|
206
|
-
|
207
|
-
end
|
data/lib/bitcoin/wallet/txdp.rb
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
# encoding: ascii-8bit
|
2
|
-
|
3
|
-
class Bitcoin::Wallet::TxDP
|
4
|
-
|
5
|
-
attr_accessor :id, :tx, :inputs
|
6
|
-
def initialize tx = []
|
7
|
-
@id = Bitcoin.int_to_base58(rand(1e14))
|
8
|
-
@tx = tx
|
9
|
-
@inputs = []
|
10
|
-
return unless tx.any?
|
11
|
-
@tx[0].in.each_with_index do |input, i|
|
12
|
-
prev_out_hash = input.prev_out.reverse_hth
|
13
|
-
prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
|
14
|
-
raise "prev tx #{prev_out_hash} not found" unless prev_tx
|
15
|
-
prev_out = prev_tx.out[input.prev_out_index]
|
16
|
-
raise "prev out ##{input.prev_out_index} not found in tx #{@tx.hash}" unless prev_out
|
17
|
-
out_script = Bitcoin::Script.new(prev_out.pk_script)
|
18
|
-
out_script.get_addresses.each do |addr|
|
19
|
-
add_sig(i, prev_out.value, addr, input.script_sig)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def add_sig(in_idx, value, addr, sig)
|
25
|
-
sig = sig ? [[addr, sig.unpack("H*")[0]]] : []
|
26
|
-
@inputs[in_idx] = [value, sig]
|
27
|
-
end
|
28
|
-
|
29
|
-
def sign_inputs
|
30
|
-
@inputs.each_with_index do |txin, i|
|
31
|
-
input = @tx[0].in[i]
|
32
|
-
prev_out_hash = input.prev_out.reverse_hth
|
33
|
-
prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
|
34
|
-
raise "prev tx #{prev_out_hash} not found" unless prev_tx
|
35
|
-
prev_out = prev_tx.out[input.prev_out_index]
|
36
|
-
raise "prev out ##{input.prev_out_index} not found in tx #{@tx.hash}" unless prev_out
|
37
|
-
out_script = Bitcoin::Script.new(prev_out.pk_script)
|
38
|
-
out_script.get_addresses.each do |addr|
|
39
|
-
sig = yield(@tx[0], prev_tx, i, addr)
|
40
|
-
if sig
|
41
|
-
@inputs[i][1] ||= []
|
42
|
-
@inputs[i][1] << [addr, sig]
|
43
|
-
break
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def serialize
|
50
|
-
lines = []
|
51
|
-
lines << "-----BEGIN-TRANSACTION-#{@id}".ljust(80, '-')
|
52
|
-
size = [@tx.first.to_payload.bytesize].pack("C").ljust(2, "\x00").reverse_hth
|
53
|
-
lines << "_TXDIST_#{Bitcoin.network[:magic_head].unpack("H*")[0]}_#{@id}_#{size}"
|
54
|
-
tx = @tx.map(&:to_payload).join.unpack("H*")[0]
|
55
|
-
tx_str = ""; tx.split('').each_with_index{|c,i| tx_str << (i % 80 == 0 ? "\n#{c}" : c)}
|
56
|
-
lines << tx_str.strip
|
57
|
-
@inputs.each_with_index do |input, idx|
|
58
|
-
lines << "_TXINPUT_#{idx.to_s.rjust(2, '0')}_#{"%.8f" % (input[0].to_f / 1e8)}"
|
59
|
-
next unless input[1]
|
60
|
-
input[1].each do |sig|
|
61
|
-
size = [sig[1]].pack("H*").bytesize
|
62
|
-
size = [size].pack("C").ljust(2, "\x00").reverse_hth
|
63
|
-
lines << "_SIG_#{sig[0]}_#{idx.to_s.rjust(2, '0')}_#{size}"
|
64
|
-
sig_str = ""; sig[1].split('').each_with_index{|c,i| sig_str << (i % 80 == 0 ? "\n#{c}" : c)}
|
65
|
-
lines << sig_str.strip
|
66
|
-
end
|
67
|
-
end
|
68
|
-
lines << "-------END-TRANSACTION-#{@id}".ljust(80, '-')
|
69
|
-
lines.join("\n")
|
70
|
-
end
|
71
|
-
|
72
|
-
def parse str
|
73
|
-
str.match(/-+BEGIN-TRANSACTION-(.*?)-+$(.*?)END-TRANSACTION-#{$1}/m) do |m|
|
74
|
-
_, id, content = *m
|
75
|
-
txdist, *inputs = content.split(/_TXINPUT_/)
|
76
|
-
@id = id
|
77
|
-
@txdist = parse_txdist(txdist)
|
78
|
-
inputs.each {|input| parse_input(input) }
|
79
|
-
end
|
80
|
-
self
|
81
|
-
end
|
82
|
-
|
83
|
-
def parse_txdist txdist
|
84
|
-
_, magic, txdp_id, size, serialized_tx = *txdist.match(/_TXDIST_(.*?)_(.*?)_(.*?)$(.*)/m)
|
85
|
-
raise "Wrong network magic" unless [magic].pack("H*") == Bitcoin.network[:magic_head]
|
86
|
-
tx = Bitcoin::P::Tx.new(nil)
|
87
|
-
rest = [serialized_tx.gsub!("\n", '')].pack("H*")
|
88
|
-
while rest = tx.parse_data(rest)
|
89
|
-
@tx << tx
|
90
|
-
break if rest == true
|
91
|
-
tx = Bitcoin::P::Tx.new(nil)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def parse_input input
|
96
|
-
m = input.match(/(\d+)_(\d+\.\d+)\n(.*)/m)
|
97
|
-
_, idx, value, sigs = *m
|
98
|
-
value = (value.sub('.','').to_i)
|
99
|
-
sigs = parse_sigs(sigs)
|
100
|
-
@inputs[idx.to_i] = [value, sigs]
|
101
|
-
end
|
102
|
-
|
103
|
-
def parse_sigs sigs
|
104
|
-
return nil unless sigs["_SIG_"]
|
105
|
-
sigs = sigs.split("_SIG_").map do |s|
|
106
|
-
if s == ""
|
107
|
-
nil
|
108
|
-
else
|
109
|
-
m = s.match(/(.*?)_(\d+)_(.*?)\n(.*)/m)
|
110
|
-
[$1, $4.gsub("\n", '').gsub('-', '')]
|
111
|
-
end
|
112
|
-
end.compact
|
113
|
-
end
|
114
|
-
|
115
|
-
def self.parse str
|
116
|
-
new.parse str
|
117
|
-
end
|
118
|
-
end
|
@@ -1,281 +0,0 @@
|
|
1
|
-
# encoding: ascii-8bit
|
2
|
-
|
3
|
-
Bitcoin.require_dependency :eventmachine, exit: false
|
4
|
-
|
5
|
-
# The wallet implementation consists of several concepts:
|
6
|
-
# Wallet:: the high-level API used to manage a wallet
|
7
|
-
# SimpleKeyStore:: key store to manage keys/addresses/labels
|
8
|
-
# SimpleCoinSelector:: coin selector to find unspent outputs to use when creating tx
|
9
|
-
module Bitcoin::Wallet
|
10
|
-
|
11
|
-
# A wallet manages a set of keys (through a +keystore+), can
|
12
|
-
# list transactions/balances for those keys (using a Storage backend for
|
13
|
-
# blockchain data).
|
14
|
-
# It can also create transactions with various kinds of outputs and
|
15
|
-
# connect with a CommandClient to relay those transactions through a node.
|
16
|
-
#
|
17
|
-
# TODO: new tx notification, keygenerators, keystore cleanup
|
18
|
-
class Wallet
|
19
|
-
|
20
|
-
include Bitcoin
|
21
|
-
include Builder
|
22
|
-
|
23
|
-
# the keystore (SimpleKeyStore) managing keys/addresses/labels
|
24
|
-
attr_reader :keystore
|
25
|
-
|
26
|
-
# the Storage which holds the blockchain
|
27
|
-
attr_reader :storage
|
28
|
-
|
29
|
-
# open wallet with given +storage+ Storage backend, +keystore+ SimpleKeyStore
|
30
|
-
# and +selector+ SimpleCoinSelector
|
31
|
-
def initialize storage, keystore, selector = SimpleCoinSelector
|
32
|
-
@storage = storage
|
33
|
-
@keystore = keystore
|
34
|
-
@selector = selector
|
35
|
-
@callbacks = {}
|
36
|
-
|
37
|
-
@keystore.keys.each {|key| @storage.add_watched_address(key[:addr]) }
|
38
|
-
# connect_node if defined?(EM)
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
def connect_node
|
43
|
-
return unless EM.reactor_running?
|
44
|
-
host, port = "127.0.0.1", 9999
|
45
|
-
@node = Network::CommandClient.connect(host, port, self, @storage) do
|
46
|
-
on_connected { request :monitor, "block", "tx" }
|
47
|
-
on_block do |block, depth|
|
48
|
-
EM.defer do
|
49
|
-
block['tx'].each do |tx|
|
50
|
-
relevant, tx = @args[0].check_tx(tx['hash'])
|
51
|
-
@args[0].callback(:tx, :confirmed, tx) if relevant
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
on_tx do |response|
|
57
|
-
EM.defer do
|
58
|
-
relevant, tx = @args[0].check_tx(response['hash'])
|
59
|
-
@args[0].callback(:tx, relevant, tx) if relevant
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def check_tx tx_hash
|
66
|
-
relevant = false
|
67
|
-
addrs = addrs
|
68
|
-
tx = @storage.get_tx(tx_hash)
|
69
|
-
unless tx
|
70
|
-
log.warn { "Received tx #{response['hash']} but not found in storage" }
|
71
|
-
binding.pry
|
72
|
-
return false
|
73
|
-
end
|
74
|
-
addrs = @keystore.keys.map {|k| k[:addr] }
|
75
|
-
tx.out.each do |txout|
|
76
|
-
return :incoming, tx if (txout.get_addresses & addrs).any?
|
77
|
-
end
|
78
|
-
tx.in.each do |txin|
|
79
|
-
next unless prev_out = txin.get_prev_out
|
80
|
-
return :outgoing, tx if (prev_out.get_addresses & addrs).any?
|
81
|
-
end
|
82
|
-
return false
|
83
|
-
end
|
84
|
-
|
85
|
-
def log
|
86
|
-
return @log if @log
|
87
|
-
@log = Logger.create("wallet")
|
88
|
-
@log.level = :debug
|
89
|
-
@log
|
90
|
-
end
|
91
|
-
|
92
|
-
# call the callback specified by +name+ passing in +args+
|
93
|
-
def callback name, *args
|
94
|
-
cb = @callbacks[name.to_sym]
|
95
|
-
return unless cb
|
96
|
-
log.debug { "callback: #{name}" }
|
97
|
-
cb.call(*args)
|
98
|
-
end
|
99
|
-
|
100
|
-
# register callback methods
|
101
|
-
def method_missing(name, *args, &block)
|
102
|
-
if name =~ /^on_/
|
103
|
-
@callbacks[name.to_s.split("on_")[1].to_sym] = block
|
104
|
-
log.debug { "callback #{name} registered" }
|
105
|
-
else
|
106
|
-
super(name, *args)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
# get all Storage::Models::TxOut concerning any address from this wallet
|
111
|
-
def get_txouts(unconfirmed = false)
|
112
|
-
txouts = @keystore.keys.map {|k|
|
113
|
-
@storage.get_txouts_for_address(k[:addr])}.flatten.uniq
|
114
|
-
(unconfirmed || @storage.class.name =~ /Utxo/) ? txouts : txouts.select {|o| !!o.get_tx.get_block}
|
115
|
-
end
|
116
|
-
|
117
|
-
# get total balance for all addresses in this wallet
|
118
|
-
def get_balance(unconfirmed = false)
|
119
|
-
values = get_txouts(unconfirmed).select{|o| !o.get_next_in}.map(&:value)
|
120
|
-
([0] + values).inject(:+)
|
121
|
-
end
|
122
|
-
|
123
|
-
# list all addresses in this wallet
|
124
|
-
def addrs
|
125
|
-
@keystore.keys.map{|k| k[:addr]}
|
126
|
-
end
|
127
|
-
|
128
|
-
# add +key+ to wallet
|
129
|
-
def add_key key
|
130
|
-
@keystore.add_key(key)
|
131
|
-
@storage.add_watched_address(key[:addr])
|
132
|
-
end
|
133
|
-
|
134
|
-
# set label for key +old+ to +new+
|
135
|
-
def label old, new
|
136
|
-
@keystore.label_key(old, new)
|
137
|
-
end
|
138
|
-
|
139
|
-
# set +flag+ for key +name+ to +value+
|
140
|
-
def flag name, flag, value
|
141
|
-
@keystore.flag_key(name, flag, value)
|
142
|
-
end
|
143
|
-
|
144
|
-
# list all keys along with their balances
|
145
|
-
def list
|
146
|
-
@keystore.keys.map do |key|
|
147
|
-
[key, @storage.get_balance(Bitcoin.hash160_from_address(key[:addr]))]
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
# create new key and return its address
|
152
|
-
def get_new_addr
|
153
|
-
key = @keystore.new_key
|
154
|
-
@storage.add_watched_address(key.addr)
|
155
|
-
key.addr
|
156
|
-
end
|
157
|
-
|
158
|
-
def import_key base58, label = nil
|
159
|
-
key = @keystore.import(base58, label)
|
160
|
-
@storage.add_watched_address(key.addr)
|
161
|
-
key.addr
|
162
|
-
end
|
163
|
-
|
164
|
-
def rescan
|
165
|
-
@storage.rescan
|
166
|
-
end
|
167
|
-
|
168
|
-
# get SimpleCoinSelector with txouts for this wallet
|
169
|
-
def get_selector
|
170
|
-
@selector.new(get_txouts)
|
171
|
-
end
|
172
|
-
|
173
|
-
# create a transaction with given +outputs+, +fee+ and +change_policy+.
|
174
|
-
#
|
175
|
-
# outputs are of the form
|
176
|
-
# [<type>, <recipients>, <value>]
|
177
|
-
# examples:
|
178
|
-
# [:address, <addr>, <value>]
|
179
|
-
# [:multisig, 2, 3, <addr>, <addr>, <addr>, <value>]
|
180
|
-
#
|
181
|
-
# inputs are selected automatically by the SimpleCoinSelector.
|
182
|
-
#
|
183
|
-
# change_policy controls where the change_output is spent to.
|
184
|
-
# see #get_change_addr
|
185
|
-
def new_tx outputs, fee = 0, change_policy = :back
|
186
|
-
output_value = outputs.map{|o| o[-1] }.inject(:+)
|
187
|
-
|
188
|
-
prev_outs = get_selector.select(output_value)
|
189
|
-
raise "Insufficient funds." if !prev_outs
|
190
|
-
if Bitcoin.namecoin?
|
191
|
-
prev_out = nil
|
192
|
-
outputs.each do |out|
|
193
|
-
if out[0] == :name_firstupdate
|
194
|
-
name_hash = Bitcoin.hash160(out[2] + out[1].hth)
|
195
|
-
break if prev_out = get_txouts.find {|o|
|
196
|
-
o.type == :name_new && o.script.get_namecoin_hash == name_hash }
|
197
|
-
elsif out[0] == :name_update
|
198
|
-
break if prev_out = storage.name_show(out[1]).get_txout rescue nil
|
199
|
-
end
|
200
|
-
end
|
201
|
-
if outputs.find{|o| [:name_firstupdate, :name_update].include?(o[0]) }
|
202
|
-
raise "previous name tx not found in wallet." unless prev_out
|
203
|
-
prev_outs += [prev_out]
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
input_value = prev_outs.map(&:value).inject(:+)
|
208
|
-
raise "Insufficient funds." unless input_value >= (output_value + fee)
|
209
|
-
|
210
|
-
tx = build_tx do |t|
|
211
|
-
t.version 0x7100 if Bitcoin.namecoin? && outputs.find {|o| o[0].to_s =~ /^name_/ }
|
212
|
-
outputs.each do |type, *addrs, value|
|
213
|
-
t.output do |o|
|
214
|
-
o.value value
|
215
|
-
o.script do |s|
|
216
|
-
s.type type
|
217
|
-
s.recipient *addrs
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
change_value = input_value - output_value - fee
|
223
|
-
if change_value > 0
|
224
|
-
change_addr = get_change_addr(change_policy, prev_outs.sample.get_address)
|
225
|
-
t.output do |o|
|
226
|
-
o.value change_value
|
227
|
-
o.script do |s|
|
228
|
-
s.type :address
|
229
|
-
s.recipient change_addr
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
prev_outs.each_with_index do |prev_out, idx|
|
235
|
-
t.input do |i|
|
236
|
-
prev_tx = prev_out.get_tx
|
237
|
-
i.prev_out prev_tx
|
238
|
-
i.prev_out_index prev_tx.out.index(prev_out)
|
239
|
-
pk_script = Script.new(prev_out.pk_script)
|
240
|
-
if pk_script.is_pubkey? || pk_script.is_hash160? || pk_script.is_namecoin?
|
241
|
-
i.signature_key @keystore.key(prev_out.get_address)[:key]
|
242
|
-
elsif pk_script.is_multisig?
|
243
|
-
raise "multisig not implemented"
|
244
|
-
end
|
245
|
-
end
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
# TODO: spend multisig outputs again
|
250
|
-
# TODO: verify signatures
|
251
|
-
raise "Payload Error" unless P::Tx.new(tx.to_payload).to_payload == tx.to_payload
|
252
|
-
|
253
|
-
tx
|
254
|
-
end
|
255
|
-
|
256
|
-
protected
|
257
|
-
|
258
|
-
# get address to send change output to.
|
259
|
-
# +policy+ controls which address is chosen:
|
260
|
-
# first:: send to the first key in the wallets keystore
|
261
|
-
# random:: send to a random key from the wallets keystore
|
262
|
-
# new:: send to a new key generated in the wallets keystore
|
263
|
-
# back:: send to the address given as +in_addr+
|
264
|
-
def get_change_addr(policy, in_addr)
|
265
|
-
case policy
|
266
|
-
when :first
|
267
|
-
@keystore.keys[0].addr
|
268
|
-
when :random
|
269
|
-
@keystore.keys.sample.addr
|
270
|
-
when :new
|
271
|
-
@keystore.new_key.addr
|
272
|
-
when :back
|
273
|
-
in_addr
|
274
|
-
else
|
275
|
-
policy
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
end
|
280
|
-
|
281
|
-
end
|