bitcoin-ruby 0.0.6 → 0.0.7
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.
- 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
|