bitcoinrb 0.6.0 → 1.0.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: 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