bitcoinrb 1.12.1 → 1.14.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/bitcoinrb.gemspec +2 -2
  3. data/lib/bitcoin/base58.rb +15 -3
  4. data/lib/bitcoin/bip321_uri.rb +3 -6
  5. data/lib/bitcoin/bip324/fs_chacha20.rb +1 -1
  6. data/lib/bitcoin/bip324/fs_chacha_poly1305.rb +1 -0
  7. data/lib/bitcoin/bip324.rb +6 -1
  8. data/lib/bitcoin/block.rb +3 -3
  9. data/lib/bitcoin/descriptor/checksum.rb +6 -2
  10. data/lib/bitcoin/ext_key.rb +8 -4
  11. data/lib/bitcoin/key.rb +2 -2
  12. data/lib/bitcoin/message/base.rb +7 -1
  13. data/lib/bitcoin/message/cf_parser.rb +2 -2
  14. data/lib/bitcoin/message/fee_filter.rb +2 -2
  15. data/lib/bitcoin/message/filter_load.rb +10 -0
  16. data/lib/bitcoin/message/header_and_short_ids.rb +4 -4
  17. data/lib/bitcoin/message/inventory.rb +1 -1
  18. data/lib/bitcoin/message/merkle_block.rb +6 -0
  19. data/lib/bitcoin/message/network_addr.rb +7 -3
  20. data/lib/bitcoin/message/ping.rb +2 -2
  21. data/lib/bitcoin/message/pong.rb +2 -2
  22. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  23. data/lib/bitcoin/message/version.rb +3 -3
  24. data/lib/bitcoin/message.rb +15 -2
  25. data/lib/bitcoin/mnemonic.rb +9 -4
  26. data/lib/bitcoin/partial_tree.rb +25 -0
  27. data/lib/bitcoin/psbt/input.rb +58 -33
  28. data/lib/bitcoin/psbt/key_origin_info.rb +2 -2
  29. data/lib/bitcoin/psbt/output.rb +9 -5
  30. data/lib/bitcoin/psbt/tx.rb +22 -15
  31. data/lib/bitcoin/psbt.rb +1 -1
  32. data/lib/bitcoin/rpc/bitcoin_core_client.rb +3 -2
  33. data/lib/bitcoin/script/script.rb +15 -5
  34. data/lib/bitcoin/script/script_interpreter.rb +1 -1
  35. data/lib/bitcoin/script/tx_checker.rb +7 -2
  36. data/lib/bitcoin/secp256k1/native.rb +50 -1
  37. data/lib/bitcoin/secp256k1/rfc6979.rb +6 -3
  38. data/lib/bitcoin/secp256k1/ruby.rb +115 -2
  39. data/lib/bitcoin/sighash_generator.rb +8 -2
  40. data/lib/bitcoin/silent_payment.rb +56 -90
  41. data/lib/bitcoin/taproot/control_block.rb +1 -0
  42. data/lib/bitcoin/taproot/custom_depth_builder.rb +2 -2
  43. data/lib/bitcoin/taproot/simple_builder.rb +9 -7
  44. data/lib/bitcoin/taproot.rb +6 -1
  45. data/lib/bitcoin/tx.rb +8 -6
  46. data/lib/bitcoin/tx_out.rb +2 -2
  47. data/lib/bitcoin/util.rb +4 -4
  48. data/lib/bitcoin/version.rb +1 -1
  49. data/lib/bitcoin/wallet/account.rb +2 -2
  50. data/lib/bitcoin/wallet/db.rb +3 -3
  51. data/lib/bitcoin/wallet/master_key.rb +83 -24
  52. data/lib/bitcoin/wallet/utxo.rb +2 -2
  53. data/lib/bitcoin.rb +1 -0
  54. metadata +5 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e60d83b48a9f7189cd07c75ee61749ab50a83895f86dd97c7d7fbbd568234067
4
- data.tar.gz: 04e39f6f592379617273944009f78d224cf181f1877de15a285665e9b25602b3
3
+ metadata.gz: 360a1a8533df9b68228d18279909e7bc5f45d4d5679caa1a02cda75e0a9140d0
4
+ data.tar.gz: ff86fcec32b3fe1394eea043080aaedafee61e724ec689cde5efdd7b9ca5c307
5
5
  SHA512:
6
- metadata.gz: 13046ad4ba44662c2689d13dec7d51bc88e2d80cccaedc0048ee7484136e5cf04600161b5b7ae99faf4e1d7081fb845b403ebc3defb9cf92e46847e500befd65
7
- data.tar.gz: 12c68844e79ca382dc774d6d4d6cdad8bbd33064a315098e0a31eecb88ac1369c2bd4ff6b39da49d3d5b2eae9750beff11e5761dbbdbe6221c3ef1100cd8cde2
6
+ metadata.gz: 9aeb17cc9dc42ce33c4c31ed1f6424569d5c0f4f20627015c930d11e5ba2bf9c15f43cd20a666257caba689ffc0010b08b5855c13a3cf61a724c05fe8bcd383d
7
+ data.tar.gz: 14848d9c37a69ec5b0d2f993ce92c3cad3bdfc6275151789ff768ad5989d101f9c5545f1dc668d372f847b574fdc60d10e5f90b81b377b23ac20bfb3705b0a3a
data/bitcoinrb.gemspec CHANGED
@@ -27,8 +27,8 @@ Gem::Specification.new do |spec|
27
27
  spec.add_runtime_dependency 'bip-schnorr', '>= 0.7.0'
28
28
  spec.add_runtime_dependency 'base32', '>= 0.3.4'
29
29
  spec.add_runtime_dependency 'base64', '~> 0.2.0'
30
- spec.add_runtime_dependency 'secp256k1rb', '0.1.1'
30
+ spec.add_runtime_dependency 'secp256k1rb', '0.8.0'
31
31
  spec.add_runtime_dependency 'logger'
32
- spec.add_runtime_dependency 'merkle', '0.3.0'
32
+ spec.add_runtime_dependency 'merkle', '1.0.0'
33
33
  spec.add_runtime_dependency 'dnsruby', '1.73.0'
34
34
  end
@@ -8,6 +8,10 @@ module Bitcoin
8
8
  ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
9
9
  SIZE = ALPHABET.size
10
10
 
11
+ # Upper bound for #decode. Decoding is quadratic in the length of the input, and the
12
+ # longest value Bitcoin encodes with Base58 is an extended key, at 111 characters.
13
+ MAX_LENGTH = 256
14
+
11
15
  # encode hex value to base58 string.
12
16
  def encode(hex)
13
17
  leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2
@@ -21,11 +25,19 @@ module Bitcoin
21
25
  end
22
26
 
23
27
  # decode base58 string to hex value.
28
+ # @param [String] base58_val Base58 string.
29
+ # @return [String] Decoded value with hex format.
30
+ # @raise [ArgumentError] If +base58_val+ is longer than MAX_LENGTH or holds a character
31
+ # which is not in the alphabet.
24
32
  def decode(base58_val)
33
+ if base58_val.length > MAX_LENGTH
34
+ raise ArgumentError, "Base58 string must not be longer than #{MAX_LENGTH} characters."
35
+ end
25
36
  int_val = 0
26
- base58_val.reverse.split(//).each_with_index do |char,index|
27
- raise ArgumentError, 'Value passed not a valid Base58 String.' if (char_index = ALPHABET.index(char)).nil?
28
- int_val += char_index * (SIZE ** index)
37
+ base58_val.each_char do |char|
38
+ char_index = ALPHABET.index(char)
39
+ raise ArgumentError, 'Value passed not a valid Base58 String.' if char_index.nil?
40
+ int_val = int_val * SIZE + char_index
29
41
  end
30
42
  s = int_val.to_even_length_hex
31
43
  s = '' if s == '00'
@@ -105,7 +105,7 @@ module Bitcoin
105
105
  req_pop = true
106
106
  k = 'pop'
107
107
  end
108
- if %w[bc tb].include?(k.downcase)
108
+ if %w[bc tb bcrt].include?(k.downcase)
109
109
  raise ArgumentError, "#{k} not allowed in current network." unless Bitcoin.chain_params.bech32_hrp == k.downcase
110
110
  query_addrs << v
111
111
  nil
@@ -160,12 +160,9 @@ module Bitcoin
160
160
  all_params = base_params.merge other_params
161
161
  params = all_params.map do |k, v|
162
162
  "#{k}=#{CGI.escape(v).gsub('+', '%20')}"
163
- end.join('&')
164
- uri << "?#{params}" unless params.empty?
165
- unless query_addrs.empty?
166
- uri << '?' unless uri.include?('?')
167
- uri << query_addrs.map {|addr| "#{Bitcoin.chain_params.bech32_hrp}=#{addr}"}.join('&')
168
163
  end
164
+ params += query_addrs.map {|addr| "#{Bitcoin.chain_params.bech32_hrp}=#{addr}"}
165
+ uri << "?#{params.join('&')}" unless params.empty?
169
166
  uri
170
167
  end
171
168
  end
@@ -101,7 +101,7 @@ module Bitcoin
101
101
 
102
102
  def key_stream_bytes(n_bytes)
103
103
  while key_stream.bytesize < n_bytes
104
- nonce = [0, (chunk_counter / REKEY_INTERVAL)].pack("VQ<")
104
+ nonce = [0, (chunk_counter / rekey_interval)].pack("VQ<")
105
105
  self.key_stream << ChaCha20.block(key, nonce, block_counter)
106
106
  self.block_counter += 1
107
107
  end
@@ -65,6 +65,7 @@ module Bitcoin
65
65
  # @return [Array] [header, plaintext]
66
66
  def decrypt(aad, ciphertext)
67
67
  contents = crypt(aad, ciphertext, true)
68
+ raise Bitcoin::BIP324::Error, 'Decryption failed.' unless contents
68
69
  [contents[0], contents[1..-1]]
69
70
  end
70
71
 
@@ -10,7 +10,9 @@ module Bitcoin
10
10
 
11
11
  autoload :EllSwiftPubkey, 'bitcoin/bip324/ell_swift_pubkey'
12
12
  autoload :Cipher, 'bitcoin/bip324/cipher'
13
+ autoload :ChaCha20, 'bitcoin/bip324/fs_chacha20'
13
14
  autoload :FSChaCha20, 'bitcoin/bip324/fs_chacha20'
15
+ autoload :Poly1305, 'bitcoin/bip324/fs_chacha_poly1305'
14
16
  autoload :FSChaCha20Poly1305, 'bitcoin/bip324/fs_chacha_poly1305'
15
17
 
16
18
  FIELD_SIZE = 2**256 - 2**32 - 977
@@ -89,13 +91,16 @@ module Bitcoin
89
91
  ECDSA::Format::IntegerOctetString.encode(result, 32).bth
90
92
  end
91
93
 
94
+ # Number of cases xswiftec_inv selects between. It reads c as 3 bits, c & 1, c & 2 and c & 4.
95
+ CASE_COUNT = 8
96
+
92
97
  # Given a field element X on the curve, find (u, t) that encode them.
93
98
  # @param [String] x coordinate with hex format.
94
99
  # @return [String] ElligatorSwift public key with hex format.
95
100
  def xelligatorswift(x)
96
101
  loop do
97
102
  u = SecureRandom.random_number(1..ECDSA::Group::Secp256k1.order).to_s(16)
98
- c = SecureRandom.random_number(9)
103
+ c = SecureRandom.random_number(CASE_COUNT)
99
104
  t = xswiftec_inv(x, u, c)
100
105
  unless t.nil?
101
106
  return (ECDSA::Format::IntegerOctetString.encode(u.hex, 32) +
data/lib/bitcoin/block.rb CHANGED
@@ -28,7 +28,7 @@ module Bitcoin
28
28
  header = BlockHeader.new(
29
29
  version,
30
30
  '00' * 32,
31
- Merkle::BinaryTree.new(config: Merkle::Config.bitcoin, leaves: [coinbase.txid]).compute_root.rhex,
31
+ Merkle::BinaryTree.new(config: Merkle::Config.bitcoin(element_encoding: :hex), leaves: [coinbase.txid]).compute_root.rhex,
32
32
  time,
33
33
  bits,
34
34
  nonce
@@ -72,7 +72,7 @@ module Bitcoin
72
72
 
73
73
  # calculate merkle root from tx list.
74
74
  def calculate_merkle_root
75
- tree = Merkle::BinaryTree.new(config: Merkle::Config.bitcoin, leaves: transactions.map(&:tx_hash))
75
+ tree = Merkle::BinaryTree.new(config: Merkle::Config.bitcoin(element_encoding: :hex), leaves: transactions.map(&:tx_hash))
76
76
  tree.compute_root
77
77
  end
78
78
 
@@ -86,7 +86,7 @@ module Bitcoin
86
86
  witness_hashes = [COINBASE_WTXID]
87
87
  witness_hashes += (transactions[1..-1].map(&:witness_hash))
88
88
  reserved_value = transactions[0].inputs[0].script_witness.stack.map(&:bth).join
89
- tree = Merkle::BinaryTree.new(config: Merkle::Config.bitcoin, leaves: witness_hashes)
89
+ tree = Merkle::BinaryTree.new(config: Merkle::Config.bitcoin(element_encoding: :hex), leaves: witness_hashes)
90
90
  root_hash = tree.compute_root
91
91
  Bitcoin.double_sha256([root_hash + reserved_value].pack('H*')).bth
92
92
  end
@@ -18,7 +18,9 @@ module Bitcoin
18
18
  s[-8..-1].each_char do |c|
19
19
  return false unless CHECKSUM_CHARSET.include?(c)
20
20
  end
21
- symbols = descsum_expand(s[0...-9]) + s[-8..-1].each_char.map{|c|CHECKSUM_CHARSET.index(c)}
21
+ expanded = descsum_expand(s[0...-9])
22
+ return false unless expanded
23
+ symbols = expanded + s[-8..-1].each_char.map{|c|CHECKSUM_CHARSET.index(c)}
22
24
  descsum_polymod(symbols) == 1
23
25
  end
24
26
 
@@ -26,7 +28,9 @@ module Bitcoin
26
28
  # @param [String] s Descriptor string without checksum.
27
29
  # @return [String] Descriptor string with checksum.
28
30
  def descsum_create(s)
29
- symbols = descsum_expand(s) + [0, 0, 0, 0, 0, 0, 0, 0]
31
+ expanded = descsum_expand(s)
32
+ raise ArgumentError, 'Descriptor contains a character which is not in the input charset.' unless expanded
33
+ symbols = expanded + [0, 0, 0, 0, 0, 0, 0, 0]
30
34
  checksum = descsum_polymod(symbols) ^ 1
31
35
  result = 8.times.map do |i|
32
36
  CHECKSUM_CHARSET[(checksum >> (5 * (7 - i))) & 31]
@@ -114,14 +114,16 @@ module Bitcoin
114
114
  child_priv = ECDSA::Format::IntegerOctetString.encode(child_priv, 32)
115
115
  new_key.key = Bitcoin::Key.new(priv_key: child_priv.bth, key_type: key_type)
116
116
  new_key.chain_code = l[32..-1]
117
- new_key.ver = version
117
+ # A depth 1 key derives its version bytes from the purpose, see #version.
118
+ new_key.ver = new_key.depth == 1 ? nil : version
118
119
  new_key
119
120
  end
120
121
 
121
122
  # get version bytes using serialization format
122
123
  def version
124
+ return ver if ver
123
125
  return ExtKey.version_from_purpose(number) if depth == 1
124
- ver ? ver : Bitcoin.chain_params.extended_privkey_version
126
+ Bitcoin.chain_params.extended_privkey_version
125
127
  end
126
128
 
127
129
  # get key type defined by BIP-178 using version.
@@ -286,14 +288,16 @@ module Bitcoin
286
288
  p2 = Bitcoin::Key.new(pubkey: pubkey, key_type: key_type).to_point
287
289
  new_key.pubkey = (p1 + p2).to_hex
288
290
  new_key.chain_code = l[32..-1]
289
- new_key.ver = version
291
+ # A depth 1 key derives its version bytes from the purpose, see #version.
292
+ new_key.ver = new_key.depth == 1 ? nil : version
290
293
  new_key
291
294
  end
292
295
 
293
296
  # get version bytes using serialization format
294
297
  def version
298
+ return ver if ver
295
299
  return ExtPubkey.version_from_purpose(number) if depth == 1
296
- ver ? ver : Bitcoin.chain_params.extended_pubkey_version
300
+ Bitcoin.chain_params.extended_pubkey_version
297
301
  end
298
302
 
299
303
  # get key type defined by BIP-178 using version.
data/lib/bitcoin/key.rb CHANGED
@@ -120,7 +120,7 @@ module Bitcoin
120
120
  if low_r && !sig_has_low_r?(sig)
121
121
  counter = 1
122
122
  until sig_has_low_r?(sig)
123
- extra_entropy = [counter].pack('I*').bth.ljust(64, '0').htb
123
+ extra_entropy = [counter].pack('V').bth.ljust(64, '0').htb
124
124
  sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
125
125
  counter += 1
126
126
  end
@@ -170,7 +170,7 @@ module Bitcoin
170
170
  else
171
171
  false
172
172
  end
173
- rescue Exception
173
+ rescue StandardError
174
174
  false
175
175
  end
176
176
  end
@@ -32,11 +32,17 @@ module Bitcoin
32
32
  magic = buf.read(4)
33
33
  raise ArgumentError, 'Invalid magic.' unless magic == Bitcoin.chain_params.magic_head.htb
34
34
  command = buf.read(12).delete("\x00")
35
+ # The length is announced by the peer, so it is checked before it is used to read.
35
36
  length = buf.read(4).unpack1('V')
37
+ if length > MAX_PROTOCOL_MESSAGE_LENGTH
38
+ raise ArgumentError, "Payload must not be longer than #{MAX_PROTOCOL_MESSAGE_LENGTH} bytes."
39
+ end
36
40
  checksum = buf.read(4)
41
+ # read returns "" for a length of 0 and nil once the buffer is exhausted.
37
42
  payload = buf.read(length)
43
+ raise ArgumentError, 'Payload is shorter than the announced length.' unless payload&.bytesize == length
38
44
  raise ArgumentError, 'Checksum do not match.' unless checksum == Bitcoin.double_sha256(payload)[0...4]
39
- Bitcoin::Message.decode(command, payload&.bth)
45
+ Bitcoin::Message.decode(command, payload.bth)
40
46
  end
41
47
 
42
48
  end
@@ -3,12 +3,12 @@ module Bitcoin
3
3
  module CFParser
4
4
 
5
5
  def parse_from_payload(payload)
6
- type, start, hash = payload.unpack('CLH*')
6
+ type, start, hash = payload.unpack('CVH*')
7
7
  self.new(type, start, hash)
8
8
  end
9
9
 
10
10
  def to_payload
11
- [filter_type, start_height, stop_hash].pack('CLH*')
11
+ [filter_type, start_height, stop_hash].pack('CVH*')
12
12
  end
13
13
 
14
14
  end
@@ -15,11 +15,11 @@ module Bitcoin
15
15
  end
16
16
 
17
17
  def self.parse_from_payload(payload)
18
- new(payload.unpack1('Q'))
18
+ new(payload.unpack1('Q<'))
19
19
  end
20
20
 
21
21
  def to_payload
22
- [fee_rate].pack('Q')
22
+ [fee_rate].pack('Q<')
23
23
  end
24
24
 
25
25
  end
@@ -22,8 +22,18 @@ module Bitcoin
22
22
  def self.parse_from_payload(payload)
23
23
  buf = StringIO.new(payload)
24
24
  filter_count = Bitcoin.unpack_var_int_from_io(buf)
25
+ # A filterload message is received from an untrusted peer, so the limits which bound
26
+ # the work BloomFilter#add and #contains? perform have to be enforced here.
27
+ if filter_count > Bitcoin::BloomFilter::MAX_BLOOM_FILTER_SIZE
28
+ raise Bitcoin::Message::Error,
29
+ "filter size must be less than or equal to #{Bitcoin::BloomFilter::MAX_BLOOM_FILTER_SIZE}."
30
+ end
25
31
  filter = buf.read(filter_count).unpack('C*')
26
32
  func_count = buf.read(4).unpack1('V')
33
+ if func_count > Bitcoin::BloomFilter::MAX_HASH_FUNCS
34
+ raise Bitcoin::Message::Error,
35
+ "hash funcs must be less than or equal to #{Bitcoin::BloomFilter::MAX_HASH_FUNCS}."
36
+ end
27
37
  tweak = buf.read(4).unpack1('V')
28
38
  flag = buf.read(1).unpack1('C')
29
39
  FilterLoad.new(Bitcoin::BloomFilter.new(filter, func_count, tweak), flag)
@@ -16,13 +16,13 @@ module Bitcoin
16
16
  @nonce = nonce
17
17
  @short_ids = short_ids
18
18
  @prefilled_txn = prefilled_txn
19
- @siphash_key = Bitcoin.sha256(header.to_payload << [nonce].pack('q*'))[0...16]
19
+ @siphash_key = Bitcoin.sha256(header.to_payload << [nonce].pack('q<*'))[0...16]
20
20
  end
21
21
 
22
22
  def self.parse_from_payload(payload)
23
23
  buf = StringIO.new(payload)
24
24
  header = Bitcoin::BlockHeader.parse_from_payload(buf.read(80))
25
- nonce = buf.read(8).unpack1('q*')
25
+ nonce = buf.read(8).unpack1('q<*')
26
26
  short_ids_len = Bitcoin.unpack_var_int_from_io(buf)
27
27
  short_ids = short_ids_len.times.map do
28
28
  buf.read(6).reverse.bth.to_i(16)
@@ -36,9 +36,9 @@ module Bitcoin
36
36
 
37
37
  def to_payload
38
38
  p = header.to_payload
39
- p << [nonce].pack('q*')
39
+ p << [nonce].pack('q<*')
40
40
  p << Bitcoin.pack_var_int(short_ids.size)
41
- p << short_ids.map{|id|sprintf('%12x', id).htb.reverse}.join
41
+ p << short_ids.map{|id|sprintf('%012x', id).htb.reverse}.join
42
42
  p << Bitcoin.pack_var_int(prefilled_txn.size)
43
43
  p << prefilled_txn.map(&:to_payload).join
44
44
  p
@@ -26,7 +26,7 @@ module Bitcoin
26
26
  # parse inventory payload
27
27
  def self.parse_from_payload(payload)
28
28
  raise Error, 'invalid inventory size.' if payload.bytesize != 36
29
- identifier = payload[0..4].unpack1('V')
29
+ identifier = payload[0..3].unpack1('V')
30
30
  hash = payload[4..-1].bth # internal byte order
31
31
  new(identifier, hash)
32
32
  end
@@ -28,6 +28,12 @@ module Bitcoin
28
28
  flag_count = Bitcoin.unpack_var_int_from_io(buf)
29
29
  # A sequence of bits packed eight in a byte with the least significant bit first.
30
30
  m.flags = buf.read(flag_count).bth
31
+ # Reject a tree an untrusted peer could use to exhaust memory or CPU in #partial_tree.
32
+ begin
33
+ Bitcoin::PartialTree.validate!(m.tx_count, m.hashes, Bitcoin.byte_to_bit(m.flags.htb))
34
+ rescue ArgumentError => e
35
+ raise Bitcoin::Message::Error, e.message
36
+ end
31
37
  m
32
38
  end
33
39
 
@@ -107,7 +107,7 @@ module Bitcoin
107
107
  has_time = buf.size > 26
108
108
  addr = NetworkAddr.new(time: nil)
109
109
  addr.time = buf.read(4).unpack1('V') if has_time
110
- addr.services = buf.read(8).unpack1('Q')
110
+ addr.services = buf.read(8).unpack1('Q<')
111
111
  addr.addr = IPAddr::new_ntoh(buf.read(16))
112
112
  addr.port = buf.read(2).unpack1('n')
113
113
  addr
@@ -153,7 +153,7 @@ module Bitcoin
153
153
  p = ''
154
154
  p << [time].pack('V') unless skip_time
155
155
  ip = addr.ipv4? ? addr.ipv4_mapped : addr
156
- p << [services].pack('Q') << ip.hton << [port].pack('n')
156
+ p << [services].pack('Q<') << ip.hton << [port].pack('n')
157
157
  end
158
158
 
159
159
  def v2_payload
@@ -163,18 +163,22 @@ module Bitcoin
163
163
  case net
164
164
  when NETWORK_ID[:ipv4]
165
165
  p << Bitcoin.pack_var_int(4)
166
- p << addr.to_i.to_s(16).htb
166
+ p << addr.hton
167
167
  when NETWORK_ID[:ipv6]
168
168
  p << Bitcoin.pack_var_int(16)
169
169
  p << addr.hton
170
170
  when NETWORK_ID[:tor_v2]
171
171
  p << Bitcoin.pack_var_int(10)
172
+ p << addr.htb
172
173
  when NETWORK_ID[:tor_v3]
173
174
  p << Bitcoin.pack_var_int(32)
175
+ p << addr.htb
174
176
  when NETWORK_ID[:i2p]
175
177
  p << Bitcoin.pack_var_int(32)
178
+ p << addr.htb
176
179
  when NETWORK_ID[:cjdns]
177
180
  p << Bitcoin.pack_var_int(16)
181
+ p << addr.hton
178
182
  end
179
183
  p << [port].pack('n')
180
184
  p
@@ -14,11 +14,11 @@ module Bitcoin
14
14
  end
15
15
 
16
16
  def self.parse_from_payload(payload)
17
- new(payload.unpack1('Q'))
17
+ new(payload.unpack1('Q<'))
18
18
  end
19
19
 
20
20
  def to_payload
21
- nonce ? [nonce].pack('Q') : ''
21
+ nonce ? [nonce].pack('Q<') : ''
22
22
  end
23
23
 
24
24
  # Generate pong message as a response. Return nil if the ping has no nonce
@@ -14,11 +14,11 @@ module Bitcoin
14
14
  end
15
15
 
16
16
  def self.parse_from_payload(payload)
17
- new(payload.unpack1('Q'))
17
+ new(payload.unpack1('Q<'))
18
18
  end
19
19
 
20
20
  def to_payload
21
- [nonce].pack('Q')
21
+ [nonce].pack('Q<')
22
22
  end
23
23
  end
24
24
 
@@ -22,12 +22,12 @@ module Bitcoin
22
22
  def self.parse_from_payload(payload)
23
23
  buf = StringIO.new(payload)
24
24
  mode = buf.read(1).unpack1('c')
25
- version = buf.read(8).unpack1('Q')
25
+ version = buf.read(8).unpack1('Q<')
26
26
  new(mode, version)
27
27
  end
28
28
 
29
29
  def to_payload
30
- [mode, version].pack('cQ')
30
+ [mode, version].pack('cQ<')
31
31
  end
32
32
 
33
33
  def high?
@@ -32,7 +32,7 @@ module Bitcoin
32
32
  end
33
33
 
34
34
  def self.parse_from_payload(payload)
35
- version, services, timestamp, local_addr, remote_addr, nonce, rest = payload.unpack('VQQa26a26Qa*')
35
+ version, services, timestamp, local_addr, remote_addr, nonce, rest = payload.unpack('VQ<Q<a26a26Q<a*')
36
36
  v = new
37
37
  v.version = version
38
38
  v.services = services
@@ -50,10 +50,10 @@ module Bitcoin
50
50
 
51
51
  def to_payload
52
52
  [
53
- [version, services, timestamp].pack('VQQ'),
53
+ [version, services, timestamp].pack('VQ<Q<'),
54
54
  local_addr.to_payload(true),
55
55
  remote_addr.to_payload(true),
56
- [nonce].pack('Q'),
56
+ [nonce].pack('Q<'),
57
57
  pack_var_string(user_agent),
58
58
  [start_height].pack('V'),
59
59
  pack_boolean(relay)
@@ -1,8 +1,6 @@
1
1
  module Bitcoin
2
2
  module Message
3
3
 
4
- class Error < StandardError; end
5
-
6
4
  autoload :Base, 'bitcoin/message/base'
7
5
  autoload :Inventory, 'bitcoin/message/inventory'
8
6
  autoload :InventoriesParser, 'bitcoin/message/inventories_parser'
@@ -53,6 +51,9 @@ module Bitcoin
53
51
 
54
52
  USER_AGENT = "/bitcoinrb:#{Bitcoin::VERSION}/"
55
53
 
54
+ # The maximum payload size a peer may announce in a message header.
55
+ MAX_PROTOCOL_MESSAGE_LENGTH = 4_000_000
56
+
56
57
  SERVICE_FLAGS = {
57
58
  none: 0,
58
59
  network: 1 << 0, # the node is capable of serving the block chain. It is currently set by all Bitcoin Core node, and is unset by SPV clients or other peers that just want network services but don't provide them.
@@ -107,6 +108,8 @@ module Bitcoin
107
108
  Pong.parse_from_payload(payload)
108
109
  when GetHeaders::COMMAND
109
110
  GetHeaders.parse_from_payload(payload)
111
+ when GetBlocks::COMMAND
112
+ GetBlocks.parse_from_payload(payload)
110
113
  when Headers::COMMAND
111
114
  Headers.parse_from_payload(payload)
112
115
  when Block::COMMAND
@@ -125,8 +128,18 @@ module Bitcoin
125
128
  Inv.parse_from_payload(payload)
126
129
  when MerkleBlock::COMMAND
127
130
  MerkleBlock.parse_from_payload(payload)
131
+ when FilterLoad::COMMAND
132
+ FilterLoad.parse_from_payload(payload)
133
+ when FilterAdd::COMMAND
134
+ FilterAdd.parse_from_payload(payload)
135
+ when FilterClear::COMMAND
136
+ FilterClear.new
128
137
  when CmpctBlock::COMMAND
129
138
  CmpctBlock.parse_from_payload(payload)
139
+ when GetBlockTxn::COMMAND
140
+ GetBlockTxn.parse_from_payload(payload)
141
+ when BlockTxn::COMMAND
142
+ BlockTxn.parse_from_payload(payload)
130
143
  when GetData::COMMAND
131
144
  GetData.parse_from_payload(payload)
132
145
  when GetCFHeaders::COMMAND
@@ -24,7 +24,7 @@ module Bitcoin
24
24
  def to_entropy(words)
25
25
  word_master = load_words
26
26
  mnemonic = words.map do |w|
27
- index = word_master.index(w.downcase)
27
+ index = word_master.index(normalize(w).downcase)
28
28
  raise IndexError, 'word not found in words list.' unless index
29
29
  index.to_s(2).rjust(11, '0')
30
30
  end.join
@@ -53,8 +53,8 @@ module Bitcoin
53
53
  # @return [String] seed
54
54
  def to_seed(mnemonic, passphrase: '')
55
55
  to_entropy(mnemonic)
56
- OpenSSL::PKCS5.pbkdf2_hmac(mnemonic.join(' ').downcase,
57
- 'mnemonic' + passphrase, 2048, 64, OpenSSL::Digest::SHA512.new).bth
56
+ OpenSSL::PKCS5.pbkdf2_hmac(normalize(mnemonic.join(' ')).downcase,
57
+ 'mnemonic' + normalize(passphrase), 2048, 64, OpenSSL::Digest::SHA512.new).bth
58
58
  end
59
59
 
60
60
  # calculate entropy checksum
@@ -69,7 +69,12 @@ module Bitcoin
69
69
 
70
70
  # load word list contents
71
71
  def load_words
72
- File.readlines("#{WORD_DIR}/#{language}.txt").map(&:strip)
72
+ File.readlines("#{WORD_DIR}/#{language}.txt", encoding: Encoding::UTF_8).map{|w| normalize(w.strip)}
73
+ end
74
+
75
+ # BIP-39 requires that mnemonic sentences and passphrases are normalized using NFKD.
76
+ def normalize(str)
77
+ str.unicode_normalize(:nfkd)
73
78
  end
74
79
 
75
80
  end
@@ -4,6 +4,10 @@ module Bitcoin
4
4
  # For a complete Merkle tree implementation, migrate to the merkle gem.
5
5
  class PartialTree
6
6
 
7
+ # The maximum number of transactions a block can contain, so the maximum tx_count
8
+ # a valid merkleblock message can commit to.
9
+ MAX_TX_COUNT = MAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT
10
+
7
11
  attr_accessor :root
8
12
 
9
13
  def initialize(root = nil)
@@ -15,7 +19,13 @@ module Bitcoin
15
19
  end
16
20
 
17
21
  # https://bitcoin.org/en/developer-reference#creating-a-merkleblock-message
22
+ # @param [Integer] tx_count The number of transactions in the block.
23
+ # @param [Array] hashes Array of hash values with hex format.
24
+ # @param [String] flags The sequence of bits, a string of '0' and '1'.
25
+ # @return [Bitcoin::PartialTree]
26
+ # @raise [ArgumentError] If the given parameters are out of range or inconsistent.
18
27
  def self.build(tx_count, hashes, flags)
28
+ validate!(tx_count, hashes, flags)
19
29
  flags = flags.each_char.map(&:to_i)
20
30
  root = build_initial_tree( Array.new(tx_count) { Node.new })
21
31
  current_node = root
@@ -37,7 +47,22 @@ module Bitcoin
37
47
  new(root)
38
48
  end
39
49
 
50
+ # Validate the parameters of a partial merkle tree.
51
+ # A merkleblock message is received from an untrusted peer, so these bounds are required
52
+ # to stop it from making #build allocate an unbounded number of nodes or loop forever.
53
+ # See Bitcoin Core's CPartialMerkleTree::ExtractMatches.
54
+ # @raise [ArgumentError]
55
+ def self.validate!(tx_count, hashes, flags)
56
+ raise ArgumentError, 'tx_count must be greater than 0.' unless tx_count.is_a?(Integer) && tx_count > 0
57
+ raise ArgumentError, "tx_count must be less than or equal to #{MAX_TX_COUNT}." if tx_count > MAX_TX_COUNT
58
+ # There can never be more hashes provided than one for every txid.
59
+ raise ArgumentError, 'hashes must not be greater than tx_count.' if hashes.size > tx_count
60
+ # There must be at least one bit per node in the partial tree, and at least one node per hash.
61
+ raise ArgumentError, 'flags must have at least one bit per hash.' if flags.size < hashes.size
62
+ end
63
+
40
64
  def self.build_initial_tree(nodes)
65
+ raise ArgumentError, 'nodes must not be empty.' if nodes.empty?
41
66
  while nodes.size != 1
42
67
  nodes = nodes.each_slice(2).map { |m|
43
68
  parent = Node.new