bitcoinrb 0.2.9 → 0.5.0

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +3 -2
  4. data/README.md +7 -6
  5. data/bitcoinrb.gemspec +4 -4
  6. data/exe/bitcoinrbd +5 -0
  7. data/lib/bitcoin.rb +33 -1
  8. data/lib/bitcoin/bip85_entropy.rb +111 -0
  9. data/lib/bitcoin/block_header.rb +2 -0
  10. data/lib/bitcoin/chain_params.rb +0 -8
  11. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  12. data/lib/bitcoin/chainparams/testnet.yml +1 -1
  13. data/lib/bitcoin/constants.rb +3 -10
  14. data/lib/bitcoin/descriptor.rb +147 -0
  15. data/lib/bitcoin/ext.rb +5 -0
  16. data/lib/bitcoin/ext/json_parser.rb +46 -0
  17. data/lib/bitcoin/ext_key.rb +19 -4
  18. data/lib/bitcoin/key.rb +9 -5
  19. data/lib/bitcoin/key_path.rb +12 -5
  20. data/lib/bitcoin/message.rb +7 -0
  21. data/lib/bitcoin/message/base.rb +1 -0
  22. data/lib/bitcoin/message/cf_parser.rb +16 -0
  23. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  24. data/lib/bitcoin/message/cfheaders.rb +40 -0
  25. data/lib/bitcoin/message/cfilter.rb +35 -0
  26. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  27. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  28. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  29. data/lib/bitcoin/message/network_addr.rb +31 -12
  30. data/lib/bitcoin/message/version.rb +14 -22
  31. data/lib/bitcoin/mnemonic.rb +5 -5
  32. data/lib/bitcoin/network/peer.rb +12 -11
  33. data/lib/bitcoin/network/peer_discovery.rb +3 -1
  34. data/lib/bitcoin/node/cli.rb +14 -10
  35. data/lib/bitcoin/node/spv.rb +1 -1
  36. data/lib/bitcoin/out_point.rb +14 -7
  37. data/lib/bitcoin/payment_code.rb +92 -0
  38. data/lib/bitcoin/psbt.rb +3 -1
  39. data/lib/bitcoin/psbt/input.rb +7 -16
  40. data/lib/bitcoin/psbt/tx.rb +18 -12
  41. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  42. data/lib/bitcoin/rpc/request_handler.rb +3 -3
  43. data/lib/bitcoin/script/script.rb +18 -10
  44. data/lib/bitcoin/script/script_interpreter.rb +3 -5
  45. data/lib/bitcoin/secp256k1.rb +1 -0
  46. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  47. data/lib/bitcoin/secp256k1/ruby.rb +4 -35
  48. data/lib/bitcoin/slip39.rb +93 -0
  49. data/lib/bitcoin/slip39/share.rb +122 -0
  50. data/lib/bitcoin/slip39/sss.rb +245 -0
  51. data/lib/bitcoin/slip39/wordlist/english.txt +1024 -0
  52. data/lib/bitcoin/store.rb +2 -1
  53. data/lib/bitcoin/store/chain_entry.rb +1 -0
  54. data/lib/bitcoin/store/db/level_db.rb +2 -2
  55. data/lib/bitcoin/store/utxo_db.rb +226 -0
  56. data/lib/bitcoin/tx.rb +6 -10
  57. data/lib/bitcoin/tx_in.rb +4 -5
  58. data/lib/bitcoin/util.rb +29 -1
  59. data/lib/bitcoin/version.rb +1 -1
  60. data/lib/bitcoin/wallet.rb +1 -0
  61. data/lib/bitcoin/wallet/account.rb +1 -0
  62. data/lib/bitcoin/wallet/base.rb +3 -3
  63. data/lib/bitcoin/wallet/db.rb +1 -1
  64. data/lib/bitcoin/wallet/master_key.rb +1 -0
  65. data/lib/bitcoin/wallet/utxo.rb +37 -0
  66. metadata +45 -26
@@ -0,0 +1,5 @@
1
+ module Bitcoin
2
+ module Ext
3
+ autoload :JsonParser, 'bitcoin/ext/json_parser'
4
+ end
5
+ end
@@ -0,0 +1,46 @@
1
+ require 'json/pure'
2
+
3
+ module Bitcoin
4
+ module Ext
5
+ # Extension of JSON::Pure::Parser.
6
+ # This class convert Float value to String value.
7
+ class JsonParser < JSON::Pure::Parser
8
+
9
+ def parse_value
10
+ case
11
+ when scan(FLOAT)
12
+ self[1].to_s
13
+ when scan(INTEGER)
14
+ Integer(self[1])
15
+ when scan(TRUE)
16
+ true
17
+ when scan(FALSE)
18
+ false
19
+ when scan(NULL)
20
+ nil
21
+ when !UNPARSED.equal?(string = parse_string)
22
+ string
23
+ when scan(ARRAY_OPEN)
24
+ @current_nesting += 1
25
+ ary = parse_array
26
+ @current_nesting -= 1
27
+ ary
28
+ when scan(OBJECT_OPEN)
29
+ @current_nesting += 1
30
+ obj = parse_object
31
+ @current_nesting -= 1
32
+ obj
33
+ when @allow_nan && scan(NAN)
34
+ NaN
35
+ when @allow_nan && scan(INFINITY)
36
+ Infinity
37
+ when @allow_nan && scan(MINUS_INFINITY)
38
+ MinusInfinity
39
+ else
40
+ UNPARSED
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -6,6 +6,11 @@ module Bitcoin
6
6
  # BIP32 Extended private key
7
7
  class ExtKey
8
8
 
9
+ include Bitcoin::HexConverter
10
+
11
+ MAX_DEPTH = 255
12
+ MASTER_FINGERPRINT = '00000000'
13
+
9
14
  attr_accessor :ver
10
15
  attr_accessor :depth
11
16
  attr_accessor :number
@@ -18,7 +23,7 @@ module Bitcoin
18
23
  def self.generate_master(seed)
19
24
  ext_key = ExtKey.new
20
25
  ext_key.depth = ext_key.number = 0
21
- ext_key.parent_fingerprint = '00000000'
26
+ ext_key.parent_fingerprint = MASTER_FINGERPRINT
22
27
  l = Bitcoin.hmac_sha512('Bitcoin seed', seed.htb)
23
28
  left = l[0..31].bth.to_i(16)
24
29
  raise 'invalid key' if left >= CURVE_ORDER || left == 0
@@ -47,7 +52,7 @@ module Bitcoin
47
52
 
48
53
  # Base58 encoded extended private key
49
54
  def to_base58
50
- h = to_payload.bth
55
+ h = to_hex
51
56
  hex = h + Bitcoin.calc_checksum(h)
52
57
  Base58.encode(hex)
53
58
  end
@@ -94,6 +99,7 @@ module Bitcoin
94
99
  number += Bitcoin::HARDENED_THRESHOLD if harden
95
100
  new_key = ExtKey.new
96
101
  new_key.depth = depth + 1
102
+ raise IndexError, 'Depth over 255.' if new_key.depth > MAX_DEPTH
97
103
  new_key.number = number
98
104
  new_key.parent_fingerprint = fingerprint
99
105
  if number > (Bitcoin::HARDENED_THRESHOLD - 1)
@@ -143,6 +149,9 @@ module Bitcoin
143
149
  raise 'An unsupported version byte was specified.' unless ExtKey.support_version?(ext_key.ver)
144
150
  ext_key.depth = buf.read(1).unpack('C').first
145
151
  ext_key.parent_fingerprint = buf.read(4).bth
152
+ if ext_key.depth == 0
153
+ raise ArgumentError, 'Invalid parent fingerprint.' unless ext_key.parent_fingerprint == MASTER_FINGERPRINT
154
+ end
146
155
  ext_key.number = buf.read(4).unpack('N').first
147
156
  ext_key.chain_code = buf.read(32)
148
157
  buf.read(1) # 0x00
@@ -191,6 +200,8 @@ module Bitcoin
191
200
  # BIP-32 Extended public key
192
201
  class ExtPubkey
193
202
 
203
+ include Bitcoin::HexConverter
204
+
194
205
  attr_accessor :ver
195
206
  attr_accessor :depth
196
207
  attr_accessor :number
@@ -242,7 +253,7 @@ module Bitcoin
242
253
 
243
254
  # Base58 encoded extended pubkey
244
255
  def to_base58
245
- h = to_payload.bth
256
+ h = to_hex
246
257
  hex = h + Bitcoin.calc_checksum(h)
247
258
  Base58.encode(hex)
248
259
  end
@@ -256,6 +267,7 @@ module Bitcoin
256
267
  def derive(number)
257
268
  new_key = ExtPubkey.new
258
269
  new_key.depth = depth + 1
270
+ raise IndexError, 'Depth over 255.' if new_key.depth > Bitcoin::ExtKey::MAX_DEPTH
259
271
  new_key.number = number
260
272
  new_key.parent_fingerprint = fingerprint
261
273
  raise 'hardened key is not support' if number > (Bitcoin::HARDENED_THRESHOLD - 1)
@@ -265,7 +277,7 @@ module Bitcoin
265
277
  raise 'invalid key' if left >= CURVE_ORDER
266
278
  p1 = Bitcoin::Secp256k1::GROUP.generator.multiply_by_scalar(left)
267
279
  p2 = Bitcoin::Key.new(pubkey: pubkey, key_type: key_type).to_point
268
- new_key.pubkey = ECDSA::Format::PointOctetString.encode(p1 + p2, compression: true).bth
280
+ new_key.pubkey = (p1 + p2).to_hex
269
281
  new_key.chain_code = l[32..-1]
270
282
  new_key.ver = version
271
283
  new_key
@@ -301,6 +313,9 @@ module Bitcoin
301
313
  raise 'An unsupported version byte was specified.' unless ExtPubkey.support_version?(ext_pubkey.ver)
302
314
  ext_pubkey.depth = buf.read(1).unpack('C').first
303
315
  ext_pubkey.parent_fingerprint = buf.read(4).bth
316
+ if ext_pubkey.depth == 0
317
+ raise ArgumentError, 'Invalid parent fingerprint.' unless ext_pubkey.parent_fingerprint == ExtKey::MASTER_FINGERPRINT
318
+ end
304
319
  ext_pubkey.number = buf.read(4).unpack('N').first
305
320
  ext_pubkey.chain_code = buf.read(32)
306
321
  ext_pubkey.pubkey = buf.read(33).bth
@@ -125,19 +125,19 @@ module Bitcoin
125
125
  # get pay to pubkey hash address
126
126
  # @deprecated
127
127
  def to_p2pkh
128
- Bitcoin::Script.to_p2pkh(hash160).addresses.first
128
+ Bitcoin::Script.to_p2pkh(hash160).to_addr
129
129
  end
130
130
 
131
131
  # get pay to witness pubkey hash address
132
132
  # @deprecated
133
133
  def to_p2wpkh
134
- Bitcoin::Script.to_p2wpkh(hash160).addresses.first
134
+ Bitcoin::Script.to_p2wpkh(hash160).to_addr
135
135
  end
136
136
 
137
137
  # get p2wpkh address nested in p2sh.
138
138
  # @deprecated
139
139
  def to_nested_p2wpkh
140
- Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.addresses.first
140
+ Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.to_addr
141
141
  end
142
142
 
143
143
  def compressed?
@@ -227,8 +227,12 @@ module Bitcoin
227
227
  # fully validate whether this is a valid public key (more expensive than IsValid())
228
228
  def fully_valid_pubkey?
229
229
  return false unless valid_pubkey?
230
- point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1)
231
- ECDSA::Group::Secp256k1.valid_public_key?(point)
230
+ begin
231
+ point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1)
232
+ ECDSA::Group::Secp256k1.valid_public_key?(point)
233
+ rescue ECDSA::Format::DecodeError
234
+ false
235
+ end
232
236
  end
233
237
 
234
238
  private
@@ -4,14 +4,21 @@ module Bitcoin
4
4
  # key path convert an array of derive number
5
5
  # @param [String] path_string
6
6
  # @return [Array[Integer]] key path numbers.
7
+ # @raise [ArgumentError] if invalid +path_string+.
7
8
  def parse_key_path(path_string)
8
- path_string.split('/').map.with_index do|p, index|
9
+ paths = path_string.split('/')
10
+ raise ArgumentError, "Invalid path." if path_string.include?(" ")
11
+ raise ArgumentError, "Invalid path." unless path_string.count("/") <= paths.size
12
+ paths.map.with_index do|p, index|
9
13
  if index == 0
10
- raise ArgumentError.new("#{path_string} is invalid format.") unless p == 'm'
11
- next
14
+ next if p == 'm'
15
+ raise ArgumentError, "Invalid path." unless p == 'm'
12
16
  end
13
- raise ArgumentError.new("#{path_string} is invalid format.") unless p.delete("'") =~ /^[0-9]+$/
14
- (p[-1] == "'" ? p.delete("'").to_i + Bitcoin::HARDENED_THRESHOLD : p.to_i)
17
+ raise ArgumentError, "Invalid path." if p.count("'") > 1 || (p.count("'") == 1 && p[-1] != "'")
18
+ raise ArgumentError, "Invalid path." unless p.delete("'") =~ /^[0-9]+$/
19
+ value = (p[-1] == "'" ? p.delete("'").to_i + Bitcoin::HARDENED_THRESHOLD : p.to_i)
20
+ raise ArgumentError, "Invalid path." if value > 4294967295 # 4294967295 = 0xFFFFFFFF (uint32 max)
21
+ value
15
22
  end[1..-1]
16
23
  end
17
24
 
@@ -37,6 +37,13 @@ module Bitcoin
37
37
  autoload :BlockTransactionRequest, 'bitcoin/message/block_transaction_request'
38
38
  autoload :BlockTxn, 'bitcoin/message/block_txn'
39
39
  autoload :BlockTransactions, 'bitcoin/message/block_transactions'
40
+ autoload :GetCFilters, 'bitcoin/message/get_cfilters'
41
+ autoload :GetCFHeaders, 'bitcoin/message/get_cfheaders'
42
+ autoload :CFParser, 'bitcoin/message/cf_parser'
43
+ autoload :GetCFCheckpt, 'bitcoin/message/get_cfcheckpt'
44
+ autoload :CFCheckpt, 'bitcoin/message/cfcheckpt'
45
+ autoload :CFilter, 'bitcoin/message/cfilter'
46
+ autoload :CFHeaders, 'bitcoin/message/cfheaders'
40
47
 
41
48
  USER_AGENT = "/bitcoinrb:#{Bitcoin::VERSION}/"
42
49
 
@@ -3,6 +3,7 @@ module Bitcoin
3
3
 
4
4
  # Base message class
5
5
  class Base
6
+ include Bitcoin::HexConverter
6
7
  include Bitcoin::Util
7
8
  extend Bitcoin::Util
8
9
 
@@ -0,0 +1,16 @@
1
+ module Bitcoin
2
+ module Message
3
+ module CFParser
4
+
5
+ def parse_from_payload(payload)
6
+ type, start, hash = payload.unpack('CLH*')
7
+ self.new(type, start, hash)
8
+ end
9
+
10
+ def to_payload
11
+ [filter_type, start_height, stop_hash].pack('CLH*')
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,36 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # cfcheckpt message for BIP-157
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#cfcheckpt
6
+ class CFCheckpt < Base
7
+
8
+ COMMAND = 'cfcheckpt'
9
+
10
+ attr_accessor :filter_type
11
+ attr_accessor :stop_hash # little endian
12
+ attr_accessor :filter_headers # little endian
13
+
14
+ def initialize(filter_type, stop_hash, filter_headers)
15
+ @filter_type = filter_type
16
+ @stop_hash = stop_hash
17
+ @filter_headers = filter_headers
18
+ end
19
+
20
+ def self.parse_from_payload(payload)
21
+ buf = StringIO.new(payload)
22
+ type = buf.read(1).unpack('C').first
23
+ hash = buf.read(32).unpack('H*').first
24
+ count = Bitcoin.unpack_var_int_from_io(buf)
25
+ headers = count.times.map{buf.read(32).bth}
26
+ self.new(type, hash, headers)
27
+ end
28
+
29
+ def to_payload
30
+ [filter_type, stop_hash].pack('CH*') +
31
+ Bitcoin.pack_var_int(filter_headers.size) + filter_headers.map(&:htb).join
32
+ end
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,40 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # cfheaders message for BIP-157
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#cfheaders
6
+ class CFHeaders < Base
7
+
8
+ COMMAND = 'cfheaders'
9
+
10
+ attr_accessor :filter_type
11
+ attr_accessor :stop_hash # little endian
12
+ attr_accessor :prev_filter_header # little endian
13
+ attr_accessor :filter_hashes # little endian
14
+
15
+ def initialize(filter_type, stop_hash, prev_filter_header, filter_hashes)
16
+ @filter_type = filter_type
17
+ @stop_hash = stop_hash
18
+ @prev_filter_header = prev_filter_header
19
+ @filter_hashes = filter_hashes
20
+ end
21
+
22
+ def self.parse_from_payload(payload)
23
+ buf = StringIO.new(payload)
24
+ type = buf.read(1).unpack("C").first
25
+ hash = buf.read(32).bth
26
+ header = buf.read(32).bth
27
+ count = Bitcoin.unpack_var_int_from_io(buf)
28
+ hashes = count.times.map{buf.read(32).bth}
29
+ self.new(type, hash, header, hashes)
30
+ end
31
+
32
+ def to_payload
33
+ [filter_type].pack('C') + stop_hash.htb + prev_filter_header.htb +
34
+ Bitcoin.pack_var_int(filter_hashes.size) + filter_hashes.map(&:htb).join
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,35 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # cfilter message for BIP-157
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#cfilter
6
+ class CFilter < Base
7
+
8
+ COMMAND = 'cfilter'
9
+
10
+ attr_accessor :filter_type
11
+ attr_accessor :block_hash # little endian
12
+ attr_accessor :filter # little endian
13
+
14
+ def initialize(filter_type, block_hash, filter)
15
+ @filter_type = filter_type
16
+ @block_hash = block_hash
17
+ @filter = filter
18
+ end
19
+
20
+ def self.parse_from_payload(payload)
21
+ buf = StringIO.new(payload)
22
+ type = buf.read(1).unpack("C").first
23
+ hash = buf.read(32).bth
24
+ len = Bitcoin.unpack_var_int_from_io(buf)
25
+ filter = buf.read(len).bth
26
+ self.new(type, hash, filter)
27
+ end
28
+
29
+ def to_payload
30
+ [filter_type, block_hash].pack('CH*') + Bitcoin.pack_var_string(filter.htb)
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,29 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # getcfcheckpt message for BIP-157
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#getcfcheckpt
6
+ class GetCFCheckpt < Base
7
+
8
+ COMMAND = 'getcfcheckpt'
9
+
10
+ attr_accessor :filter_type
11
+ attr_accessor :stop_hash # little endian
12
+
13
+ def initialize(filter_type, stop_hash)
14
+ @filter_type = filter_type
15
+ @stop_hash = stop_hash
16
+ end
17
+
18
+ def self.parse_from_payload(payload)
19
+ type, hash = payload.unpack('CH*')
20
+ self.new(type, hash)
21
+ end
22
+
23
+ def to_payload
24
+ [filter_type, stop_hash].pack('CH*')
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # getcfheaders message for BIP-157
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#getcfheaders
6
+ class GetCFHeaders < Base
7
+ include CFParser
8
+ extend CFParser
9
+
10
+ COMMAND = 'getcfheaders'
11
+
12
+ attr_accessor :filter_type
13
+ attr_accessor :start_height
14
+ attr_accessor :stop_hash # little endian
15
+
16
+ def initialize(filter_type, start_height, stop_hash)
17
+ @filter_type = filter_type
18
+ @start_height = start_height
19
+ @stop_hash = stop_hash
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # getcfilters message for BIP-157
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#getcfilters
6
+ class GetCFilters < Base
7
+ include CFParser
8
+ extend CFParser
9
+
10
+ COMMAND = 'getcfilters'
11
+
12
+ attr_accessor :filter_type
13
+ attr_accessor :start_height
14
+ attr_accessor :stop_hash # little endian
15
+
16
+ def initialize(filter_type, start_height, stop_hash)
17
+ @filter_type = filter_type
18
+ @start_height = start_height
19
+ @stop_hash = stop_hash
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end