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