bitcoinrb 0.5.0 → 0.9.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 +4 -4
- data/.github/workflows/ruby.yml +37 -0
- data/.rspec_parallel +2 -0
- data/.ruby-version +1 -1
- data/README.md +11 -1
- data/bitcoinrb.gemspec +7 -6
- data/lib/bitcoin/block_filter.rb +14 -0
- data/lib/bitcoin/chain_params.rb +9 -0
- data/lib/bitcoin/chainparams/signet.yml +39 -0
- data/lib/bitcoin/constants.rb +45 -4
- data/lib/bitcoin/descriptor.rb +1 -1
- data/lib/bitcoin/errors.rb +19 -0
- data/lib/bitcoin/ext/array_ext.rb +22 -0
- data/lib/bitcoin/ext/ecdsa.rb +36 -0
- data/lib/bitcoin/ext.rb +1 -0
- data/lib/bitcoin/ext_key.rb +36 -20
- data/lib/bitcoin/key.rb +85 -28
- data/lib/bitcoin/message/addr_v2.rb +34 -0
- data/lib/bitcoin/message/base.rb +16 -0
- data/lib/bitcoin/message/cfcheckpt.rb +2 -2
- data/lib/bitcoin/message/cfheaders.rb +1 -1
- data/lib/bitcoin/message/cfilter.rb +1 -1
- data/lib/bitcoin/message/fee_filter.rb +1 -1
- data/lib/bitcoin/message/filter_load.rb +3 -3
- data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
- data/lib/bitcoin/message/inventory.rb +1 -1
- data/lib/bitcoin/message/merkle_block.rb +1 -1
- data/lib/bitcoin/message/network_addr.rb +141 -18
- data/lib/bitcoin/message/ping.rb +1 -1
- data/lib/bitcoin/message/pong.rb +1 -1
- data/lib/bitcoin/message/send_addr_v2.rb +13 -0
- data/lib/bitcoin/message/send_cmpct.rb +2 -2
- data/lib/bitcoin/message/tx.rb +1 -1
- data/lib/bitcoin/message.rb +72 -0
- data/lib/bitcoin/message_sign.rb +47 -0
- data/lib/bitcoin/mnemonic.rb +2 -2
- data/lib/bitcoin/network/peer_discovery.rb +1 -3
- data/lib/bitcoin/node/configuration.rb +3 -1
- data/lib/bitcoin/node/spv.rb +8 -0
- data/lib/bitcoin/opcodes.rb +14 -1
- data/lib/bitcoin/payment_code.rb +2 -2
- data/lib/bitcoin/payments/payment.pb.rb +1 -1
- data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
- data/lib/bitcoin/psbt/input.rb +4 -4
- data/lib/bitcoin/psbt/output.rb +1 -1
- data/lib/bitcoin/psbt/tx.rb +14 -5
- data/lib/bitcoin/psbt.rb +8 -0
- data/lib/bitcoin/rpc/bitcoin_core_client.rb +1 -1
- data/lib/bitcoin/rpc/request_handler.rb +3 -3
- data/lib/bitcoin/script/script.rb +80 -30
- data/lib/bitcoin/script/script_error.rb +27 -1
- data/lib/bitcoin/script/script_interpreter.rb +164 -62
- data/lib/bitcoin/script/tx_checker.rb +62 -14
- data/lib/bitcoin/secp256k1/native.rb +184 -17
- data/lib/bitcoin/secp256k1/ruby.rb +108 -21
- data/lib/bitcoin/sighash_generator.rb +157 -0
- data/lib/bitcoin/taproot/leaf_node.rb +23 -0
- data/lib/bitcoin/taproot/simple_builder.rb +155 -0
- data/lib/bitcoin/taproot.rb +45 -0
- data/lib/bitcoin/tx.rb +30 -96
- data/lib/bitcoin/tx_in.rb +1 -1
- data/lib/bitcoin/tx_out.rb +2 -3
- data/lib/bitcoin/util.rb +15 -6
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/account.rb +1 -1
- data/lib/bitcoin.rb +32 -24
- metadata +58 -18
- data/.travis.yml +0 -12
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,8 +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
|
-
puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil?
|
32
|
+
def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false)
|
33
33
|
if key_type
|
34
34
|
@key_type = key_type
|
35
35
|
compressed = @key_type != TYPES[:uncompressed]
|
@@ -39,13 +39,14 @@ module Bitcoin
|
|
39
39
|
@secp256k1_module = Bitcoin.secp_impl
|
40
40
|
@priv_key = priv_key
|
41
41
|
if @priv_key
|
42
|
-
raise ArgumentError,
|
42
|
+
raise ArgumentError, Errors::Messages::INVALID_PRIV_KEY unless validate_private_key_range(@priv_key)
|
43
43
|
end
|
44
44
|
if pubkey
|
45
45
|
@pubkey = pubkey
|
46
46
|
else
|
47
47
|
@pubkey = generate_pubkey(priv_key, compressed: compressed) if priv_key
|
48
48
|
end
|
49
|
+
raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless fully_valid_pubkey?(allow_hybrid)
|
49
50
|
end
|
50
51
|
|
51
52
|
# generate key pair
|
@@ -63,9 +64,9 @@ module Bitcoin
|
|
63
64
|
data = hex[2...-8].htb
|
64
65
|
checksum = hex[-8..-1]
|
65
66
|
raise ArgumentError, 'invalid version' unless version == Bitcoin.chain_params.privkey_version
|
66
|
-
raise ArgumentError,
|
67
|
+
raise ArgumentError, Errors::Messages::INVALID_CHECKSUM unless Bitcoin.calc_checksum(version + data.bth) == checksum
|
67
68
|
key_len = data.bytesize
|
68
|
-
if key_len == COMPRESSED_PUBLIC_KEY_SIZE && data[-1].
|
69
|
+
if key_len == COMPRESSED_PUBLIC_KEY_SIZE && data[-1].unpack1('C') == 1
|
69
70
|
key_type = TYPES[:compressed]
|
70
71
|
data = data[0..-2]
|
71
72
|
elsif key_len == 32
|
@@ -76,6 +77,23 @@ module Bitcoin
|
|
76
77
|
new(priv_key: data.bth, key_type: key_type)
|
77
78
|
end
|
78
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
|
+
|
79
97
|
# export private key with wif format
|
80
98
|
def to_wif
|
81
99
|
version = Bitcoin.chain_params.privkey_version
|
@@ -88,30 +106,69 @@ module Bitcoin
|
|
88
106
|
# sign +data+ with private key
|
89
107
|
# @param [String] data a data to be signed with binary format
|
90
108
|
# @param [Boolean] low_r flag to apply low-R.
|
91
|
-
# @param [String] extra_entropy the extra entropy for rfc6979.
|
109
|
+
# @param [String] extra_entropy the extra entropy with binary format for rfc6979.
|
110
|
+
# @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
|
92
111
|
# @return [String] signature data with binary format
|
93
|
-
def sign(data, low_r = true, extra_entropy = nil)
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
112
|
+
def sign(data, low_r = true, extra_entropy = nil, algo: :ecdsa)
|
113
|
+
case algo
|
114
|
+
when :ecdsa
|
115
|
+
sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
|
116
|
+
if low_r && !sig_has_low_r?(sig)
|
117
|
+
counter = 1
|
118
|
+
until sig_has_low_r?(sig)
|
119
|
+
extra_entropy = [counter].pack('I*').bth.ljust(64, '0').htb
|
120
|
+
sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
|
121
|
+
counter += 1
|
122
|
+
end
|
101
123
|
end
|
124
|
+
sig
|
125
|
+
when :schnorr
|
126
|
+
secp256k1_module.sign_data(data, priv_key, extra_entropy, algo: :schnorr)
|
127
|
+
else
|
128
|
+
raise ArgumentError "Unsupported algo specified: #{algo}"
|
102
129
|
end
|
103
|
-
|
130
|
+
end
|
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)
|
104
153
|
end
|
105
154
|
|
106
155
|
# verify signature using public key
|
107
156
|
# @param [String] sig signature data with binary format
|
108
|
-
# @param [String]
|
157
|
+
# @param [String] data original message
|
158
|
+
# @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
|
109
159
|
# @return [Boolean] verify result
|
110
|
-
def verify(sig,
|
160
|
+
def verify(sig, data, algo: :ecdsa)
|
111
161
|
return false unless valid_pubkey?
|
112
162
|
begin
|
113
|
-
|
114
|
-
|
163
|
+
case algo
|
164
|
+
when :ecdsa
|
165
|
+
sig = ecdsa_signature_parse_der_lax(sig)
|
166
|
+
secp256k1_module.verify_sig(data, sig, pubkey)
|
167
|
+
when :schnorr
|
168
|
+
secp256k1_module.verify_sig(data, sig, xonly_pubkey, algo: :schnorr)
|
169
|
+
else
|
170
|
+
false
|
171
|
+
end
|
115
172
|
rescue Exception
|
116
173
|
false
|
117
174
|
end
|
@@ -148,10 +205,16 @@ module Bitcoin
|
|
148
205
|
# @return [ECDSA::Point]
|
149
206
|
def to_point
|
150
207
|
p = pubkey
|
151
|
-
p ||= generate_pubkey(priv_key, compressed: compressed)
|
208
|
+
p ||= generate_pubkey(priv_key, compressed: compressed?)
|
152
209
|
ECDSA::Format::PointOctetString.decode(p.htb, Bitcoin::Secp256k1::GROUP)
|
153
210
|
end
|
154
211
|
|
212
|
+
# get xonly public key (32 bytes).
|
213
|
+
# @return [String] xonly public key with hex format
|
214
|
+
def xonly_pubkey
|
215
|
+
pubkey[2..65]
|
216
|
+
end
|
217
|
+
|
155
218
|
# check +pubkey+ (hex) is compress or uncompress pubkey.
|
156
219
|
def self.compress_or_uncompress_pubkey?(pubkey)
|
157
220
|
p = pubkey.htb
|
@@ -225,14 +288,8 @@ module Bitcoin
|
|
225
288
|
end
|
226
289
|
|
227
290
|
# fully validate whether this is a valid public key (more expensive than IsValid())
|
228
|
-
def fully_valid_pubkey?
|
229
|
-
|
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
|
291
|
+
def fully_valid_pubkey?(allow_hybrid = false)
|
292
|
+
valid_pubkey? && secp256k1_module.parse_ec_pubkey?(pubkey, allow_hybrid)
|
236
293
|
end
|
237
294
|
|
238
295
|
private
|
@@ -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
|
data/lib/bitcoin/message/base.rb
CHANGED
@@ -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
|
@@ -19,8 +19,8 @@ module Bitcoin
|
|
19
19
|
|
20
20
|
def self.parse_from_payload(payload)
|
21
21
|
buf = StringIO.new(payload)
|
22
|
-
type = buf.read(1).
|
23
|
-
hash = buf.read(32).
|
22
|
+
type = buf.read(1).unpack1('C')
|
23
|
+
hash = buf.read(32).unpack1('H*')
|
24
24
|
count = Bitcoin.unpack_var_int_from_io(buf)
|
25
25
|
headers = count.times.map{buf.read(32).bth}
|
26
26
|
self.new(type, hash, headers)
|
@@ -21,7 +21,7 @@ module Bitcoin
|
|
21
21
|
|
22
22
|
def self.parse_from_payload(payload)
|
23
23
|
buf = StringIO.new(payload)
|
24
|
-
type = buf.read(1).
|
24
|
+
type = buf.read(1).unpack1("C")
|
25
25
|
hash = buf.read(32).bth
|
26
26
|
header = buf.read(32).bth
|
27
27
|
count = Bitcoin.unpack_var_int_from_io(buf)
|
@@ -19,7 +19,7 @@ module Bitcoin
|
|
19
19
|
|
20
20
|
def self.parse_from_payload(payload)
|
21
21
|
buf = StringIO.new(payload)
|
22
|
-
type = buf.read(1).
|
22
|
+
type = buf.read(1).unpack1("C")
|
23
23
|
hash = buf.read(32).bth
|
24
24
|
len = Bitcoin.unpack_var_int_from_io(buf)
|
25
25
|
filter = buf.read(len).bth
|
@@ -23,9 +23,9 @@ module Bitcoin
|
|
23
23
|
buf = StringIO.new(payload)
|
24
24
|
filter_count = Bitcoin.unpack_var_int_from_io(buf)
|
25
25
|
filter = buf.read(filter_count).unpack('C*')
|
26
|
-
func_count = buf.read(4).
|
27
|
-
tweak = buf.read(4).
|
28
|
-
flag = buf.read(1).
|
26
|
+
func_count = buf.read(4).unpack1('V')
|
27
|
+
tweak = buf.read(4).unpack1('V')
|
28
|
+
flag = buf.read(1).unpack1('C')
|
29
29
|
FilterLoad.new(Bitcoin::BloomFilter.new(filter, func_count, tweak), flag)
|
30
30
|
end
|
31
31
|
|
@@ -22,7 +22,7 @@ module Bitcoin
|
|
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).
|
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)
|
@@ -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].
|
29
|
+
identifier = payload[0..4].unpack1('V')
|
30
30
|
hash = payload[4..-1].bth # internal byte order
|
31
31
|
new(identifier, hash)
|
32
32
|
end
|
@@ -20,7 +20,7 @@ module Bitcoin
|
|
20
20
|
m = new
|
21
21
|
buf = StringIO.new(payload)
|
22
22
|
m.header = Bitcoin::BlockHeader.parse_from_payload(buf.read(80))
|
23
|
-
m.tx_count = buf.read(4).
|
23
|
+
m.tx_count = buf.read(4).unpack1('V')
|
24
24
|
hash_count = Bitcoin.unpack_var_int_from_io(buf)
|
25
25
|
hash_count.times do
|
26
26
|
m.hashes << buf.read(32).bth
|
@@ -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 :
|
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,
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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.
|
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
|
-
|
50
|
-
|
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
|
51
102
|
end
|
52
103
|
|
53
|
-
|
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
|
150
|
+
end
|
151
|
+
|
152
|
+
def legacy_payload(skip_time)
|
54
153
|
p = ''
|
55
154
|
p << [time].pack('V') unless skip_time
|
56
|
-
|
57
|
-
p << [services].pack('Q') <<
|
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
|
data/lib/bitcoin/message/ping.rb
CHANGED
data/lib/bitcoin/message/pong.rb
CHANGED
@@ -21,8 +21,8 @@ module Bitcoin
|
|
21
21
|
|
22
22
|
def self.parse_from_payload(payload)
|
23
23
|
buf = StringIO.new(payload)
|
24
|
-
mode = buf.read(1).
|
25
|
-
version = buf.read(8).
|
24
|
+
mode = buf.read(1).unpack1('c')
|
25
|
+
version = buf.read(8).unpack1('Q')
|
26
26
|
new(mode, version)
|
27
27
|
end
|
28
28
|
|