bitcoinrb 0.3.2 → 0.8.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +37 -0
  3. data/.rspec_parallel +2 -0
  4. data/.ruby-version +1 -1
  5. data/README.md +17 -6
  6. data/bitcoinrb.gemspec +9 -8
  7. data/exe/bitcoinrbd +5 -0
  8. data/lib/bitcoin.rb +37 -19
  9. data/lib/bitcoin/bip85_entropy.rb +111 -0
  10. data/lib/bitcoin/block_filter.rb +14 -0
  11. data/lib/bitcoin/block_header.rb +2 -0
  12. data/lib/bitcoin/chain_params.rb +9 -8
  13. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  14. data/lib/bitcoin/chainparams/signet.yml +39 -0
  15. data/lib/bitcoin/chainparams/testnet.yml +1 -1
  16. data/lib/bitcoin/constants.rb +44 -10
  17. data/lib/bitcoin/descriptor.rb +1 -1
  18. data/lib/bitcoin/errors.rb +19 -0
  19. data/lib/bitcoin/ext.rb +6 -0
  20. data/lib/bitcoin/ext/array_ext.rb +22 -0
  21. data/lib/bitcoin/ext/ecdsa.rb +36 -0
  22. data/lib/bitcoin/ext/json_parser.rb +46 -0
  23. data/lib/bitcoin/ext_key.rb +51 -20
  24. data/lib/bitcoin/key.rb +89 -30
  25. data/lib/bitcoin/key_path.rb +12 -5
  26. data/lib/bitcoin/message.rb +79 -0
  27. data/lib/bitcoin/message/addr_v2.rb +34 -0
  28. data/lib/bitcoin/message/base.rb +17 -0
  29. data/lib/bitcoin/message/cf_parser.rb +16 -0
  30. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  31. data/lib/bitcoin/message/cfheaders.rb +40 -0
  32. data/lib/bitcoin/message/cfilter.rb +35 -0
  33. data/lib/bitcoin/message/fee_filter.rb +1 -1
  34. data/lib/bitcoin/message/filter_load.rb +3 -3
  35. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  36. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  37. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  38. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  39. data/lib/bitcoin/message/inventory.rb +1 -1
  40. data/lib/bitcoin/message/merkle_block.rb +1 -1
  41. data/lib/bitcoin/message/network_addr.rb +141 -18
  42. data/lib/bitcoin/message/ping.rb +1 -1
  43. data/lib/bitcoin/message/pong.rb +1 -1
  44. data/lib/bitcoin/message/send_addr_v2.rb +13 -0
  45. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  46. data/lib/bitcoin/message/tx.rb +1 -1
  47. data/lib/bitcoin/message/version.rb +7 -0
  48. data/lib/bitcoin/message_sign.rb +47 -0
  49. data/lib/bitcoin/mnemonic.rb +7 -7
  50. data/lib/bitcoin/network/peer.rb +9 -4
  51. data/lib/bitcoin/network/peer_discovery.rb +1 -1
  52. data/lib/bitcoin/node/cli.rb +14 -10
  53. data/lib/bitcoin/node/configuration.rb +3 -1
  54. data/lib/bitcoin/node/spv.rb +9 -1
  55. data/lib/bitcoin/opcodes.rb +14 -1
  56. data/lib/bitcoin/out_point.rb +2 -0
  57. data/lib/bitcoin/payment_code.rb +92 -0
  58. data/lib/bitcoin/payments/payment.pb.rb +1 -1
  59. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  60. data/lib/bitcoin/psbt/input.rb +9 -18
  61. data/lib/bitcoin/psbt/output.rb +1 -1
  62. data/lib/bitcoin/psbt/tx.rb +12 -17
  63. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  64. data/lib/bitcoin/rpc/request_handler.rb +5 -5
  65. data/lib/bitcoin/script/script.rb +96 -39
  66. data/lib/bitcoin/script/script_error.rb +27 -1
  67. data/lib/bitcoin/script/script_interpreter.rb +166 -66
  68. data/lib/bitcoin/script/tx_checker.rb +62 -14
  69. data/lib/bitcoin/secp256k1.rb +1 -0
  70. data/lib/bitcoin/secp256k1/native.rb +184 -17
  71. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  72. data/lib/bitcoin/secp256k1/ruby.rb +112 -56
  73. data/lib/bitcoin/sighash_generator.rb +156 -0
  74. data/lib/bitcoin/store.rb +1 -0
  75. data/lib/bitcoin/store/chain_entry.rb +1 -0
  76. data/lib/bitcoin/store/utxo_db.rb +226 -0
  77. data/lib/bitcoin/taproot.rb +9 -0
  78. data/lib/bitcoin/taproot/leaf_node.rb +23 -0
  79. data/lib/bitcoin/taproot/simple_builder.rb +139 -0
  80. data/lib/bitcoin/tx.rb +34 -104
  81. data/lib/bitcoin/tx_in.rb +4 -5
  82. data/lib/bitcoin/tx_out.rb +2 -3
  83. data/lib/bitcoin/util.rb +22 -6
  84. data/lib/bitcoin/version.rb +1 -1
  85. data/lib/bitcoin/wallet.rb +1 -0
  86. data/lib/bitcoin/wallet/account.rb +2 -1
  87. data/lib/bitcoin/wallet/base.rb +2 -2
  88. data/lib/bitcoin/wallet/master_key.rb +1 -0
  89. data/lib/bitcoin/wallet/utxo.rb +37 -0
  90. metadata +86 -32
  91. data/.travis.yml +0 -11
data/lib/bitcoin/key.rb CHANGED
@@ -10,6 +10,7 @@ module Bitcoin
10
10
  COMPRESSED_PUBLIC_KEY_SIZE = 33
11
11
  SIGNATURE_SIZE = 72
12
12
  COMPACT_SIGNATURE_SIZE = 65
13
+ COMPACT_SIG_HEADER_BYTE = 0x1b
13
14
 
14
15
  attr_accessor :priv_key
15
16
  attr_accessor :pubkey
@@ -28,7 +29,7 @@ module Bitcoin
28
29
  # @param [Integer] key_type a key type which determine address type.
29
30
  # @param [Boolean] compressed [Deprecated] whether public key is compressed.
30
31
  # @return [Bitcoin::Key] a key object.
31
- def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true)
32
+ def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false)
32
33
  puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil?
33
34
  if key_type
34
35
  @key_type = key_type
@@ -39,13 +40,14 @@ module Bitcoin
39
40
  @secp256k1_module = Bitcoin.secp_impl
40
41
  @priv_key = priv_key
41
42
  if @priv_key
42
- raise ArgumentError, 'private key is not on curve' unless validate_private_key_range(@priv_key)
43
+ raise ArgumentError, Errors::Messages::INVALID_PRIV_KEY unless validate_private_key_range(@priv_key)
43
44
  end
44
45
  if pubkey
45
46
  @pubkey = pubkey
46
47
  else
47
48
  @pubkey = generate_pubkey(priv_key, compressed: compressed) if priv_key
48
49
  end
50
+ raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless fully_valid_pubkey?(allow_hybrid)
49
51
  end
50
52
 
51
53
  # generate key pair
@@ -63,9 +65,9 @@ module Bitcoin
63
65
  data = hex[2...-8].htb
64
66
  checksum = hex[-8..-1]
65
67
  raise ArgumentError, 'invalid version' unless version == Bitcoin.chain_params.privkey_version
66
- raise ArgumentError, 'invalid checksum' unless Bitcoin.calc_checksum(version + data.bth) == checksum
68
+ raise ArgumentError, Errors::Messages::INVALID_CHECKSUM unless Bitcoin.calc_checksum(version + data.bth) == checksum
67
69
  key_len = data.bytesize
68
- if key_len == COMPRESSED_PUBLIC_KEY_SIZE && data[-1].unpack('C').first == 1
70
+ if key_len == COMPRESSED_PUBLIC_KEY_SIZE && data[-1].unpack1('C') == 1
69
71
  key_type = TYPES[:compressed]
70
72
  data = data[0..-2]
71
73
  elsif key_len == 32
@@ -76,6 +78,23 @@ module Bitcoin
76
78
  new(priv_key: data.bth, key_type: key_type)
77
79
  end
78
80
 
81
+ # Generate from xonly public key.
82
+ # @param [String] xonly_pubkey xonly public key with hex format.
83
+ # @return [Bitcoin::Key] key object has public key.
84
+ def self.from_xonly_pubkey(xonly_pubkey)
85
+ raise ArgumentError, 'xonly_pubkey must be 32 bytes' unless xonly_pubkey.htb.bytesize == 32
86
+ Bitcoin::Key.new(pubkey: "02#{xonly_pubkey}", key_type: TYPES[:compressed])
87
+ end
88
+
89
+ # Generate from public key point.
90
+ # @param [ECDSA::Point] point Public key point.
91
+ # @param [Boolean] compressed whether compressed or not.
92
+ # @return [Bitcoin::Key]
93
+ def self.from_point(point, compressed: true)
94
+ pubkey = ECDSA::Format::PointOctetString.encode(point, compression: compressed).bth
95
+ Bitcoin::Key.new(pubkey: pubkey, key_type: TYPES[:compressed])
96
+ end
97
+
79
98
  # export private key with wif format
80
99
  def to_wif
81
100
  version = Bitcoin.chain_params.privkey_version
@@ -88,30 +107,69 @@ module Bitcoin
88
107
  # sign +data+ with private key
89
108
  # @param [String] data a data to be signed with binary format
90
109
  # @param [Boolean] low_r flag to apply low-R.
91
- # @param [String] extra_entropy the extra entropy for rfc6979.
110
+ # @param [String] extra_entropy the extra entropy with binary format for rfc6979.
111
+ # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
92
112
  # @return [String] signature data with binary format
93
- def sign(data, low_r = true, extra_entropy = nil)
94
- sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
95
- if low_r && !sig_has_low_r?(sig)
96
- counter = 1
97
- until sig_has_low_r?(sig)
98
- extra_entropy = [counter].pack('I*').bth.ljust(64, '0').htb
99
- sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
100
- counter += 1
113
+ def sign(data, low_r = true, extra_entropy = nil, algo: :ecdsa)
114
+ case algo
115
+ when :ecdsa
116
+ sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
117
+ if low_r && !sig_has_low_r?(sig)
118
+ counter = 1
119
+ until sig_has_low_r?(sig)
120
+ extra_entropy = [counter].pack('I*').bth.ljust(64, '0').htb
121
+ sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
122
+ counter += 1
123
+ end
101
124
  end
125
+ sig
126
+ when :schnorr
127
+ secp256k1_module.sign_data(data, priv_key, extra_entropy, algo: :schnorr)
128
+ else
129
+ raise ArgumentError "Unsupported algo specified: #{algo}"
102
130
  end
103
- sig
131
+ end
132
+
133
+ # Sign compact signature.
134
+ # @param [String] data message digest to be signed.
135
+ # @return [String] compact signature with binary format.
136
+ def sign_compact(data)
137
+ signature, rec = secp256k1_module.sign_compact(data, priv_key)
138
+ rec = Bitcoin::Key::COMPACT_SIG_HEADER_BYTE + rec + (compressed? ? 4 : 0)
139
+ [rec].pack('C') + ECDSA::Format::IntegerOctetString.encode(signature.r, 32) +
140
+ ECDSA::Format::IntegerOctetString.encode(signature.s, 32)
141
+ end
142
+
143
+ # Recover public key from compact signature.
144
+ # @param [String] data message digest using signature.
145
+ # @param [String] signature signature with binary format.
146
+ # @return [Bitcoin::Key] Recovered public key.
147
+ def self.recover_compact(data, signature)
148
+ rec_id = signature.unpack1('C')
149
+ rec = rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE
150
+ raise ArgumentError, 'Invalid signature parameter' if rec < 0 || rec > 15
151
+ rec = rec & 3
152
+ compressed = (rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE) & 4 != 0
153
+ Bitcoin.secp_impl.recover_compact(data, signature, rec, compressed)
104
154
  end
105
155
 
106
156
  # verify signature using public key
107
157
  # @param [String] sig signature data with binary format
108
- # @param [String] origin original message
158
+ # @param [String] data original message
159
+ # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
109
160
  # @return [Boolean] verify result
110
- def verify(sig, origin)
161
+ def verify(sig, data, algo: :ecdsa)
111
162
  return false unless valid_pubkey?
112
163
  begin
113
- sig = ecdsa_signature_parse_der_lax(sig)
114
- secp256k1_module.verify_sig(origin, sig, pubkey)
164
+ case algo
165
+ when :ecdsa
166
+ sig = ecdsa_signature_parse_der_lax(sig)
167
+ secp256k1_module.verify_sig(data, sig, pubkey)
168
+ when :schnorr
169
+ secp256k1_module.verify_sig(data, sig, xonly_pubkey, algo: :schnorr)
170
+ else
171
+ false
172
+ end
115
173
  rescue Exception
116
174
  false
117
175
  end
@@ -125,19 +183,19 @@ module Bitcoin
125
183
  # get pay to pubkey hash address
126
184
  # @deprecated
127
185
  def to_p2pkh
128
- Bitcoin::Script.to_p2pkh(hash160).addresses.first
186
+ Bitcoin::Script.to_p2pkh(hash160).to_addr
129
187
  end
130
188
 
131
189
  # get pay to witness pubkey hash address
132
190
  # @deprecated
133
191
  def to_p2wpkh
134
- Bitcoin::Script.to_p2wpkh(hash160).addresses.first
192
+ Bitcoin::Script.to_p2wpkh(hash160).to_addr
135
193
  end
136
194
 
137
195
  # get p2wpkh address nested in p2sh.
138
196
  # @deprecated
139
197
  def to_nested_p2wpkh
140
- Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.addresses.first
198
+ Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.to_addr
141
199
  end
142
200
 
143
201
  def compressed?
@@ -148,10 +206,17 @@ module Bitcoin
148
206
  # @return [ECDSA::Point]
149
207
  def to_point
150
208
  p = pubkey
151
- p ||= generate_pubkey(priv_key, compressed: compressed)
209
+ p ||= generate_pubkey(priv_key, compressed: compressed?)
152
210
  ECDSA::Format::PointOctetString.decode(p.htb, Bitcoin::Secp256k1::GROUP)
153
211
  end
154
212
 
213
+ # get xonly public key (32 bytes).
214
+ # @return [String] xonly public key with hex format
215
+ def xonly_pubkey
216
+ puts "Derive a public key whose y-coordinate is different from this public key." if compressed? && pubkey[0...2] != '02'
217
+ pubkey[2..65]
218
+ end
219
+
155
220
  # check +pubkey+ (hex) is compress or uncompress pubkey.
156
221
  def self.compress_or_uncompress_pubkey?(pubkey)
157
222
  p = pubkey.htb
@@ -225,14 +290,8 @@ module Bitcoin
225
290
  end
226
291
 
227
292
  # fully validate whether this is a valid public key (more expensive than IsValid())
228
- def fully_valid_pubkey?
229
- return false unless valid_pubkey?
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
293
+ def fully_valid_pubkey?(allow_hybrid = false)
294
+ valid_pubkey? && secp256k1_module.parse_ec_pubkey?(pubkey, allow_hybrid)
236
295
  end
237
296
 
238
297
  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
 
@@ -1,6 +1,8 @@
1
1
  module Bitcoin
2
2
  module Message
3
3
 
4
+ class Error < StandardError; end
5
+
4
6
  autoload :Base, 'bitcoin/message/base'
5
7
  autoload :Inventory, 'bitcoin/message/inventory'
6
8
  autoload :InventoriesParser, 'bitcoin/message/inventories_parser'
@@ -37,6 +39,15 @@ module Bitcoin
37
39
  autoload :BlockTransactionRequest, 'bitcoin/message/block_transaction_request'
38
40
  autoload :BlockTxn, 'bitcoin/message/block_txn'
39
41
  autoload :BlockTransactions, 'bitcoin/message/block_transactions'
42
+ autoload :GetCFilters, 'bitcoin/message/get_cfilters'
43
+ autoload :GetCFHeaders, 'bitcoin/message/get_cfheaders'
44
+ autoload :CFParser, 'bitcoin/message/cf_parser'
45
+ autoload :GetCFCheckpt, 'bitcoin/message/get_cfcheckpt'
46
+ autoload :CFCheckpt, 'bitcoin/message/cfcheckpt'
47
+ autoload :CFilter, 'bitcoin/message/cfilter'
48
+ autoload :CFHeaders, 'bitcoin/message/cfheaders'
49
+ autoload :SendAddrV2, 'bitcoin/message/send_addr_v2'
50
+ autoload :AddrV2, 'bitcoin/message/addr_v2'
40
51
 
41
52
  USER_AGENT = "/bitcoinrb:#{Bitcoin::VERSION}/"
42
53
 
@@ -66,5 +77,73 @@ module Bitcoin
66
77
  compact_witness: 70015
67
78
  }
68
79
 
80
+ module_function
81
+
82
+ # Decode P2P message.
83
+ # @param [String] command P2P message command string.
84
+ # @param [String] payload P2P message payload with hex format..
85
+ # @return [Bitcoin::Message::]
86
+ def decode(command, payload = nil)
87
+ payload = payload.htb if payload
88
+ case command
89
+ when Bitcoin::Message::Version::COMMAND
90
+ Bitcoin::Message::Version.parse_from_payload(payload)
91
+ when Bitcoin::Message::VerAck::COMMAND
92
+ Bitcoin::Message::VerAck.new
93
+ when Bitcoin::Message::GetAddr::COMMAND
94
+ Bitcoin::Message::GetAddr.new
95
+ when Bitcoin::Message::Addr::COMMAND
96
+ Bitcoin::Message::Addr.parse_from_payload(payload)
97
+ when Bitcoin::Message::SendHeaders::COMMAND
98
+ Bitcoin::Message::SendHeaders.new
99
+ when Bitcoin::Message::FeeFilter::COMMAND
100
+ Bitcoin::Message::FeeFilter.parse_from_payload(payload)
101
+ when Bitcoin::Message::Ping::COMMAND
102
+ Bitcoin::Message::Ping.parse_from_payload(payload)
103
+ when Bitcoin::Message::Pong::COMMAND
104
+ Bitcoin::Message::Pong.parse_from_payload(payload)
105
+ when Bitcoin::Message::GetHeaders::COMMAND
106
+ Bitcoin::Message::GetHeaders.parse_from_payload(payload)
107
+ when Bitcoin::Message::Headers::COMMAND
108
+ Bitcoin::Message::Headers.parse_from_payload(payload)
109
+ when Bitcoin::Message::Block::COMMAND
110
+ Bitcoin::Message::Block.parse_from_payload(payload)
111
+ when Bitcoin::Message::Tx::COMMAND
112
+ Bitcoin::Message::Tx.parse_from_payload(payload)
113
+ when Bitcoin::Message::NotFound::COMMAND
114
+ Bitcoin::Message::NotFound.parse_from_payload(payload)
115
+ when Bitcoin::Message::MemPool::COMMAND
116
+ Bitcoin::Message::MemPool.new
117
+ when Bitcoin::Message::Reject::COMMAND
118
+ Bitcoin::Message::Reject.parse_from_payload(payload)
119
+ when Bitcoin::Message::SendCmpct::COMMAND
120
+ Bitcoin::Message::SendCmpct.parse_from_payload(payload)
121
+ when Bitcoin::Message::Inv::COMMAND
122
+ Bitcoin::Message::Inv.parse_from_payload(payload)
123
+ when Bitcoin::Message::MerkleBlock::COMMAND
124
+ Bitcoin::Message::MerkleBlock.parse_from_payload(payload)
125
+ when Bitcoin::Message::CmpctBlock::COMMAND
126
+ Bitcoin::Message::CmpctBlock.parse_from_payload(payload)
127
+ when Bitcoin::Message::GetData::COMMAND
128
+ Bitcoin::Message::GetData.parse_from_payload(payload)
129
+ when Bitcoin::Message::GetCFHeaders::COMMAND
130
+ Bitcoin::Message::GetCFHeaders.parse_from_payload(payload)
131
+ when Bitcoin::Message::GetCFilters::COMMAND
132
+ Bitcoin::Message::GetCFilters.parse_from_payload(payload)
133
+ when Bitcoin::Message::GetCFCheckpt::COMMAND
134
+ Bitcoin::Message::GetCFCheckpt.parse_from_payload(payload)
135
+ when Bitcoin::Message::CFCheckpt::COMMAND
136
+ Bitcoin::Message::CFCheckpt.parse_from_payload(payload)
137
+ when Bitcoin::Message::CFHeaders::COMMAND
138
+ Bitcoin::Message::CFHeaders.parse_from_payload(payload)
139
+ when Bitcoin::Message::CFilter::COMMAND
140
+ Bitcoin::Message::CFilter.parse_from_payload(payload)
141
+ when Bitcoin::Message::SendAddrV2::COMMAND
142
+ Bitcoin::Message::SendAddrV2.new
143
+ when Bitcoin::Message::AddrV2::COMMAND
144
+ Bitcoin::Message::AddrV2.parse_from_payload(payload)
145
+ end
146
+ end
147
+
69
148
  end
70
149
  end
@@ -0,0 +1,34 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # addrv2 message class.
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki
6
+ class AddrV2 < Base
7
+
8
+ COMMAND = 'addrv2'
9
+
10
+ attr_reader :addrs
11
+
12
+ def initialize(addrs = [])
13
+ @addrs = addrs
14
+ end
15
+
16
+ def self.parse_from_payload(payload)
17
+ buf = StringIO.new(payload)
18
+ addr_count = Bitcoin.unpack_var_int_from_io(buf)
19
+ v2 = new
20
+ addr_count.times do
21
+ v2.addrs << NetworkAddr.parse_from_payload(buf, type: NetworkAddr::TYPE[:addr_v2])
22
+ end
23
+ v2
24
+ end
25
+
26
+ def to_payload
27
+ buf = Bitcoin.pack_var_int(addrs.size)
28
+ buf << (addrs.map { |a| a.to_payload(type: NetworkAddr::TYPE[:addr_v2])}.join)
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
@@ -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
 
@@ -22,6 +23,22 @@ module Bitcoin
22
23
  raise 'to_payload must be implemented in a child class.'
23
24
  end
24
25
 
26
+ # Decode message data to message object.
27
+ # @param [String] message with binary format.
28
+ # @return [Bitcoin::Message::XXX] An instance of a class that inherits Bitcoin::Message::Base
29
+ # @raise [ArgumentError] Occurs for data that cannot be decoded.
30
+ def self.from_pkt(message)
31
+ buf = StringIO.new(message)
32
+ magic = buf.read(4)
33
+ raise ArgumentError, 'Invalid magic.' unless magic == Bitcoin.chain_params.magic_head.htb
34
+ command = buf.read(12).delete("\x00")
35
+ length = buf.read(4).unpack1('V')
36
+ checksum = buf.read(4)
37
+ payload = buf.read(length)
38
+ raise ArgumentError, 'Checksum do not match.' unless checksum == Bitcoin.double_sha256(payload)[0...4]
39
+ Bitcoin::Message.decode(command, payload&.bth)
40
+ end
41
+
25
42
  end
26
43
 
27
44
  end
@@ -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).unpack1('C')
23
+ hash = buf.read(32).unpack1('H*')
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