bitcoinrb 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa485194b6a1f5ea2ba0a4ed3355cae8cc8cdd4252475a78dc811ec54a93dfa4
4
- data.tar.gz: 5c2c91ab2041b88e0ada77a1099df5e4d564f2d1da0a2963e3ff1beec0b63172
3
+ metadata.gz: 5026fc4446c5a88954149551d7168d8022e5aa41cd45cbf86b44ce62fdb308a5
4
+ data.tar.gz: a6a3708940e5eca7d8eba4ac71ca941e98ba5af7e09f516ea6e7583b38299c80
5
5
  SHA512:
6
- metadata.gz: c84550004b92167af33a7832c69cafeb44e24a98d491865976194614eed2ea6c831f5ac47a716ec989d1f759786741a26cb81fa6b5da33850a2e2b4dc0fa331b
7
- data.tar.gz: fb8bdfea37eb452b565f826cb2a12c56865fd80410376f0d444cddf339a1e4eb423988ef617a1dc84b573bb114db3ddae89ae49f60afd882349e92169bac6031
6
+ metadata.gz: 0f3ce5cc15d9de9c0fbb578ec179a81148228bb7f12c9937f6a3fe6f73585d09f6d62798abe1255e95e5d415c0a0e7fdeeb51457245b16565c8908b76d2d0249
7
+ data.tar.gz: fc3f64635b4330c15152b9a38b49a2ec7b324d3a7940cd7a51c64a601d6f9e89a6731014a9f7d2c86cef8a0453b18beb39c5ecec288b8a77c24d73d615c2aebf
data/.travis.yml CHANGED
@@ -1,3 +1,4 @@
1
+ dist: bionic
1
2
  language: ruby
2
3
  rvm:
3
4
  - 2.4.10
data/bitcoinrb.gemspec CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.add_runtime_dependency 'ecdsa'
24
24
  spec.add_runtime_dependency 'eventmachine'
25
25
  spec.add_runtime_dependency 'murmurhash3'
26
- spec.add_runtime_dependency 'bech32', '~> 1.0.3'
26
+ spec.add_runtime_dependency 'bech32', '~> 1.1.0'
27
27
  spec.add_runtime_dependency 'daemon-spawn'
28
28
  spec.add_runtime_dependency 'thor'
29
29
  spec.add_runtime_dependency 'ffi'
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.add_runtime_dependency 'protobuf', '3.8.5'
35
35
  spec.add_runtime_dependency 'json_pure', '>= 2.3.1'
36
36
  spec.add_runtime_dependency 'bip-schnorr', '>= 0.3.2'
37
+ spec.add_runtime_dependency 'base32', '>= 0.3.4'
37
38
 
38
39
  # for options
39
40
  spec.add_development_dependency 'leveldb-native'
data/lib/bitcoin.rb CHANGED
@@ -133,14 +133,7 @@ module Bitcoin
133
133
 
134
134
  # get opcode
135
135
  def opcode
136
- case encoding
137
- when Encoding::ASCII_8BIT
138
- each_byte.next
139
- when Encoding::US_ASCII
140
- ord
141
- else
142
- to_i
143
- end
136
+ force_encoding(Encoding::ASCII_8BIT).ord
144
137
  end
145
138
 
146
139
  def opcode?
@@ -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'
@@ -44,6 +46,8 @@ module Bitcoin
44
46
  autoload :CFCheckpt, 'bitcoin/message/cfcheckpt'
45
47
  autoload :CFilter, 'bitcoin/message/cfilter'
46
48
  autoload :CFHeaders, 'bitcoin/message/cfheaders'
49
+ autoload :SendAddrV2, 'bitcoin/message/send_addr_v2'
50
+ autoload :AddrV2, 'bitcoin/message/addr_v2'
47
51
 
48
52
  USER_AGENT = "/bitcoinrb:#{Bitcoin::VERSION}/"
49
53
 
@@ -73,5 +77,73 @@ module Bitcoin
73
77
  compact_witness: 70015
74
78
  }
75
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
+
76
148
  end
77
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
@@ -23,6 +23,22 @@ module Bitcoin
23
23
  raise 'to_payload must be implemented in a child class.'
24
24
  end
25
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
+
26
42
  end
27
43
 
28
44
  end
@@ -1,10 +1,16 @@
1
1
  require 'ipaddr'
2
+ require 'base32'
2
3
 
3
4
  module Bitcoin
4
5
  module Message
5
6
 
7
+ NETWORK_ID = {ipv4: 0x01, ipv6: 0x02, tor_v2: 0x03, tor_v3: 0x04, i2p: 0x05, cjdns: 0x06}
8
+ INTERNAL_IN_IPV6_PREFIX = "fd6b:88c0:8724"
9
+
6
10
  class NetworkAddr
7
11
 
12
+ TYPE = {legacy: 0x01, addr_v2: 0x02}
13
+
8
14
  # unix time.
9
15
  # Nodes advertising their own IP address set this to the current time.
10
16
  # Nodes advertising IP addresses they’ve connected to set this to the last time they connected to that node.
@@ -14,47 +20,164 @@ module Bitcoin
14
20
  # The services the node advertised in its version message.
15
21
  attr_accessor :services
16
22
 
17
- attr_accessor :ip_addr # IPAddr
23
+ attr_accessor :net # network ID that defined by BIP-155
24
+
25
+ # Network address. The interpretation depends on networkID.
26
+ # If ipv4 or ipv6 this field is a IPAddr object, otherwise hex string.
27
+ attr_accessor :addr
18
28
 
19
29
  attr_accessor :port
20
30
 
21
31
  attr_reader :skip_time
22
32
 
23
- def initialize(ip: '127.0.0.1', port: Bitcoin.chain_params.default_port, services: DEFAULT_SERVICE_FLAGS, time: Time.now.to_i)
33
+ def initialize(ip: '127.0.0.1', port: Bitcoin.chain_params.default_port,
34
+ services: DEFAULT_SERVICE_FLAGS, time: Time.now.to_i, net: NETWORK_ID[:ipv4])
24
35
  @time = time
25
- @ip_addr = IPAddr.new(ip)
26
36
  @port = port
27
37
  @services = services
38
+ @net = net
39
+ case net
40
+ when NETWORK_ID[:ipv4], NETWORK_ID[:ipv6]
41
+ @addr = IPAddr.new(ip) if ip
42
+ end
28
43
  end
29
44
 
30
- def self.parse_from_payload(payload)
31
- buf = payload.is_a?(String) ? StringIO.new(payload) : payload
32
- has_time = buf.size > 26
33
- addr = new(time: nil)
34
- addr.time = buf.read(4).unpack1('V') if has_time
35
- addr.services = buf.read(8).unpack1('Q')
36
- addr.ip_addr = IPAddr::new_ntoh(buf.read(16))
37
- addr.port = buf.read(2).unpack1('n')
38
- addr
45
+ # Parse addr payload
46
+ # @param [String] payload payload of addr
47
+ # @param [Integer] type Address format type
48
+ # @return [NetworkAddr]
49
+ def self.parse_from_payload(payload, type: TYPE[:legacy])
50
+ case type
51
+ when TYPE[:legacy]
52
+ load_legacy_payload(payload)
53
+ when TYPE[:addr_v2]
54
+ load_addr_v2_payload(payload)
55
+ else
56
+ raise Bitcoin::Message::Error, "Unknown type: #{type}."
57
+ end
39
58
  end
40
59
 
41
60
  def self.local_addr
42
61
  addr = new
43
- addr.ip_addr = IPAddr.new('127.0.0.1')
62
+ addr.addr = IPAddr.new('127.0.0.1')
44
63
  addr.port = Bitcoin.chain_params.default_port
45
64
  addr.services = DEFAULT_SERVICE_FLAGS
46
65
  addr
47
66
  end
48
67
 
49
- def ip
50
- ip_addr.ipv4_mapped? ? ip_addr.native : ip_addr.to_s
68
+ # Show addr string. e.g 127.0.0.1
69
+ def addr_string
70
+ case net
71
+ when NETWORK_ID[:ipv4]
72
+ addr.native
73
+ when NETWORK_ID[:ipv6]
74
+ if addr.to_s.start_with?(INTERNAL_IN_IPV6_PREFIX)
75
+ Base32.encode(addr.hton[6..-1]).downcase.delete('=') + ".internal"
76
+ else
77
+ addr.to_s
78
+ end
79
+ when NETWORK_ID[:tor_v2]
80
+ Base32.encode(addr.htb).downcase + ".onion"
81
+ when NETWORK_ID[:tor_v3]
82
+ # TORv3 onion_address = base32(PUBKEY | CHECKSUM | VERSION) + ".onion"
83
+ pubkey = addr.htb
84
+ checksum = OpenSSL::Digest.new('SHA3-256').digest('.onion checksum' + pubkey + "\x03")
85
+ Base32.encode(pubkey + checksum[0...2] + "\x03").downcase + ".onion"
86
+ when NETWORK_ID[:i2p]
87
+ Base32.encode(addr.htb).downcase.delete('=') + ".b32.i2p"
88
+ when NETWORK_ID[:cjdns]
89
+ addr.to_s
90
+ end
91
+ end
92
+
93
+ def to_payload(skip_time = false, type: TYPE[:legacy])
94
+ case type
95
+ when TYPE[:legacy]
96
+ legacy_payload(skip_time)
97
+ when TYPE[:addr_v2]
98
+ v2_payload
99
+ else
100
+ raise Bitcoin::Message::Error, "Unknown type: #{type}."
101
+ end
102
+ end
103
+
104
+ # Load addr payload with legacy format.
105
+ def self.load_legacy_payload(payload)
106
+ buf = payload.is_a?(String) ? StringIO.new(payload) : payload
107
+ has_time = buf.size > 26
108
+ addr = NetworkAddr.new(time: nil)
109
+ addr.time = buf.read(4).unpack1('V') if has_time
110
+ addr.services = buf.read(8).unpack1('Q')
111
+ addr.addr = IPAddr::new_ntoh(buf.read(16))
112
+ addr.port = buf.read(2).unpack1('n')
113
+ addr
114
+ end
115
+
116
+ # Load addr payload with addr v2 format.
117
+ def self.load_addr_v2_payload(payload)
118
+ buf = payload.is_a?(String) ? StringIO.new(payload) : payload
119
+ addr = NetworkAddr.new(time: buf.read(4).unpack1('V'))
120
+ addr.services = Bitcoin.unpack_var_int_from_io(buf)
121
+ addr.net = buf.read(1).unpack1('C')
122
+ raise Bitcoin::Message::Error, "Unknown network id: #{addr.net}" unless NETWORK_ID.value?(addr.net)
123
+ addr_len = Bitcoin.unpack_var_int_from_io(buf)
124
+ addr.addr = case addr.net
125
+ when NETWORK_ID[:ipv4]
126
+ raise Bitcoin::Message::Error, "Invalid IPv4 address." unless addr_len == 4
127
+ IPAddr::new_ntoh(buf.read(addr_len))
128
+ when NETWORK_ID[:ipv6]
129
+ raise Bitcoin::Message::Error, "Invalid IPv6 address." unless addr_len == 16
130
+ a = IPAddr::new_ntoh(buf.read(addr_len))
131
+ raise Bitcoin::Message::Error, "Invalid IPv6 address." if a.ipv4_mapped?
132
+ a
133
+ when NETWORK_ID[:tor_v2]
134
+ raise Bitcoin::Message::Error, "Invalid Tor v2 address." unless addr_len == 10
135
+ buf.read(addr_len).bth
136
+ when NETWORK_ID[:tor_v3]
137
+ raise Bitcoin::Message::Error, "Invalid Tor v3 address." unless addr_len == 32
138
+ buf.read(addr_len).bth
139
+ when NETWORK_ID[:i2p]
140
+ raise Bitcoin::Message::Error, "Invalid I2P address." unless addr_len == 32
141
+ buf.read(addr_len).bth
142
+ when NETWORK_ID[:cjdns]
143
+ raise Bitcoin::Message::Error, "Invalid CJDNS address." unless addr_len == 16
144
+ a = IPAddr::new_ntoh(buf.read(addr_len))
145
+ raise Bitcoin::Message::Error, "Invalid CJDNS address." unless a.to_s.start_with?('fc00:')
146
+ a
147
+ end
148
+ addr.port = buf.read(2).unpack1('n')
149
+ addr
51
150
  end
52
151
 
53
- def to_payload(skip_time = false)
152
+ def legacy_payload(skip_time)
54
153
  p = ''
55
154
  p << [time].pack('V') unless skip_time
56
- addr = ip_addr.ipv4? ? ip_addr.ipv4_mapped : ip_addr
57
- p << [services].pack('Q') << addr.hton << [port].pack('n')
155
+ ip = addr.ipv4? ? addr.ipv4_mapped : addr
156
+ p << [services].pack('Q') << ip.hton << [port].pack('n')
157
+ end
158
+
159
+ def v2_payload
160
+ p = [time].pack('V')
161
+ p << Bitcoin.pack_var_int(services)
162
+ p << [net].pack('C')
163
+ case net
164
+ when NETWORK_ID[:ipv4]
165
+ p << Bitcoin.pack_var_int(4)
166
+ p << addr.to_i.to_s(16).htb
167
+ when NETWORK_ID[:ipv6]
168
+ p << Bitcoin.pack_var_int(16)
169
+ p << addr.hton
170
+ when NETWORK_ID[:tor_v2]
171
+ p << Bitcoin.pack_var_int(10)
172
+ when NETWORK_ID[:tor_v3]
173
+ p << Bitcoin.pack_var_int(32)
174
+ when NETWORK_ID[:i2p]
175
+ p << Bitcoin.pack_var_int(32)
176
+ when NETWORK_ID[:cjdns]
177
+ p << Bitcoin.pack_var_int(16)
178
+ end
179
+ p << [port].pack('n')
180
+ p
58
181
  end
59
182
 
60
183
  end
@@ -0,0 +1,13 @@
1
+ module Bitcoin
2
+ module Message
3
+ class SendAddrV2 < Base
4
+
5
+ COMMAND = 'sendaddrv2'
6
+
7
+ def to_payload
8
+ ''
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -49,7 +49,7 @@ module Bitcoin
49
49
  # Returns connected peer information.
50
50
  def getpeerinfo
51
51
  node.pool.peers.map do |peer|
52
- local_addr = "#{peer.remote_version.remote_addr.ip}:18333"
52
+ local_addr = "#{peer.remote_version.remote_addr.addr_string}:18333"
53
53
  {
54
54
  id: peer.id,
55
55
  addr: "#{peer.host}:#{peer.port}",
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.6.0"
2
+ VERSION = "0.7.0"
3
3
  end
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.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-20 00:00:00.000000000 Z
11
+ date: 2021-02-19 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.3
61
+ version: 1.1.0
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.3
68
+ version: 1.1.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: daemon-spawn
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -206,6 +206,20 @@ dependencies:
206
206
  - - ">="
207
207
  - !ruby/object:Gem::Version
208
208
  version: 0.3.2
209
+ - !ruby/object:Gem::Dependency
210
+ name: base32
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: 0.3.4
216
+ type: :runtime
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: 0.3.4
209
223
  - !ruby/object:Gem::Dependency
210
224
  name: leveldb-native
211
225
  requirement: !ruby/object:Gem::Requirement
@@ -342,6 +356,7 @@ files:
342
356
  - lib/bitcoin/merkle_tree.rb
343
357
  - lib/bitcoin/message.rb
344
358
  - lib/bitcoin/message/addr.rb
359
+ - lib/bitcoin/message/addr_v2.rb
345
360
  - lib/bitcoin/message/base.rb
346
361
  - lib/bitcoin/message/block.rb
347
362
  - lib/bitcoin/message/block_transaction_request.rb
@@ -379,6 +394,7 @@ files:
379
394
  - lib/bitcoin/message/pong.rb
380
395
  - lib/bitcoin/message/prefilled_tx.rb
381
396
  - lib/bitcoin/message/reject.rb
397
+ - lib/bitcoin/message/send_addr_v2.rb
382
398
  - lib/bitcoin/message/send_cmpct.rb
383
399
  - lib/bitcoin/message/send_headers.rb
384
400
  - lib/bitcoin/message/tx.rb