bitcoinrb 0.6.0 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa485194b6a1f5ea2ba0a4ed3355cae8cc8cdd4252475a78dc811ec54a93dfa4
4
- data.tar.gz: 5c2c91ab2041b88e0ada77a1099df5e4d564f2d1da0a2963e3ff1beec0b63172
3
+ metadata.gz: ffa8f8b7dbf104190628e7ff38dbb950aa14927ab208377c821ad7f5aeaab862
4
+ data.tar.gz: f24e89dd6de49e066fae37fe5c4118d955f31a9a751af5901d6cde32528aa8fa
5
5
  SHA512:
6
- metadata.gz: c84550004b92167af33a7832c69cafeb44e24a98d491865976194614eed2ea6c831f5ac47a716ec989d1f759786741a26cb81fa6b5da33850a2e2b4dc0fa331b
7
- data.tar.gz: fb8bdfea37eb452b565f826cb2a12c56865fd80410376f0d444cddf339a1e4eb423988ef617a1dc84b573bb114db3ddae89ae49f60afd882349e92169bac6031
6
+ metadata.gz: 306f65243198a07403c785b212d5d86f5a6eb27b189aa9454d27d280f4d93500b967e93fb7f995e8c998ce7f8f9ff4ba40a838c7f4f430b8e2fef0c04cb11206
7
+ data.tar.gz: 9e4d92cc79249f5027f105a8c7f69320f35b06b3bc3e9b5cc0416f74e7698f47910f4b63afba1821ec49e647671b1b67d4955acb80fe587d79bb34d0507ae98e
@@ -0,0 +1,37 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
15
+
16
+ jobs:
17
+ test:
18
+
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ matrix:
22
+ ruby-version: ['2.6', '2.7', '3.0']
23
+
24
+ steps:
25
+ - uses: actions/checkout@v2
26
+ - name: Install leveldb
27
+ run: sudo apt-get install libleveldb-dev
28
+ - name: Set up Ruby
29
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
30
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
31
+ # uses: ruby/setup-ruby@v1
32
+ uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
33
+ with:
34
+ ruby-version: ${{ matrix.ruby-version }}
35
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
36
+ - name: Run tests
37
+ run: bundle exec rake spec
data/.rspec_parallel ADDED
@@ -0,0 +1,2 @@
1
+ --format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log
2
+ --format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-3.0.0
1
+ ruby-3.0.2
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Bitcoinrb [![Build Status](https://travis-ci.org/chaintope/bitcoinrb.svg?branch=master)](https://travis-ci.org/chaintope/bitcoinrb) [![Gem Version](https://badge.fury.io/rb/bitcoinrb.svg)](https://badge.fury.io/rb/bitcoinrb) [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) <img src="http://segwit.co/static/public/images/logo.png" width="100">
1
+ # Bitcoinrb [![Build Status](https://github.com/chaintope/bitcoinrb/actions/workflows/ruby.yml/badge.svg?branch=master)](https://github.com/chaintope/bitcoinrb/actions/workflows/ruby.yml) [![Gem Version](https://badge.fury.io/rb/bitcoinrb.svg)](https://badge.fury.io/rb/bitcoinrb) [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) <img src="http://segwit.co/static/public/images/logo.png" width="100">
2
2
 
3
3
 
4
4
  Bitcoinrb is a Ruby implementation of Bitcoin Protocol.
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'
@@ -33,7 +33,8 @@ Gem::Specification.new do |spec|
33
33
  spec.add_runtime_dependency 'siphash'
34
34
  spec.add_runtime_dependency 'protobuf', '3.8.5'
35
35
  spec.add_runtime_dependency 'json_pure', '>= 2.3.1'
36
- spec.add_runtime_dependency 'bip-schnorr', '>= 0.3.2'
36
+ spec.add_runtime_dependency 'bip-schnorr', '>= 0.4.0'
37
+ spec.add_runtime_dependency 'base32', '>= 0.3.4'
37
38
 
38
39
  # for options
39
40
  spec.add_development_dependency 'leveldb-native'
@@ -43,5 +44,5 @@ Gem::Specification.new do |spec|
43
44
  spec.add_development_dependency 'rspec', '~> 3.0'
44
45
  spec.add_development_dependency 'timecop'
45
46
  spec.add_development_dependency 'webmock', '>= 3.11.1'
46
-
47
+ spec.add_development_dependency 'parallel', '>= 1.20.1'
47
48
  end
@@ -53,27 +53,34 @@ module Bitcoin
53
53
  MANDATORY_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH
54
54
 
55
55
  # Standard script verification flags that standard transactions will comply with.
56
- STANDARD_SCRIPT_VERIFY_FLAGS = [MANDATORY_SCRIPT_VERIFY_FLAGS,
57
- SCRIPT_VERIFY_DERSIG,
58
- SCRIPT_VERIFY_STRICTENC,
59
- SCRIPT_VERIFY_MINIMALDATA,
60
- SCRIPT_VERIFY_NULLDUMMY,
61
- SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS,
62
- SCRIPT_VERIFY_CLEANSTACK,
63
- SCRIPT_VERIFY_MINIMALIF,
64
- SCRIPT_VERIFY_NULLFAIL,
65
- SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY,
66
- SCRIPT_VERIFY_CHECKSEQUENCEVERIFY,
67
- SCRIPT_VERIFY_LOW_S,
68
- SCRIPT_VERIFY_WITNESS,
69
- SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM,
70
- SCRIPT_VERIFY_WITNESS_PUBKEYTYPE,
71
- SCRIPT_VERIFY_CONST_SCRIPTCODE].inject(SCRIPT_VERIFY_NONE){|flags, f| flags |= f}
56
+ STANDARD_SCRIPT_VERIFY_FLAGS = [
57
+ MANDATORY_SCRIPT_VERIFY_FLAGS,
58
+ SCRIPT_VERIFY_DERSIG,
59
+ SCRIPT_VERIFY_STRICTENC,
60
+ SCRIPT_VERIFY_MINIMALDATA,
61
+ SCRIPT_VERIFY_NULLDUMMY,
62
+ SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS,
63
+ SCRIPT_VERIFY_CLEANSTACK,
64
+ SCRIPT_VERIFY_MINIMALIF,
65
+ SCRIPT_VERIFY_NULLFAIL,
66
+ SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY,
67
+ SCRIPT_VERIFY_CHECKSEQUENCEVERIFY,
68
+ SCRIPT_VERIFY_LOW_S,
69
+ SCRIPT_VERIFY_WITNESS,
70
+ SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM,
71
+ SCRIPT_VERIFY_WITNESS_PUBKEYTYPE,
72
+ SCRIPT_VERIFY_CONST_SCRIPTCODE,
73
+ SCRIPT_VERIFY_TAPROOT |
74
+ SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION |
75
+ SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS |
76
+ SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE
77
+ ].inject(SCRIPT_VERIFY_NONE){|flags, f| flags |= f}
72
78
 
73
79
  # for script
74
80
 
75
81
  # witness version
76
- WITNESS_VERSION = 0x00
82
+ WITNESS_VERSION_V0 = 0x00
83
+ WITNESS_VERSION_V1 = Bitcoin::Opcodes::OP_1
77
84
 
78
85
  # Maximum script length in bytes
79
86
  MAX_SCRIPT_SIZE = 10000
@@ -0,0 +1,22 @@
1
+ module Bitcoin
2
+ module Ext
3
+ module ArrayExt
4
+
5
+ refine Array do
6
+
7
+ # resize array content with +initial_value+.
8
+ # expect to behave like vec#resize in c++.
9
+ def resize!(new_size, initial_value = 0)
10
+ if size < new_size
11
+ (new_size - size).times{self.<< initial_value}
12
+ elsif size > new_size
13
+ (size - new_size).times{delete_at(-1)}
14
+ end
15
+ self
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -3,6 +3,11 @@ class ::ECDSA::Signature
3
3
  def to_der
4
4
  ECDSA::Format::SignatureDerString.encode(self)
5
5
  end
6
+
7
+ def ==(other)
8
+ return false unless other.is_a?(ECDSA::Signature)
9
+ r == other.r && s == other.s
10
+ end
6
11
  end
7
12
 
8
13
  class ::ECDSA::Point
data/lib/bitcoin/ext.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  module Bitcoin
2
2
  module Ext
3
3
  autoload :JsonParser, 'bitcoin/ext/json_parser'
4
+ autoload :ArrayExt, 'bitcoin/ext/array_ext'
4
5
  end
5
6
  end
@@ -280,7 +280,7 @@ module Bitcoin
280
280
  l = Bitcoin.hmac_sha512(chain_code, data)
281
281
  left = l[0..31].bth.to_i(16)
282
282
  raise 'invalid key' if left >= CURVE_ORDER
283
- p1 = Bitcoin::Secp256k1::GROUP.generator.multiply_by_scalar(left)
283
+ p1 = Bitcoin::Key.new(priv_key: left.to_s(16), key_type: Bitcoin::Key::TYPES[:uncompressed]).to_point
284
284
  p2 = Bitcoin::Key.new(pubkey: pubkey, key_type: key_type).to_point
285
285
  new_key.pubkey = (p1 + p2).to_hex
286
286
  new_key.chain_code = l[32..-1]
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
@@ -29,7 +30,6 @@ module Bitcoin
29
30
  # @param [Boolean] compressed [Deprecated] whether public key is compressed.
30
31
  # @return [Bitcoin::Key] a key object.
31
32
  def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false)
32
- puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil?
33
33
  if key_type
34
34
  @key_type = key_type
35
35
  compressed = @key_type != TYPES[:uncompressed]
@@ -77,6 +77,23 @@ module Bitcoin
77
77
  new(priv_key: data.bth, key_type: key_type)
78
78
  end
79
79
 
80
+ # Generate from xonly public key.
81
+ # @param [String] xonly_pubkey xonly public key with hex format.
82
+ # @return [Bitcoin::Key] key object has public key.
83
+ def self.from_xonly_pubkey(xonly_pubkey)
84
+ raise ArgumentError, 'xonly_pubkey must be 32 bytes' unless xonly_pubkey.htb.bytesize == 32
85
+ Bitcoin::Key.new(pubkey: "02#{xonly_pubkey}", key_type: TYPES[:compressed])
86
+ end
87
+
88
+ # Generate from public key point.
89
+ # @param [ECDSA::Point] point Public key point.
90
+ # @param [Boolean] compressed whether compressed or not.
91
+ # @return [Bitcoin::Key]
92
+ def self.from_point(point, compressed: true)
93
+ pubkey = ECDSA::Format::PointOctetString.encode(point, compression: compressed).bth
94
+ Bitcoin::Key.new(pubkey: pubkey, key_type: TYPES[:compressed])
95
+ end
96
+
80
97
  # export private key with wif format
81
98
  def to_wif
82
99
  version = Bitcoin.chain_params.privkey_version
@@ -112,6 +129,29 @@ module Bitcoin
112
129
  end
113
130
  end
114
131
 
132
+ # Sign compact signature.
133
+ # @param [String] data message digest to be signed.
134
+ # @return [String] compact signature with binary format.
135
+ def sign_compact(data)
136
+ signature, rec = secp256k1_module.sign_compact(data, priv_key)
137
+ rec = Bitcoin::Key::COMPACT_SIG_HEADER_BYTE + rec + (compressed? ? 4 : 0)
138
+ [rec].pack('C') + ECDSA::Format::IntegerOctetString.encode(signature.r, 32) +
139
+ ECDSA::Format::IntegerOctetString.encode(signature.s, 32)
140
+ end
141
+
142
+ # Recover public key from compact signature.
143
+ # @param [String] data message digest using signature.
144
+ # @param [String] signature signature with binary format.
145
+ # @return [Bitcoin::Key] Recovered public key.
146
+ def self.recover_compact(data, signature)
147
+ rec_id = signature.unpack1('C')
148
+ rec = rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE
149
+ raise ArgumentError, 'Invalid signature parameter' if rec < 0 || rec > 15
150
+ rec = rec & 3
151
+ compressed = (rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE) & 4 != 0
152
+ Bitcoin.secp_impl.recover_compact(data, signature, rec, compressed)
153
+ end
154
+
115
155
  # verify signature using public key
116
156
  # @param [String] sig signature data with binary format
117
157
  # @param [String] data original message
@@ -165,7 +205,7 @@ module Bitcoin
165
205
  # @return [ECDSA::Point]
166
206
  def to_point
167
207
  p = pubkey
168
- p ||= generate_pubkey(priv_key, compressed: compressed)
208
+ p ||= generate_pubkey(priv_key, compressed: compressed?)
169
209
  ECDSA::Format::PointOctetString.decode(p.htb, Bitcoin::Secp256k1::GROUP)
170
210
  end
171
211
 
@@ -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
@@ -16,7 +16,7 @@ module Bitcoin
16
16
  end
17
17
 
18
18
  def self.parse_from_payload(payload)
19
- tx = Bitcoin::Tx.parse_from_payload(payload)
19
+ tx = Bitcoin::Tx.parse_from_payload(payload, strict: true)
20
20
  new(tx, tx.witness?)
21
21
  end
22
22
 
@@ -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