bitcoin-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. data/.gitignore +12 -0
  2. data/COPYING +18 -0
  3. data/Gemfile +4 -0
  4. data/README.rdoc +189 -0
  5. data/Rakefile +104 -0
  6. data/bin/bitcoin_dns_seed +130 -0
  7. data/bin/bitcoin_gui +80 -0
  8. data/bin/bitcoin_node +174 -0
  9. data/bin/bitcoin_shell +12 -0
  10. data/bin/bitcoin_wallet +323 -0
  11. data/bitcoin-ruby.gemspec +27 -0
  12. data/concept-examples/blockchain-pow.rb +151 -0
  13. data/doc/CONFIG.rdoc +66 -0
  14. data/doc/EXAMPLES.rdoc +9 -0
  15. data/doc/NODE.rdoc +35 -0
  16. data/doc/STORAGE.rdoc +21 -0
  17. data/doc/WALLET.rdoc +102 -0
  18. data/examples/balance.rb +60 -0
  19. data/examples/bbe_verify_tx.rb +55 -0
  20. data/examples/connect.rb +36 -0
  21. data/examples/relay_tx.rb +22 -0
  22. data/examples/verify_tx.rb +57 -0
  23. data/lib/bitcoin.rb +370 -0
  24. data/lib/bitcoin/builder.rb +266 -0
  25. data/lib/bitcoin/config.rb +56 -0
  26. data/lib/bitcoin/connection.rb +126 -0
  27. data/lib/bitcoin/ffi/openssl.rb +121 -0
  28. data/lib/bitcoin/gui/addr_view.rb +42 -0
  29. data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
  30. data/lib/bitcoin/gui/bitcoin-ruby.svg +80 -0
  31. data/lib/bitcoin/gui/conn_view.rb +36 -0
  32. data/lib/bitcoin/gui/connection.rb +68 -0
  33. data/lib/bitcoin/gui/em_gtk.rb +28 -0
  34. data/lib/bitcoin/gui/gui.builder +1643 -0
  35. data/lib/bitcoin/gui/gui.rb +290 -0
  36. data/lib/bitcoin/gui/helpers.rb +113 -0
  37. data/lib/bitcoin/gui/tree_view.rb +82 -0
  38. data/lib/bitcoin/gui/tx_view.rb +67 -0
  39. data/lib/bitcoin/key.rb +125 -0
  40. data/lib/bitcoin/logger.rb +65 -0
  41. data/lib/bitcoin/network/command_client.rb +93 -0
  42. data/lib/bitcoin/network/command_handler.rb +179 -0
  43. data/lib/bitcoin/network/connection_handler.rb +274 -0
  44. data/lib/bitcoin/network/node.rb +399 -0
  45. data/lib/bitcoin/protocol.rb +140 -0
  46. data/lib/bitcoin/protocol/address.rb +48 -0
  47. data/lib/bitcoin/protocol/alert.rb +47 -0
  48. data/lib/bitcoin/protocol/block.rb +154 -0
  49. data/lib/bitcoin/protocol/handler.rb +38 -0
  50. data/lib/bitcoin/protocol/parser.rb +148 -0
  51. data/lib/bitcoin/protocol/tx.rb +205 -0
  52. data/lib/bitcoin/protocol/txin.rb +97 -0
  53. data/lib/bitcoin/protocol/txout.rb +73 -0
  54. data/lib/bitcoin/protocol/version.rb +70 -0
  55. data/lib/bitcoin/script.rb +634 -0
  56. data/lib/bitcoin/storage/dummy.rb +164 -0
  57. data/lib/bitcoin/storage/models.rb +133 -0
  58. data/lib/bitcoin/storage/sequel.rb +335 -0
  59. data/lib/bitcoin/storage/sequel_store/sequel_migrations.rb +84 -0
  60. data/lib/bitcoin/storage/storage.rb +243 -0
  61. data/lib/bitcoin/version.rb +3 -0
  62. data/lib/bitcoin/wallet/coinselector.rb +30 -0
  63. data/lib/bitcoin/wallet/keygenerator.rb +75 -0
  64. data/lib/bitcoin/wallet/keystore.rb +203 -0
  65. data/lib/bitcoin/wallet/txdp.rb +116 -0
  66. data/lib/bitcoin/wallet/wallet.rb +243 -0
  67. data/spec/bitcoin/bitcoin_spec.rb +472 -0
  68. data/spec/bitcoin/builder_spec.rb +90 -0
  69. data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +27 -0
  70. data/spec/bitcoin/fixtures/23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63.json +23 -0
  71. data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +27 -0
  72. data/spec/bitcoin/fixtures/60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1.json +45 -0
  73. data/spec/bitcoin/fixtures/bc179baab547b7d7c1d5d8d6f8b0cc6318eaa4b0dd0a093ad6ac7f5a1cb6b3ba.json +34 -0
  74. data/spec/bitcoin/fixtures/rawblock-0.bin +0 -0
  75. data/spec/bitcoin/fixtures/rawblock-0.json +39 -0
  76. data/spec/bitcoin/fixtures/rawblock-1.bin +0 -0
  77. data/spec/bitcoin/fixtures/rawblock-1.json +39 -0
  78. data/spec/bitcoin/fixtures/rawblock-131025.bin +0 -0
  79. data/spec/bitcoin/fixtures/rawblock-131025.json +5063 -0
  80. data/spec/bitcoin/fixtures/rawblock-170.bin +0 -0
  81. data/spec/bitcoin/fixtures/rawblock-170.json +68 -0
  82. data/spec/bitcoin/fixtures/rawblock-9.bin +0 -0
  83. data/spec/bitcoin/fixtures/rawblock-9.json +39 -0
  84. data/spec/bitcoin/fixtures/rawblock-testnet-26478.bin +0 -0
  85. data/spec/bitcoin/fixtures/rawblock-testnet-26478.json +64 -0
  86. data/spec/bitcoin/fixtures/rawtx-01.bin +0 -0
  87. data/spec/bitcoin/fixtures/rawtx-01.json +27 -0
  88. data/spec/bitcoin/fixtures/rawtx-02.bin +0 -0
  89. data/spec/bitcoin/fixtures/rawtx-02.json +27 -0
  90. data/spec/bitcoin/fixtures/rawtx-03.bin +0 -0
  91. data/spec/bitcoin/fixtures/rawtx-03.json +48 -0
  92. data/spec/bitcoin/fixtures/rawtx-04.json +27 -0
  93. data/spec/bitcoin/fixtures/rawtx-0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9.bin +0 -0
  94. data/spec/bitcoin/fixtures/rawtx-05.json +23 -0
  95. data/spec/bitcoin/fixtures/rawtx-14be6fff8c6014f7c9493b4a6e4a741699173f39d74431b6b844fcb41ebb9984.bin +0 -0
  96. data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.bin +0 -0
  97. data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.json +27 -0
  98. data/spec/bitcoin/fixtures/rawtx-406b2b06bcd34d3c8733e6b79f7a394c8a431fbf4ff5ac705c93f4076bb77602.json +23 -0
  99. data/spec/bitcoin/fixtures/rawtx-52250a162c7d03d2e1fbc5ebd1801a88612463314b55102171c5b5d817d2d7b2.bin +0 -0
  100. data/spec/bitcoin/fixtures/rawtx-b5d4e8883533f99e5903ea2cf001a133a322fa6b1370b18a16c57c946a40823d.bin +0 -0
  101. data/spec/bitcoin/fixtures/rawtx-ba1ff5cd66713133c062a871a8adab92416f1e38d17786b2bf56ac5f6ffdfdf5.json +37 -0
  102. data/spec/bitcoin/fixtures/rawtx-c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73.json +24 -0
  103. data/spec/bitcoin/fixtures/rawtx-de35d060663750b3975b7997bde7fb76307cec5b270d12fcd9c4ad98b279c28c.json +23 -0
  104. data/spec/bitcoin/fixtures/rawtx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16.bin +0 -0
  105. data/spec/bitcoin/fixtures/rawtx-testnet-a220adf1902c46a39db25a24bc4178b6a88440f977a7e2cabfdd8b5c1dd35cfb.json +27 -0
  106. data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.bin +0 -0
  107. data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.json +41 -0
  108. data/spec/bitcoin/fixtures/reorg/blk_0_to_4.dat +0 -0
  109. data/spec/bitcoin/fixtures/reorg/blk_3A.dat +0 -0
  110. data/spec/bitcoin/fixtures/reorg/blk_4A.dat +0 -0
  111. data/spec/bitcoin/fixtures/reorg/blk_5A.dat +0 -0
  112. data/spec/bitcoin/fixtures/testnet/block_0.bin +0 -0
  113. data/spec/bitcoin/fixtures/testnet/block_1.bin +0 -0
  114. data/spec/bitcoin/fixtures/testnet/block_2.bin +0 -0
  115. data/spec/bitcoin/fixtures/testnet/block_3.bin +0 -0
  116. data/spec/bitcoin/fixtures/testnet/block_4.bin +0 -0
  117. data/spec/bitcoin/fixtures/testnet/block_5.bin +0 -0
  118. data/spec/bitcoin/fixtures/txdp-1.txt +32 -0
  119. data/spec/bitcoin/fixtures/txdp-2-signed.txt +19 -0
  120. data/spec/bitcoin/fixtures/txdp-2-unsigned.txt +14 -0
  121. data/spec/bitcoin/key_spec.rb +123 -0
  122. data/spec/bitcoin/network_spec.rb +48 -0
  123. data/spec/bitcoin/protocol/addr_spec.rb +68 -0
  124. data/spec/bitcoin/protocol/alert_spec.rb +20 -0
  125. data/spec/bitcoin/protocol/block_spec.rb +101 -0
  126. data/spec/bitcoin/protocol/inv_spec.rb +124 -0
  127. data/spec/bitcoin/protocol/ping_spec.rb +49 -0
  128. data/spec/bitcoin/protocol/tx_spec.rb +226 -0
  129. data/spec/bitcoin/protocol/version_spec.rb +77 -0
  130. data/spec/bitcoin/reorg_spec.rb +129 -0
  131. data/spec/bitcoin/script/opcodes_spec.rb +417 -0
  132. data/spec/bitcoin/script/script_spec.rb +246 -0
  133. data/spec/bitcoin/spec_helper.rb +36 -0
  134. data/spec/bitcoin/storage_spec.rb +229 -0
  135. data/spec/bitcoin/wallet/coinselector_spec.rb +35 -0
  136. data/spec/bitcoin/wallet/keygenerator_spec.rb +64 -0
  137. data/spec/bitcoin/wallet/keystore_spec.rb +188 -0
  138. data/spec/bitcoin/wallet/txdp_spec.rb +74 -0
  139. data/spec/bitcoin/wallet/wallet_spec.rb +207 -0
  140. metadata +295 -0
data/bin/bitcoin_gui ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift( File.expand_path("../../lib", __FILE__) )
3
+
4
+ require 'bitcoin'
5
+ require 'optparse'
6
+
7
+ Bitcoin.require_dependency 'ffi-gtk3', gem: "gir_ffi"
8
+ Bitcoin.require_dependency :eventmachine
9
+ Bitcoin.require_dependency :notify
10
+ Bitcoin.require_dependency :json
11
+
12
+
13
+ defaults = {
14
+ :network => "bitcoin",
15
+ :storage => "dummy",
16
+ :keystore => "simple::file=~/.bitcoin-ruby/keys.json",
17
+ :command => "127.0.0.1:9999"
18
+ }
19
+ options = Bitcoin::Config.load(defaults, :wallet)
20
+
21
+ optparse = OptionParser.new do |opts|
22
+ opts.banner =
23
+ "Usage: bitcoin_wallet [options] <command> [<command options>]\n"
24
+
25
+ opts.separator("\nAvailable options:\n")
26
+
27
+ opts.on("-c", "--config FILE",
28
+ "Config file (default: #{Bitcoin::Config::CONFIG_PATHS})") do |file|
29
+ options = Bitcoin::Config.load_file(options, file, :wallet)
30
+ end
31
+
32
+ opts.on("-n", "--network NETWORK",
33
+ "User Network (default: #{options[:network]})") do |network|
34
+ options[:network] = network
35
+ end
36
+
37
+ opts.on("-s", "--storage BACKEND::CONFIG",
38
+ "Use storage backend (default: #{options[:storage]})") do |storage|
39
+ options[:storage] = storage
40
+ end
41
+
42
+ opts.on("--command [HOST:PORT]",
43
+ "Node command socket (default: #{options[:command]})") do |command|
44
+ options[:command] = command
45
+ end
46
+
47
+ opts.on("-k", "--keystore [backend::<config>]",
48
+ "Key store (default: #{options[:store]})") do |store|
49
+ options[:keystore] = store
50
+ end
51
+
52
+ opts.on("-h", "--help", "Display this help") do
53
+ puts opts; exit
54
+ end
55
+
56
+ opts.separator "\nAvailable commands:\n" +
57
+ " balance [<addr>] - display balance for given addr or whole wallet\n" +
58
+ " list <addr> - list transaction history for address\n" +
59
+ " send <addr>:<amount>[,<addr>:<amount>...] [<fee>] - send transaction\n" +
60
+ " new - generate new key and add to keystore\n" +
61
+ " import <base58> - import key in base58 format\n" +
62
+ " export <addr> - export key to base58 format\n"
63
+
64
+ end
65
+
66
+ optparse.parse!
67
+
68
+ Bitcoin.network = options[:network]
69
+
70
+ backend, config = options[:storage].split("::")
71
+ @storage = Bitcoin::Storage.send(backend, :db => config)
72
+
73
+ backend, config = options[:keystore].split("::")
74
+ config = Hash[config.split(",").map{|c| c.split("=")}]
75
+
76
+ EM.run do
77
+ @gui = Bitcoin::Gui::Gui.new(@storage, config['file'])
78
+ Bitcoin::Gui::Connection.new(*options[:command].split(":"), @gui)
79
+ EM.gtk_main
80
+ end
data/bin/bitcoin_node ADDED
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift( File.expand_path("../../lib", __FILE__) )
3
+
4
+ require 'bitcoin'
5
+ require 'optparse'
6
+ Bitcoin.require_dependency :eventmachine
7
+ Bitcoin.require_dependency :json
8
+
9
+ defaults = {
10
+ :network => "bitcoin",
11
+ :command => "127.0.0.1:9999",
12
+ :listen => "0.0.0.0:8332",
13
+ :connect => "",
14
+ :storage => "sequel::sqlite://bitcoin.db",
15
+ :headers_only => false,
16
+ :dns => true,
17
+ :epoll => false,
18
+ :epoll_limit => 10000,
19
+ :epoll_user => nil,
20
+ :log => {
21
+ :network => :info,
22
+ :storage => :info,
23
+ },
24
+ :max => {
25
+ :connections => 32,
26
+ :addr => 1024,
27
+ :queue => 500,
28
+ :inv => 500,
29
+ },
30
+ :intervals => {
31
+ :queue => 5,
32
+ :inv_queue => 5,
33
+ :addrs => 15,
34
+ :connect => 30,
35
+ },
36
+ :daemon => false,
37
+ }
38
+
39
+ options = Bitcoin::Config.load(defaults, :blockchain)
40
+
41
+ optparse = OptionParser.new do |opts|
42
+ opts.banner = "Usage: bitcoin_node [options]"
43
+
44
+ opts.separator("\nAvailable options:\n")
45
+
46
+ opts.on("-c", "--config FILE",
47
+ "Config file (default: #{Bitcoin::Config::CONFIG_PATHS})") do |file|
48
+ options = Bitcoin::Config.load_file(options, file, :blockchain)
49
+ end
50
+
51
+ opts.on("-n", "--network [NETWORK]",
52
+ "User Network (default: #{options[:network]})") do |network|
53
+ options[:network] = network
54
+ end
55
+
56
+ opts.on("--command [HOST:PORT]",
57
+ "Command socket (default: #{options[:command]})") do |command|
58
+ options[:command] = command
59
+ end
60
+
61
+ opts.on("-l", "--listen [HOST:PORT]",
62
+ "Listen address/port (default: #{options[:listen]})") do |listen|
63
+ options[:listen] = listen
64
+ end
65
+
66
+ opts.on("--connect [HOST:PORT[,HOST:PORT[...]]]",
67
+ "Hosts to connect to (default: #{options[:connect]})") do |connect|
68
+ options[:connect] = connect
69
+ end
70
+
71
+ opts.on("-s", "--storage [BACKEND::CONFIG]",
72
+ "Use storage backend (default: #{options[:storage]})") do |storage|
73
+ options[:storage] = storage
74
+ end
75
+
76
+ opts.on("--ho", "--headers-only",
77
+ "Download only block headers") do
78
+ options[:headers_only] = true
79
+ end
80
+
81
+ opts.on("-d", "--dns", "Use DNS seeds (default)") do
82
+ options[:dns] = true
83
+ end
84
+
85
+ opts.on("--nd", "--no-dns", "Don't use DNS seeds") do
86
+ options[:dns] = false
87
+ end
88
+
89
+ opts.on("--epoll", "Enable epoll support") do
90
+ options[:epoll] = true
91
+ end
92
+
93
+ opts.on("--epoll-limit [NUM]",
94
+ "Increase socket descriptor limit to NUM") do |num|
95
+ options[:epoll_limit] = num.to_i
96
+ end
97
+
98
+ opts.on("--epoll-user [NAME]",
99
+ "Set effective user after increasing socket descriptor limit") do |user|
100
+ options[:epoll_user] = user
101
+ end
102
+
103
+ [:connections, :addr, :queue, :inv].each do |name|
104
+ opts.on("--m#{name.to_s[0]}", "--max-#{name} [COUNT]",
105
+ "Max #{name} (default: #{options[:max][name]})") do |count|
106
+ options[:max][name] = count.to_i
107
+ end
108
+ end
109
+
110
+ [:queue, :inv_queue, :blocks, :addrs, :connect].each do |name|
111
+ opts.on("--i#{name.to_s[0]}", "--interval-#{name} [SECONDS]",
112
+ "Interval for #{name} worker (default: #{options[:intervals][name]})") do |sec|
113
+ options[:intervals][name] = sec.to_i
114
+ end
115
+ end
116
+
117
+ [:network, :storage].each do |name|
118
+ opts.on("--l#{name.to_s[0]}", "--log-#{name} [LEVEL]",
119
+ "Log level for #{name} (default: #{options[:log][name]})") do |level|
120
+ options[:log][name] = level.to_sym
121
+ end
122
+ end
123
+
124
+ opts.on("-v", "--verbose", "Set all loggers to debug") do
125
+ options[:log].each_key {|k| options[:log][k] = :debug }
126
+ end
127
+
128
+ opts.on("-q", "--quiet", "Set all loggers to warn") do
129
+ options[:log].each_key {|k| options[:log][k] = :warn }
130
+ end
131
+
132
+ opts.on( '-h', '--help', 'Display this screen' ) do
133
+ puts opts; exit
134
+ end
135
+ end
136
+
137
+ optparse.parse!
138
+
139
+ Bitcoin.network = options[:network]
140
+ options[:command] = options[:command] == "" ? nil : options[:command].split(':')
141
+ options[:listen] = options[:listen] == "" ? nil : options[:listen].split(':')
142
+ options[:connect] = options[:connect] == "" ? [] : options[:connect].split(',').map{|h| h.split(':')}
143
+
144
+ if ARGV.any?
145
+ EM.run do
146
+ Bitcoin::Network::CommandClient.connect(*options[:command]) do
147
+ on_response do |cmd, data|
148
+ unless cmd == "monitor"
149
+ puts JSON.pretty_generate data
150
+ EM.stop
151
+ end
152
+ end
153
+ on_block do |block, depth|
154
+ puts "block: #{block['hash']} (#{depth})"
155
+ end
156
+ on_tx do |tx|
157
+ puts "tx: #{tx['hash']}"
158
+ end
159
+ on_connection do |type, host|
160
+ if type == "connected"
161
+ puts "Connected: #{host['host']}:#{host['port']}"
162
+ else
163
+ puts "Disconnected: #{host.inspect}"
164
+ end
165
+ end
166
+ on_connected do
167
+ request(ARGV[0], *(ARGV[1] || "").split(" "))
168
+ end
169
+ end
170
+ end
171
+ else
172
+ node = Bitcoin::Network::Node.new(options)
173
+ node.run
174
+ end
data/bin/bitcoin_shell ADDED
@@ -0,0 +1,12 @@
1
+ $:.unshift( File.expand_path("../../lib", __FILE__) )
2
+ require 'bitcoin'
3
+
4
+ include Bitcoin
5
+ include Bitcoin::Util
6
+ include Bitcoin::Storage
7
+
8
+ Bitcoin.network = ARGV.shift || :bitcoin
9
+
10
+ require 'irb'
11
+ require 'irb/completion'
12
+ IRB.start
@@ -0,0 +1,323 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift( File.expand_path("../../lib", __FILE__) )
3
+
4
+ require 'bitcoin'
5
+ require 'eventmachine'
6
+ require 'optparse'
7
+ require 'yaml'
8
+
9
+ defaults = {
10
+ :network => "testnet",
11
+ :storage => "dummy",
12
+ :keystore => "simple::file=#{ENV['HOME']}/.bitcoin-ruby/keys.json",
13
+ :command => "127.0.0.1:9999"
14
+ }
15
+ options = Bitcoin::Config.load(defaults, :wallet)
16
+
17
+ optparse = OptionParser.new do |opts|
18
+ opts.banner =
19
+ "Usage: bitcoin_wallet [options] <command> [<command options>]\n"
20
+
21
+ opts.separator("\nAvailable options:\n")
22
+
23
+ opts.on("-c", "--config FILE",
24
+ "Config file (default: #{Bitcoin::Config::CONFIG_PATHS})") do |file|
25
+ options = Bitcoin::Config.load_file(options, file, :wallet)
26
+ end
27
+
28
+ opts.on("-n", "--network NETWORK",
29
+ "User Network (default: #{options[:network]})") do |network|
30
+ options[:network] = network
31
+ end
32
+
33
+ opts.on("-s", "--storage BACKEND::CONFIG",
34
+ "Use storage backend (default: #{options[:storage]})") do |storage|
35
+ options[:storage] = storage
36
+ end
37
+
38
+ opts.on("--command [HOST:PORT]",
39
+ "Node command socket (default: #{options[:command]})") do |command|
40
+ options[:command] = command
41
+ end
42
+
43
+ opts.on("-k", "--keystore [backend::<config>]",
44
+ "Key store (default: #{options[:store]})") do |store|
45
+ options[:keystore] = store.gsub("~", ENV['HOME'])
46
+ end
47
+
48
+ opts.on("-h", "--help", "Display this help") do
49
+ puts opts; exit
50
+ end
51
+
52
+ opts.separator "\nAvailable commands:\n" +
53
+ " balance [<addr>] - display balance for given addr or whole wallet\n" +
54
+ " list <addr> - list transaction history for address\n" +
55
+ " send <addr>:<amount>[,<addr>:<amount>...] [<fee>] - send transaction\n" +
56
+ " new - generate new key and add to keystore\n" +
57
+ " import <base58> - import key in base58 format\n" +
58
+ " export <addr> - export key to base58 format\n"
59
+
60
+ end
61
+
62
+ optparse.parse!
63
+
64
+ cmd = ARGV.shift; cmdopts = ARGV
65
+ unless cmd
66
+ exit puts optparse
67
+ end
68
+
69
+ Bitcoin.network = options[:network]
70
+ backend, config = options[:keystore].split("::")
71
+ config = Hash[config.split(",").map{|c| c.split("=")}]
72
+ keystore = Bitcoin::Wallet.const_get("#{backend.capitalize}KeyStore").new(config)
73
+ if backend == "deterministic" && !config["nonce"]
74
+ puts "nonce: #{keystore.generator.nonce}"
75
+ end
76
+ #puts *keystore.get_keys.map(&:addr)
77
+ backend, config = options[:storage].split("::")
78
+ storage = Bitcoin::Storage.send(backend, :db => config)
79
+
80
+
81
+ wallet = Bitcoin::Wallet::Wallet.new(storage, keystore, Bitcoin::Wallet::SimpleCoinSelector)
82
+
83
+
84
+ def str_val(val, pre='')
85
+ ("#{pre}%.8f" % (val / 1e8)).rjust(15)
86
+ end
87
+
88
+ def val_str(str)
89
+ (str.to_f * 1e8).to_i
90
+ end
91
+
92
+ case cmd
93
+ when "balance"
94
+ if cmdopts && cmdopts.size == 1
95
+ addr = cmdopts[0]
96
+ balance = storage.get_balance(Bitcoin.hash160_from_address(addr))
97
+ puts "#{addr} balance: #{str_val balance}"
98
+ else
99
+ puts "Total balance: #{str_val wallet.get_balance}"
100
+ end
101
+
102
+ when "new"
103
+ puts "Generated new key with address: #{wallet.get_new_addr}"
104
+
105
+ when "add"
106
+ key = {:label => ARGV[2]}
107
+ case ARGV[0]
108
+ when "pub"
109
+ k = Bitcoin::Key.new(nil, ARGV[1])
110
+ key[:key] = k
111
+ key[:addr] = k.addr
112
+ when "priv"
113
+ k = Bitcoin::Key.new(ARGV[1], nil)
114
+ k.regenerate_pubkey
115
+ key[:key] = k
116
+ key[:addr] = k.addr
117
+ when "addr"
118
+ key[:addr] = ARGV[1]
119
+ else
120
+ raise "unknown type #{ARGV[0]}"
121
+ end
122
+ wallet.add_key key
123
+
124
+ when "label"
125
+ wallet.label(ARGV[0], ARGV[1])
126
+
127
+ when "flag"
128
+ wallet.flag(ARGV[0], *ARGV[1].split("="))
129
+
130
+ when "key"
131
+ key = wallet.keystore.key(ARGV[0])
132
+ puts "Label: #{key[:label]}"
133
+ puts "Address: #{key[:addr]}"
134
+ puts "Pubkey: #{key[:key].pub}"
135
+ puts "Privkey: #{key[:key].priv}" if ARGV[1] == '-p'
136
+ puts "Mine: #{key[:mine]}"
137
+
138
+
139
+ when "import"
140
+ if wallet.keystore.respond_to?(:import)
141
+ addr = wallet.keystore.import(cmdopts[0])
142
+ puts "Key for #{addr} imported."
143
+ else
144
+ puts "Keystore doesn't support importing."
145
+ end
146
+
147
+ when "export"
148
+ base58 = wallet.keystore.export(cmdopts[0])
149
+ puts "Base58 encoded private key for #{cmdopts[0]}:"
150
+ puts base58
151
+
152
+ when "list"
153
+ if cmdopts && cmdopts.size == 1
154
+ depth = storage.get_depth
155
+ total = 0
156
+ key = wallet.keystore.key(cmdopts[0])
157
+ storage.get_txouts_for_address(key[:addr]).each do |txout|
158
+ total += txout.value
159
+ tx = txout.get_tx
160
+ blocks = depth - tx.get_block.depth rescue 0
161
+ puts "#{tx.hash} | #{str_val txout.value, '+ '} | " +
162
+ "#{str_val total} | #{blocks}"
163
+ tx.in.map(&:get_prev_out).each do |prev_out|
164
+ if prev_out
165
+ puts " <- #{prev_out.get_address}"
166
+ else
167
+ puts " <- generation"
168
+ end
169
+ end
170
+ puts
171
+
172
+ if txin = txout.get_next_in
173
+ tx = txin.get_tx
174
+ total -= txout.value
175
+ blocks = depth - tx.get_block.depth rescue 0
176
+ puts "#{tx.hash} | #{str_val txout.value, '- '} | " +
177
+ "#{str_val total} | #{blocks}"
178
+ txin.get_tx.out.each do |out|
179
+ puts " -> #{out.get_addresses.join(', ')}"
180
+ end
181
+ puts
182
+ end
183
+ end
184
+ puts "Total balance: #{str_val total}"
185
+ else
186
+ puts "Wallet addresses:"
187
+ total = 0
188
+ wallet.list.each do |key, balance|
189
+ total += balance
190
+ icon = key[:key] && key[:key].priv ? "P" : (key[:mine] ? "M" : " ")
191
+ puts " #{icon} #{key[:label].to_s.ljust(10)} (#{key[:addr].to_s.ljust(34)}) - #{("%.8f" % (balance / 1e8)).rjust(15)}"
192
+ end
193
+ puts "Total balance: #{str_val wallet.get_balance}"
194
+ end
195
+
196
+ when "send"
197
+ to = cmdopts[0].split(',').map do |pair|
198
+ type, *addrs, value = pair.split(":")
199
+ value = val_str(value)
200
+ [type.to_sym, *addrs, value]
201
+ end
202
+ fee = val_str(cmdopts[1]) || 0
203
+ value = val_str value
204
+
205
+ unless wallet.get_balance >= (to.map{|t|t[-1]}.inject{|a,b|a+=b;a} + fee)
206
+ puts "Insufficient funds."; exit
207
+ end
208
+
209
+ tx = wallet.new_tx(to, fee)
210
+
211
+ if tx.is_a?(Bitcoin::Wallet::TxDP)
212
+ puts "Transaction needs to be signed by additional keys."
213
+ print "Filename to save TxDP: [./#{tx.id}.txdp] "
214
+ $stdout.flush
215
+ filename = $stdin.gets.strip
216
+ filename = "./#{tx.id}.txdp" if filename == ""
217
+ File.open(filename, "w") {|f| f.write(tx.serialize) }
218
+ exit
219
+ end
220
+
221
+ unless tx
222
+ puts "Error creating tx."; exit
223
+ end
224
+
225
+ total = 0
226
+ puts "Hash: #{tx.hash}"
227
+ puts "inputs:"
228
+ tx.in.each do |txin|
229
+ prev_out = storage.get_txout_for_txin(txin)
230
+ total += prev_out.value
231
+ puts " #{prev_out.get_address} - #{str_val prev_out.value}"
232
+ end
233
+
234
+ puts "outputs:"
235
+ tx.out.each do |txout|
236
+ total -= txout.value
237
+ script = Bitcoin::Script.new(txout.pk_script)
238
+ if script.is_pubkey?
239
+ puts "#{str_val txout.value} #{script.get_pubkey} (pubkey)"
240
+ elsif script.is_hash160?
241
+ puts "#{str_val txout.value} #{script.get_address} (address)"
242
+ elsif script.is_multisig?
243
+ puts "#{str_val txout.value} #{script.get_addresses.join(' ')} (multisig)"
244
+ end
245
+ end
246
+ puts "Fee: #{str_val total}"
247
+
248
+ $stdout.sync = true
249
+ print "Really send transaction? (y/N) " and $stdout.flush
250
+ unless $stdin.gets.chomp.downcase == 'y'
251
+ puts "Aborted."; exit
252
+ end
253
+
254
+ EM.run do
255
+ Bitcoin::Network::CommandClient.connect(*options[:command].split(":")) do
256
+ on_connected do
257
+ request(:relay_tx, tx)
258
+ end
259
+ on_relay_tx do
260
+ puts "Transaction #{tx.hash} relayed"
261
+ EM.stop
262
+ end
263
+ end
264
+ end
265
+
266
+ when "sign"
267
+ txt = File.read(ARGV[0])
268
+ txdp = Bitcoin::Wallet::TxDP.parse(txt)
269
+ puts txdp.tx[0].to_json
270
+
271
+ print "Really sign transaction? (y/N) " and $stdout.flush
272
+ unless $stdin.gets.chomp.downcase == 'y'
273
+ puts "Aborted."; exit
274
+ end
275
+
276
+ txdp.sign_inputs do |tx, prev_tx, i, addr|
277
+ key = keystore.key(addr)[:key] rescue nil
278
+ next nil unless key && !key.priv.nil?
279
+ sig_hash = tx.signature_hash_for_input(i, prev_tx)
280
+ sig = key.sign(sig_hash)
281
+ script_sig = Bitcoin::Script.to_pubkey_script_sig(sig, [key.pub].pack("H*"))
282
+ script_sig.unpack("H*")[0]
283
+ end
284
+ File.open(ARGV[0], "w") {|f| f.write txdp.serialize }
285
+
286
+ when "relay"
287
+ txt = File.read(ARGV[0])
288
+ txdp = Bitcoin::Wallet::TxDP.parse(txt)
289
+ tx = txdp.tx[0]
290
+ puts tx.to_json
291
+ txdp.inputs.each_with_index do |s, i|
292
+ value, sigs = *s
293
+ tx.in[i].script_sig = [sigs[0][1]].pack("H*")
294
+ end
295
+ tx.in.each_with_index do |txin, i|
296
+ p txdp.tx.map(&:hash)
297
+ prev_tx = storage.get_tx(txin.prev_out.reverse.unpack("H*")[0])
298
+ raise "prev tx #{txin.prev_out.reverse.unpack("H*")[0]} not found" unless prev_tx
299
+ raise "signature error" unless tx.verify_input_signature(i, prev_tx)
300
+ end
301
+
302
+ $stdout.sync = true
303
+ print "Really send transaction? (y/N) " and $stdout.flush
304
+ unless $stdin.gets.chomp.downcase == 'y'
305
+ puts "Aborted."; exit
306
+ end
307
+
308
+ EM.run do
309
+ EM.connect(*options[:command].split(":")) do |conn|
310
+ conn.send_data(["relay_tx", tx.to_payload.unpack("H*")[0]].to_json)
311
+ def conn.receive_data(data)
312
+ (@buf ||= BufferedTokenizer.new("\x00")).extract(data).each do |packet|
313
+ res = JSON.load(packet)
314
+ puts "Transaction relayed: #{res[1]["hash"]}"
315
+ EM.stop
316
+ end
317
+ end
318
+ end
319
+ end
320
+
321
+ else
322
+ puts "Unknown command."
323
+ end