bitcoinrb 0.1.3 → 0.1.4

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -0
  3. data/README.md +31 -9
  4. data/bitcoinrb.conf.sample +0 -0
  5. data/bitcoinrb.gemspec +6 -2
  6. data/exe/bitcoinrb-cli +2 -2
  7. data/lib/bitcoin/block_header.rb +9 -2
  8. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  9. data/lib/bitcoin/constants.rb +3 -0
  10. data/lib/bitcoin/key.rb +70 -5
  11. data/lib/bitcoin/logger.rb +25 -1
  12. data/lib/bitcoin/message/addr.rb +0 -39
  13. data/lib/bitcoin/message/headers.rb +1 -0
  14. data/lib/bitcoin/message/inventory.rb +4 -0
  15. data/lib/bitcoin/message/network_addr.rb +44 -0
  16. data/lib/bitcoin/message/version.rb +8 -3
  17. data/lib/bitcoin/message.rb +12 -0
  18. data/lib/bitcoin/mnemonic.rb +2 -2
  19. data/lib/bitcoin/network/connection.rb +14 -1
  20. data/lib/bitcoin/network/message_handler.rb +52 -19
  21. data/lib/bitcoin/network/peer.rb +142 -5
  22. data/lib/bitcoin/network/peer_discovery.rb +16 -8
  23. data/lib/bitcoin/network/pool.rb +39 -14
  24. data/lib/bitcoin/network.rb +0 -1
  25. data/lib/bitcoin/node/cli.rb +66 -0
  26. data/lib/bitcoin/node/configuration.rb +37 -0
  27. data/lib/bitcoin/node/spv.rb +13 -3
  28. data/lib/bitcoin/node.rb +2 -1
  29. data/lib/bitcoin/rpc/http_server.rb +56 -0
  30. data/lib/bitcoin/rpc/request_handler.rb +84 -0
  31. data/lib/bitcoin/rpc.rb +6 -0
  32. data/lib/bitcoin/script/multisig.rb +92 -0
  33. data/lib/bitcoin/script/script.rb +17 -7
  34. data/lib/bitcoin/script/script_interpreter.rb +2 -38
  35. data/lib/bitcoin/store/chain_entry.rb +64 -0
  36. data/lib/bitcoin/store/db/level_db.rb +101 -0
  37. data/lib/bitcoin/store/db.rb +9 -0
  38. data/lib/bitcoin/store/spv_chain.rb +96 -0
  39. data/lib/bitcoin/store.rb +5 -1
  40. data/lib/bitcoin/tx.rb +6 -2
  41. data/lib/bitcoin/version.rb +1 -1
  42. data/lib/bitcoin/wallet/account.rb +82 -0
  43. data/lib/bitcoin/wallet/base.rb +84 -0
  44. data/lib/bitcoin/wallet/db.rb +57 -0
  45. data/lib/bitcoin/wallet/master_key.rb +100 -0
  46. data/lib/bitcoin/wallet.rb +8 -0
  47. data/lib/bitcoin.rb +4 -0
  48. data/lib/openassets/payload.rb +6 -2
  49. metadata +70 -13
  50. data/lib/bitcoin/node/spv_block_chain.rb +0 -15
  51. data/lib/bitcoin/store/spv_block_store.rb +0 -11
@@ -0,0 +1,84 @@
1
+ require 'leveldb'
2
+ module Bitcoin
3
+ module Wallet
4
+
5
+ class Base
6
+
7
+ attr_accessor :wallet_id
8
+ attr_reader :db
9
+
10
+ DEFAULT_PATH_PREFIX = "#{Bitcoin.base_dir}/db/wallet"
11
+
12
+ # Create new wallet. If wallet already exist, throw error.
13
+ # The wallet generates a seed using SecureRandom and store to db at initialization.
14
+ # @param [String] wallet_id new wallet id.
15
+ # @param [String] path_prefix wallet file path prefix.
16
+ # @return [Bitcoin::Wallet::Base] the wallet
17
+ def self.create(wallet_id = 1, path_prefix = DEFAULT_PATH_PREFIX)
18
+ raise ArgumentError, "wallet_id : #{wallet_id} already exist." if self.exist?(wallet_id, path_prefix)
19
+ w = self.new(wallet_id, path_prefix)
20
+ # generate seed
21
+ raise RuntimeError, 'the seed already exist.' if w.db.registered_master?
22
+ master = Bitcoin::Wallet::MasterKey.generate
23
+ w.db.register_master_key(master)
24
+ w
25
+ end
26
+
27
+ # load wallet with specified +wallet_id+
28
+ # @return [Bitcoin::Wallet::Base] the wallet
29
+ def self.load(wallet_id, path_prefix = DEFAULT_PATH_PREFIX)
30
+ raise ArgumentError, "wallet_id : #{wallet_id} dose not exist." unless self.exist?(wallet_id, path_prefix)
31
+ self.new(wallet_id, path_prefix)
32
+ end
33
+
34
+ # get account list based on BIP-44
35
+ def accounts
36
+ db.accounts.map{|raw| Account.parse_from_payload(raw)}
37
+ end
38
+
39
+ def create_account(purpose = Account::PURPOSE_TYPE[:legacy], index = 0, name)
40
+ account = Account.new(purpose, index, name)
41
+ account.wallet = self
42
+ account.init
43
+ account
44
+ end
45
+
46
+ # close database wallet
47
+ def close
48
+ db.close
49
+ end
50
+
51
+ # get master key
52
+ # @return [Bitcoin::Wallet::MasterKey]
53
+ def master_key
54
+ db.master_key
55
+ end
56
+
57
+ # encrypt wallet
58
+ # @param [String] passphrase the wallet passphrase
59
+ def encrypt(passphrase)
60
+
61
+ end
62
+
63
+ # decrypt wallet
64
+ # @param [String] passphrase the wallet passphrase
65
+ def decrypt(passphrase)
66
+
67
+ end
68
+
69
+ private
70
+
71
+ def initialize(wallet_id, path_prefix)
72
+ @db = Bitcoin::Wallet::DB.new("#{path_prefix}_#{wallet_id}")
73
+ @wallet_id = wallet_id
74
+ end
75
+
76
+ def self.exist?(wallet_id, path_prefix)
77
+ path = "#{path_prefix}_#{wallet_id}"
78
+ Dir.exist?(path)
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,57 @@
1
+ module Bitcoin
2
+ module Wallet
3
+
4
+ class DB
5
+
6
+ KEY_PREFIX = {
7
+ account: 'a', # key: account index, value: Account raw data.
8
+ master: 'm', # value : wallet seed.
9
+ }
10
+
11
+ attr_reader :level_db
12
+ attr_accessor :master_key
13
+
14
+ def initialize(path = "#{Bitcoin.base_dir}/db/wallet")
15
+ FileUtils.mkdir_p(path)
16
+ @level_db = ::LevelDB::DB.new(path)
17
+ end
18
+
19
+ # close database
20
+ def close
21
+ level_db.close
22
+ end
23
+
24
+ # get accounts raw data.
25
+ def accounts
26
+ from = KEY_PREFIX[:account] + '00000000'
27
+ to = KEY_PREFIX[:account] + 'ffffffff'
28
+ level_db.each(from: from, to: to).map { |k, v| v}
29
+ end
30
+
31
+ def save_account(account)
32
+ level_db.batch do
33
+ key = KEY_PREFIX[:account] + account.index.to_s(16).rjust(8, '0')
34
+ level_db.put(key, account.to_payload)
35
+ end
36
+ end
37
+
38
+ # get master_key
39
+ def master_key
40
+ @master_key ||= Bitcoin::Wallet::MasterKey.parse_from_payload(level_db.get(KEY_PREFIX[:master]))
41
+ end
42
+
43
+ # save seed
44
+ # @param [Bitcoin::Wallet::MasterKey] master a master key.
45
+ def register_master_key(master)
46
+ level_db.put(KEY_PREFIX[:master], master.to_payload)
47
+ @master_key = master
48
+ end
49
+
50
+ # whether master key registered.
51
+ def registered_master?
52
+ !level_db.get(KEY_PREFIX[:master]).nil?
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,100 @@
1
+ module Bitcoin
2
+ module Wallet
3
+
4
+ # HD Wallet master seed
5
+ class MasterKey
6
+ extend Bitcoin::Util
7
+ include Bitcoin::Util
8
+
9
+ attr_reader :seed
10
+ attr_accessor :salt
11
+ attr_accessor :encrypted
12
+ attr_accessor :mnemonic # ephemeral data existing only at initialization
13
+
14
+ def initialize(seed, salt: '', encrypted: false, mnemonic: nil)
15
+ @mnemonic = mnemonic
16
+ @seed = seed
17
+ @encrypted = encrypted
18
+ @salt = salt
19
+ end
20
+
21
+ # generate new master key.
22
+ # @return Bitcoin::Wallet::MasterKey
23
+ def self.generate
24
+ entropy = SecureRandom.hex(16)
25
+ mnemonic = Bitcoin::Mnemonic.new('english')
26
+ self.recover_from_words(mnemonic.to_mnemonic(entropy))
27
+ end
28
+
29
+ # recover master key from mnemonic word list.
30
+ # @param [Array] words the mnemonic word list.
31
+ # @return Bitcoin::Wallet::MasterKey
32
+ def self.recover_from_words(words)
33
+ mnemonic = Bitcoin::Mnemonic.new('english')
34
+ seed = mnemonic.to_seed(words)
35
+ self.new(seed, mnemonic: words)
36
+ end
37
+
38
+ # parse master key raw data
39
+ # @param [String] payload raw data
40
+ # @return [Bitcoin::Wallet::MasterKey]
41
+ def self.parse_from_payload(payload)
42
+ flag, payload = unpack_var_int(payload)
43
+ raise 'encrypted flag is invalid.' unless [0, 1].include?(flag)
44
+ salt, payload = unpack_var_string(payload)
45
+ seed, payload = unpack_var_string(payload)
46
+ self.new(seed.bth, salt: salt.bth, encrypted: flag == 1)
47
+ end
48
+
49
+ # generate payload with following format
50
+ # [encrypted(false:0, true:1)][salt(var str)][seed(var str)]
51
+ def to_payload
52
+ flg = encrypted ? 1 : 0
53
+ pack_var_int(flg) << [salt, seed].map{|v|pack_var_string(v.htb)}.join
54
+ end
55
+
56
+ # get master key
57
+ # @return [Bitcoin::ExtKey] the master key
58
+ def key
59
+ raise 'seed is encrypted. please decrypt the seed.' if encrypted
60
+ Bitcoin::ExtKey.generate_master(seed)
61
+ end
62
+
63
+ # encrypt seed
64
+ def encrypt(passphrase)
65
+ raise 'seed already encrypted.' if encrypted
66
+ @salt = SecureRandom.hex(16)
67
+ enc = OpenSSL::Cipher.new('AES-256-CBC')
68
+ enc.encrypt
69
+ enc.key, enc.iv = key_iv(enc, passphrase)
70
+ encrypted_data = ''
71
+ encrypted_data << enc.update(seed)
72
+ encrypted_data << enc.final
73
+ @seed = encrypted_data
74
+ @encrypted = true
75
+ end
76
+
77
+ # decrypt seed
78
+ def decrypt(passphrase)
79
+ raise 'seed is not encrypted.' unless encrypted
80
+ dec = OpenSSL::Cipher.new('AES-256-CBC')
81
+ dec.decrypt
82
+ dec.key, dec.iv = key_iv(dec, passphrase)
83
+ decrypted_data = ''
84
+ decrypted_data << dec.update(seed)
85
+ decrypted_data << dec.final
86
+ @seed = decrypted_data
87
+ @encrypted = false
88
+ @salt = ''
89
+ end
90
+
91
+ private
92
+
93
+ def key_iv(enc, passphrase)
94
+ key_iv = OpenSSL::PKCS5.pbkdf2_hmac_sha1(passphrase, salt, 2000, enc.key_len + enc.iv_len)
95
+ [key_iv[0, enc.key_len], key_iv[enc.key_len, enc.iv_len]]
96
+ end
97
+
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,8 @@
1
+ module Bitcoin
2
+ module Wallet
3
+ autoload :Base, 'bitcoin/wallet/base'
4
+ autoload :Account, 'bitcoin/wallet/account'
5
+ autoload :DB, 'bitcoin/wallet/db'
6
+ autoload :MasterKey, 'bitcoin/wallet/master_key'
7
+ end
8
+ end
data/lib/bitcoin.rb CHANGED
@@ -5,6 +5,7 @@ require 'securerandom'
5
5
  require 'json'
6
6
  require 'bech32'
7
7
  require 'ffi'
8
+ require 'tmpdir'
8
9
  require_relative 'openassets'
9
10
 
10
11
  module Bitcoin
@@ -17,6 +18,7 @@ module Bitcoin
17
18
  autoload :BlockHeader, 'bitcoin/block_header'
18
19
  autoload :Tx, 'bitcoin/tx'
19
20
  autoload :Script, 'bitcoin/script/script'
21
+ autoload :Multisig, 'bitcoin/script/multisig'
20
22
  autoload :ScriptInterpreter, 'bitcoin/script/script_interpreter'
21
23
  autoload :ScriptError, 'bitcoin/script/script_error'
22
24
  autoload :TxChecker, 'bitcoin/script/tx_checker'
@@ -35,6 +37,8 @@ module Bitcoin
35
37
  autoload :ValidationState, 'bitcoin/validation'
36
38
  autoload :Network, 'bitcoin/network'
37
39
  autoload :Store, 'bitcoin/store'
40
+ autoload :RPC, 'bitcoin/rpc'
41
+ autoload :Wallet, 'bitcoin/wallet'
38
42
 
39
43
  require_relative 'bitcoin/constants'
40
44
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OpenAssets
2
4
 
3
5
  MARKER = "\x4f\x41"
@@ -34,9 +36,11 @@ module OpenAssets
34
36
 
35
37
  # generate binary payload
36
38
  def to_payload
37
- payload = MARKER << VERSION
39
+ payload = String.new
40
+ payload << MARKER
41
+ payload << VERSION
38
42
  payload << Bitcoin.pack_var_int(quantities.size) << quantities.map{|q| LEB128.encode_unsigned(q).read }.join
39
- payload << Bitcoin.pack_var_int(metadata.length) << metadata.bytes.map{|b|b.to_s(16)}.join.htb
43
+ payload << Bitcoin.pack_var_int(metadata.length) << metadata.bytes.map{|b| sprintf("%02x", b)}.join.htb
40
44
  payload
41
45
  end
42
46
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitcoinrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-10-03 00:00:00.000000000 Z
11
+ date: 2017-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ecdsa
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 1.0.1
61
+ version: 1.0.3
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 1.0.1
68
+ version: 1.0.3
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: daemon-spawn
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -109,7 +109,21 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: parallel
112
+ name: leb128
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 1.0.0
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 1.0.0
125
+ - !ruby/object:Gem::Dependency
126
+ name: leveldb-ruby
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
129
  - - ">="
@@ -123,19 +137,47 @@ dependencies:
123
137
  - !ruby/object:Gem::Version
124
138
  version: '0'
125
139
  - !ruby/object:Gem::Dependency
126
- name: leb128
140
+ name: eventmachine_httpserver
127
141
  requirement: !ruby/object:Gem::Requirement
128
142
  requirements:
129
- - - "~>"
143
+ - - ">="
130
144
  - !ruby/object:Gem::Version
131
- version: 1.0.0
145
+ version: '0'
132
146
  type: :runtime
133
147
  prerelease: false
134
148
  version_requirements: !ruby/object:Gem::Requirement
135
149
  requirements:
136
- - - "~>"
150
+ - - ">="
137
151
  - !ruby/object:Gem::Version
138
- version: 1.0.0
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rest-client
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: iniparse
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
139
181
  - !ruby/object:Gem::Dependency
140
182
  name: bundler
141
183
  requirement: !ruby/object:Gem::Requirement
@@ -213,6 +255,7 @@ files:
213
255
  - Rakefile
214
256
  - bin/console
215
257
  - bin/setup
258
+ - bitcoinrb.conf.sample
216
259
  - bitcoinrb.gemspec
217
260
  - exe/bitcoinrb-cli
218
261
  - exe/bitcoinrbd
@@ -249,6 +292,7 @@ files:
249
292
  - lib/bitcoin/message/inventory.rb
250
293
  - lib/bitcoin/message/mem_pool.rb
251
294
  - lib/bitcoin/message/merkle_block.rb
295
+ - lib/bitcoin/message/network_addr.rb
252
296
  - lib/bitcoin/message/not_found.rb
253
297
  - lib/bitcoin/message/ping.rb
254
298
  - lib/bitcoin/message/pong.rb
@@ -273,10 +317,15 @@ files:
273
317
  - lib/bitcoin/network/peer_discovery.rb
274
318
  - lib/bitcoin/network/pool.rb
275
319
  - lib/bitcoin/node.rb
320
+ - lib/bitcoin/node/cli.rb
321
+ - lib/bitcoin/node/configuration.rb
276
322
  - lib/bitcoin/node/spv.rb
277
- - lib/bitcoin/node/spv_block_chain.rb
278
323
  - lib/bitcoin/opcodes.rb
279
324
  - lib/bitcoin/out_point.rb
325
+ - lib/bitcoin/rpc.rb
326
+ - lib/bitcoin/rpc/http_server.rb
327
+ - lib/bitcoin/rpc/request_handler.rb
328
+ - lib/bitcoin/script/multisig.rb
280
329
  - lib/bitcoin/script/script.rb
281
330
  - lib/bitcoin/script/script_error.rb
282
331
  - lib/bitcoin/script/script_interpreter.rb
@@ -286,13 +335,21 @@ files:
286
335
  - lib/bitcoin/secp256k1/native.rb
287
336
  - lib/bitcoin/secp256k1/ruby.rb
288
337
  - lib/bitcoin/store.rb
289
- - lib/bitcoin/store/spv_block_store.rb
338
+ - lib/bitcoin/store/chain_entry.rb
339
+ - lib/bitcoin/store/db.rb
340
+ - lib/bitcoin/store/db/level_db.rb
341
+ - lib/bitcoin/store/spv_chain.rb
290
342
  - lib/bitcoin/tx.rb
291
343
  - lib/bitcoin/tx_in.rb
292
344
  - lib/bitcoin/tx_out.rb
293
345
  - lib/bitcoin/util.rb
294
346
  - lib/bitcoin/validation.rb
295
347
  - lib/bitcoin/version.rb
348
+ - lib/bitcoin/wallet.rb
349
+ - lib/bitcoin/wallet/account.rb
350
+ - lib/bitcoin/wallet/base.rb
351
+ - lib/bitcoin/wallet/db.rb
352
+ - lib/bitcoin/wallet/master_key.rb
296
353
  - lib/openassets.rb
297
354
  - lib/openassets/marker_output.rb
298
355
  - lib/openassets/payload.rb
@@ -316,7 +373,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
316
373
  version: '0'
317
374
  requirements: []
318
375
  rubyforge_project:
319
- rubygems_version: 2.6.13
376
+ rubygems_version: 2.6.12
320
377
  signing_key:
321
378
  specification_version: 4
322
379
  summary: "[WIP]The implementation of Bitcoin Protocol for Ruby."
@@ -1,15 +0,0 @@
1
- module Bitcoin
2
- module Node
3
-
4
- class SPVBlockChain
5
-
6
- attr_reader :block_store
7
-
8
- def initialize(block_store = Bitcoin::Store::SPVBlockStore.new)
9
- @block_store = block_store
10
- end
11
-
12
- end
13
-
14
- end
15
- end
@@ -1,11 +0,0 @@
1
- module Bitcoin
2
-
3
- module Store
4
-
5
- class SPVBlockStore
6
-
7
- end
8
-
9
- end
10
-
11
- end