bitcoinrb 1.15.0 → 1.16.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/bitcoinrb.gemspec +5 -4
- data/lib/bitcoin/bip321_uri.rb +95 -32
- data/lib/bitcoin/bip324/ell_swift_pubkey.rb +3 -1
- data/lib/bitcoin/bip324/fs_chacha20.rb +3 -0
- data/lib/bitcoin/bip324.rb +1 -1
- data/lib/bitcoin/bip85_entropy.rb +79 -29
- data/lib/bitcoin/bit_stream.rb +2 -2
- data/lib/bitcoin/block.rb +46 -7
- data/lib/bitcoin/block_filter.rb +3 -2
- data/lib/bitcoin/block_header.rb +31 -8
- data/lib/bitcoin/bloom_filter.rb +5 -2
- data/lib/bitcoin/chainparams/regtest.yml +0 -1
- data/lib/bitcoin/chainparams/signet.yml +1 -1
- data/lib/bitcoin/constants.rb +13 -3
- data/lib/bitcoin/descriptor/combo.rb +13 -1
- data/lib/bitcoin/descriptor/expression.rb +35 -15
- data/lib/bitcoin/descriptor/multi.rb +9 -1
- data/lib/bitcoin/descriptor/multi_a.rb +4 -0
- data/lib/bitcoin/descriptor/musig.rb +1 -0
- data/lib/bitcoin/descriptor/pk.rb +7 -0
- data/lib/bitcoin/descriptor/pkh.rb +5 -0
- data/lib/bitcoin/descriptor/raw.rb +1 -1
- data/lib/bitcoin/descriptor/sh.rb +1 -0
- data/lib/bitcoin/descriptor/sp.rb +2 -2
- data/lib/bitcoin/descriptor/tr.rb +29 -3
- data/lib/bitcoin/descriptor/wpkh.rb +1 -0
- data/lib/bitcoin/descriptor/wsh.rb +1 -0
- data/lib/bitcoin/descriptor.rb +59 -35
- data/lib/bitcoin/ext/array_ext.rb +3 -0
- data/lib/bitcoin/ext/ecdsa.rb +7 -1
- data/lib/bitcoin/ext_key.rb +10 -0
- data/lib/bitcoin/gcs_filter.rb +21 -6
- data/lib/bitcoin/key.rb +66 -14
- data/lib/bitcoin/key_path.rb +8 -5
- data/lib/bitcoin/message/addr.rb +1 -0
- data/lib/bitcoin/message/addr_v2.rb +1 -0
- data/lib/bitcoin/message/base.rb +8 -1
- data/lib/bitcoin/message/block.rb +4 -8
- data/lib/bitcoin/message/block_transaction_request.rb +11 -8
- data/lib/bitcoin/message/block_transactions.rb +2 -1
- data/lib/bitcoin/message/cf_parser.rb +1 -0
- data/lib/bitcoin/message/cfcheckpt.rb +3 -3
- data/lib/bitcoin/message/cfheaders.rb +5 -4
- data/lib/bitcoin/message/cfilter.rb +3 -3
- data/lib/bitcoin/message/error.rb +3 -1
- data/lib/bitcoin/message/fee_filter.rb +1 -0
- data/lib/bitcoin/message/filter_add.rb +3 -1
- data/lib/bitcoin/message/filter_load.rb +4 -4
- data/lib/bitcoin/message/get_cfcheckpt.rb +1 -0
- data/lib/bitcoin/message/header_and_short_ids.rb +26 -11
- data/lib/bitcoin/message/headers.rb +10 -2
- data/lib/bitcoin/message/headers_parser.rb +4 -2
- data/lib/bitcoin/message/inventories_parser.rb +2 -1
- data/lib/bitcoin/message/inventory.rb +15 -6
- data/lib/bitcoin/message/merkle_block.rb +4 -4
- data/lib/bitcoin/message/network_addr.rb +13 -13
- data/lib/bitcoin/message/ping.rb +1 -0
- data/lib/bitcoin/message/pong.rb +1 -0
- data/lib/bitcoin/message/reject.rb +1 -0
- data/lib/bitcoin/message/send_cmpct.rb +2 -2
- data/lib/bitcoin/message/send_tx_rcncl.rb +1 -1
- data/lib/bitcoin/message/version.rb +23 -8
- data/lib/bitcoin/message.rb +15 -0
- data/lib/bitcoin/message_sign.rb +44 -6
- data/lib/bitcoin/mnemonic.rb +8 -0
- data/lib/bitcoin/out_point.rb +6 -2
- data/lib/bitcoin/partial_tree.rb +41 -28
- data/lib/bitcoin/payment_code.rb +26 -8
- data/lib/bitcoin/psbt/hd_key_path.rb +1 -2
- data/lib/bitcoin/psbt/input.rb +199 -48
- data/lib/bitcoin/psbt/key_origin_info.rb +2 -0
- data/lib/bitcoin/psbt/output.rb +9 -6
- data/lib/bitcoin/psbt/proprietary.rb +6 -3
- data/lib/bitcoin/psbt/tx.rb +9 -4
- data/lib/bitcoin/psbt.rb +46 -0
- data/lib/bitcoin/rpc/bitcoin_core_client.rb +54 -13
- data/lib/bitcoin/rpc/error.rb +43 -0
- data/lib/bitcoin/rpc.rb +1 -0
- data/lib/bitcoin/script/script.rb +114 -33
- data/lib/bitcoin/script/script_interpreter.rb +11 -6
- data/lib/bitcoin/script/tx_checker.rb +7 -2
- data/lib/bitcoin/script_witness.rb +5 -1
- data/lib/bitcoin/secp256k1/ruby.rb +10 -4
- data/lib/bitcoin/silent_payment.rb +6 -3
- data/lib/bitcoin/slip39/share.rb +35 -9
- data/lib/bitcoin/slip39/sss.rb +26 -11
- data/lib/bitcoin/slip39.rb +13 -5
- data/lib/bitcoin/taproot/control_block.rb +6 -1
- data/lib/bitcoin/taproot/custom_depth_builder.rb +12 -0
- data/lib/bitcoin/tx.rb +35 -14
- data/lib/bitcoin/tx_in.rb +2 -2
- data/lib/bitcoin/tx_out.rb +2 -2
- data/lib/bitcoin/util.rb +49 -28
- data/lib/bitcoin/validation.rb +64 -12
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/account.rb +35 -11
- data/lib/bitcoin/wallet/base.rb +40 -15
- data/lib/bitcoin/wallet/utxo.rb +22 -4
- data/lib/bitcoin.rb +16 -4
- metadata +24 -9
data/lib/bitcoin/key.rb
CHANGED
|
@@ -95,7 +95,7 @@ module Bitcoin
|
|
|
95
95
|
# @return [Bitcoin::Key]
|
|
96
96
|
def self.from_point(point, compressed: true)
|
|
97
97
|
pubkey = ECDSA::Format::PointOctetString.encode(point, compression: compressed).bth
|
|
98
|
-
Bitcoin::Key.new(pubkey: pubkey, key_type: TYPES[:compressed])
|
|
98
|
+
Bitcoin::Key.new(pubkey: pubkey, key_type: compressed ? TYPES[:compressed] : TYPES[:uncompressed])
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
# export private key with wif format
|
|
@@ -164,6 +164,7 @@ module Bitcoin
|
|
|
164
164
|
case algo
|
|
165
165
|
when :ecdsa
|
|
166
166
|
sig = ecdsa_signature_parse_der_lax(sig)
|
|
167
|
+
return false unless sig
|
|
167
168
|
secp256k1_module.verify_sig(data, sig, pubkey)
|
|
168
169
|
when :schnorr
|
|
169
170
|
secp256k1_module.verify_sig(data, sig, xonly_pubkey, algo: :schnorr)
|
|
@@ -359,7 +360,11 @@ module Bitcoin
|
|
|
359
360
|
if secp256k1_module.native?
|
|
360
361
|
Bitcoin::BIP324::EllSwiftPubkey.new(secp256k1_module.ellswift_create(priv_key))
|
|
361
362
|
else
|
|
362
|
-
Bitcoin::BIP324
|
|
363
|
+
encoded = Bitcoin::BIP324.xelligatorswift(xonly_pubkey)
|
|
364
|
+
# t and -t decode to the same x, and the parity of t gives the Y coordinate, as libsecp256k1 encodes it.
|
|
365
|
+
t = encoded[64..-1].hex
|
|
366
|
+
t = Bitcoin::BIP324::FIELD.mod(-t) if t.odd? != to_point.y.odd?
|
|
367
|
+
Bitcoin::BIP324::EllSwiftPubkey.new(encoded[0...64] + ECDSA::Format::IntegerOctetString.encode(t, 32).bth)
|
|
363
368
|
end
|
|
364
369
|
end
|
|
365
370
|
|
|
@@ -394,22 +399,69 @@ module Bitcoin
|
|
|
394
399
|
MIN_PRIV_KEY_MOD_ORDER <= value && value <= MAX_PRIV_KEY_MOD_ORDER
|
|
395
400
|
end
|
|
396
401
|
|
|
397
|
-
#
|
|
398
|
-
#
|
|
399
|
-
#
|
|
400
|
-
#
|
|
401
|
-
# violations present in the blockchain before that point.
|
|
402
|
+
# Parse a DER signature the way ecdsa_signature_parse_der_lax in Bitcoin Core does, and return it
|
|
403
|
+
# as strict DER for the backend. Supported violations include negative integers, excessive padding,
|
|
404
|
+
# garbage at the end, and overly long length descriptors. This is safe to use in Bitcoin because
|
|
405
|
+
# since the activation of BIP66, signatures are verified to be strict DER before being passed to
|
|
406
|
+
# this method, and it supports all violations present in the blockchain before that point.
|
|
407
|
+
# @param [String] sig a signature with binary format, without the sighash type.
|
|
408
|
+
# @return [String, nil] the signature as strict DER, or nil when it cannot be parsed or R or S is
|
|
409
|
+
# zero or not below the curve order, which Bitcoin Core also treats as an invalid signature.
|
|
402
410
|
def ecdsa_signature_parse_der_lax(sig)
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
411
|
+
bytes = sig.unpack('C*')
|
|
412
|
+
pos = 0
|
|
413
|
+
return nil if bytes[pos] != 0x30 # sequence tag
|
|
414
|
+
pos += 1
|
|
415
|
+
return nil if pos >= bytes.size
|
|
416
|
+
len_byte = bytes[pos] # the sequence length is skipped, however long it claims to be
|
|
417
|
+
pos += 1
|
|
418
|
+
if len_byte & 0x80 != 0
|
|
419
|
+
len_byte -= 0x80
|
|
420
|
+
return nil if len_byte > bytes.size - pos
|
|
421
|
+
pos += len_byte
|
|
422
|
+
end
|
|
423
|
+
r, pos = parse_lax_der_integer(bytes, pos)
|
|
424
|
+
return nil unless r
|
|
425
|
+
s, _ = parse_lax_der_integer(bytes, pos)
|
|
426
|
+
return nil unless s
|
|
427
|
+
order = ECDSA::Group::Secp256k1.order
|
|
428
|
+
return nil if r.zero? || s.zero? || r >= order || s >= order
|
|
429
|
+
ECDSA::Signature.new(r, s).to_der
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Parse one INTEGER of a lax DER signature starting at +pos+.
|
|
433
|
+
# @return [Array(Integer, Integer), nil] the value and the position after it, or nil.
|
|
434
|
+
def parse_lax_der_integer(bytes, pos)
|
|
435
|
+
return nil if pos >= bytes.size || bytes[pos] != 0x02 # integer tag
|
|
436
|
+
pos += 1
|
|
437
|
+
return nil if pos >= bytes.size
|
|
438
|
+
len_byte = bytes[pos]
|
|
439
|
+
pos += 1
|
|
440
|
+
if len_byte & 0x80 != 0
|
|
441
|
+
len_byte -= 0x80
|
|
442
|
+
return nil if len_byte > bytes.size - pos
|
|
443
|
+
while len_byte > 0 && bytes[pos] == 0 # leading zeros of the length
|
|
444
|
+
pos += 1
|
|
445
|
+
len_byte -= 1
|
|
446
|
+
end
|
|
447
|
+
return nil if len_byte >= 4
|
|
448
|
+
len = 0
|
|
449
|
+
len_byte.times do
|
|
450
|
+
len = (len << 8) + bytes[pos]
|
|
451
|
+
pos += 1
|
|
452
|
+
end
|
|
453
|
+
else
|
|
454
|
+
len = len_byte
|
|
455
|
+
end
|
|
456
|
+
return nil if len > bytes.size - pos
|
|
457
|
+
# Read as an unsigned number: a leading 0x80 bit, which strict DER would call negative, and
|
|
458
|
+
# leading zero padding are both accepted.
|
|
459
|
+
value = len.zero? ? 0 : bytes[pos, len].pack('C*').unpack1('H*').to_i(16)
|
|
460
|
+
[value, pos + len]
|
|
409
461
|
end
|
|
410
462
|
|
|
411
463
|
def valid_pubkey?
|
|
412
|
-
!pubkey.nil? && pubkey.size > 0
|
|
464
|
+
!pubkey.nil? && pubkey.size > 0 && pubkey.valid_hex?
|
|
413
465
|
end
|
|
414
466
|
|
|
415
467
|
# check whether the signature is low-R
|
data/lib/bitcoin/key_path.rb
CHANGED
|
@@ -2,6 +2,7 @@ module Bitcoin
|
|
|
2
2
|
module KeyPath
|
|
3
3
|
|
|
4
4
|
# key path convert an array of derive number
|
|
5
|
+
# A hardened index is marked with a trailing ' or h, as Bitcoin Core's ParseHDKeypath reads it.
|
|
5
6
|
# @param [String] path_string
|
|
6
7
|
# @return [Array[Integer]] key path numbers.
|
|
7
8
|
# @raise [ArgumentError] if invalid +path_string+.
|
|
@@ -14,11 +15,13 @@ module Bitcoin
|
|
|
14
15
|
next if p == 'm'
|
|
15
16
|
raise ArgumentError, "Invalid path." unless p == 'm'
|
|
16
17
|
end
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
hardened = p.end_with?("'", 'h')
|
|
19
|
+
number = hardened ? p[0...-1] : p
|
|
20
|
+
raise ArgumentError, "Invalid path." unless number =~ /\A[0-9]+\z/
|
|
21
|
+
value = number.to_i
|
|
22
|
+
# 4294967295 = 0xFFFFFFFF (uint32 max)
|
|
23
|
+
raise ArgumentError, "Invalid path." if value > (hardened ? Bitcoin::HARDENED_THRESHOLD - 1 : 4294967295)
|
|
24
|
+
hardened ? value + Bitcoin::HARDENED_THRESHOLD : value
|
|
22
25
|
end[1..-1]
|
|
23
26
|
end
|
|
24
27
|
|
data/lib/bitcoin/message/addr.rb
CHANGED
|
@@ -18,6 +18,7 @@ module Bitcoin
|
|
|
18
18
|
def self.parse_from_payload(payload)
|
|
19
19
|
buf = StringIO.new(payload)
|
|
20
20
|
addr_count = Bitcoin.unpack_var_int_from_io(buf)
|
|
21
|
+
raise Bitcoin::Message::Error, "addr message size #{addr_count} exceeds #{MAX_ADDR_TO_SEND}." if addr_count > MAX_ADDR_TO_SEND
|
|
21
22
|
addr = new
|
|
22
23
|
addr_count.times do
|
|
23
24
|
addr.addrs << NetworkAddr.parse_from_payload(buf)
|
|
@@ -16,6 +16,7 @@ module Bitcoin
|
|
|
16
16
|
def self.parse_from_payload(payload)
|
|
17
17
|
buf = StringIO.new(payload)
|
|
18
18
|
addr_count = Bitcoin.unpack_var_int_from_io(buf)
|
|
19
|
+
raise Bitcoin::Message::Error, "addrv2 message size #{addr_count} exceeds #{MAX_ADDR_TO_SEND}." if addr_count > MAX_ADDR_TO_SEND
|
|
19
20
|
v2 = new
|
|
20
21
|
addr_count.times do
|
|
21
22
|
v2.addrs << NetworkAddr.parse_from_payload(buf, type: NetworkAddr::TYPE[:addr_v2])
|
data/lib/bitcoin/message/base.rb
CHANGED
|
@@ -7,6 +7,9 @@ module Bitcoin
|
|
|
7
7
|
include Bitcoin::Util
|
|
8
8
|
extend Bitcoin::Util
|
|
9
9
|
|
|
10
|
+
# The size of the message header: magic, command, length and checksum.
|
|
11
|
+
HEADER_SIZE = 24
|
|
12
|
+
|
|
10
13
|
# generate message header (binary format)
|
|
11
14
|
# https://bitcoin.org/en/developer-reference#message-headers
|
|
12
15
|
def to_pkt
|
|
@@ -28,10 +31,14 @@ module Bitcoin
|
|
|
28
31
|
# @return [Bitcoin::Message::XXX] An instance of a class that inherits Bitcoin::Message::Base
|
|
29
32
|
# @raise [ArgumentError] Occurs for data that cannot be decoded.
|
|
30
33
|
def self.from_pkt(message)
|
|
34
|
+
raise ArgumentError, 'Message header is too short.' if message.bytesize < HEADER_SIZE
|
|
31
35
|
buf = StringIO.new(message)
|
|
32
36
|
magic = buf.read(4)
|
|
33
37
|
raise ArgumentError, 'Invalid magic.' unless magic == Bitcoin.chain_params.magic_head.htb
|
|
34
|
-
command
|
|
38
|
+
# The command is padded with NUL bytes to 12. Bitcoin Core rejects a command with a non-NUL byte
|
|
39
|
+
# after its first NUL.
|
|
40
|
+
command, padding = buf.read(12).split("\x00", 2)
|
|
41
|
+
raise ArgumentError, 'Invalid command.' if command.nil? || padding&.delete("\x00")&.bytesize.to_i > 0
|
|
35
42
|
# The length is announced by the peer, so it is checked before it is used to read.
|
|
36
43
|
length = buf.read(4).unpack1('V')
|
|
37
44
|
if length > MAX_PROTOCOL_MESSAGE_LENGTH
|
|
@@ -19,14 +19,10 @@ module Bitcoin
|
|
|
19
19
|
|
|
20
20
|
def self.parse_from_payload(payload)
|
|
21
21
|
buf = StringIO.new(payload)
|
|
22
|
-
header = Bitcoin::BlockHeader.parse_from_payload(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
tx_count.times do
|
|
27
|
-
transactions << Bitcoin::Tx.parse_from_payload(buf)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
22
|
+
header = Bitcoin::BlockHeader.parse_from_payload(Bitcoin.read_exact(buf, 80))
|
|
23
|
+
# A block message always carries its tx count, which may be 0 only in a block nobody can produce.
|
|
24
|
+
tx_count = Bitcoin.unpack_var_int_from_io(buf)
|
|
25
|
+
transactions = tx_count.times.map { Bitcoin::Tx.parse_from_payload(buf) }
|
|
30
26
|
new(header, transactions)
|
|
31
27
|
end
|
|
32
28
|
|
|
@@ -15,18 +15,21 @@ module Bitcoin
|
|
|
15
15
|
|
|
16
16
|
def self.parse_from_payload(payload)
|
|
17
17
|
buf = StringIO.new(payload)
|
|
18
|
-
block_hash =
|
|
18
|
+
block_hash = Bitcoin.read_exact(buf, 32)
|
|
19
19
|
index_len = Bitcoin.unpack_var_int_from_io(buf)
|
|
20
|
-
|
|
21
|
-
#
|
|
20
|
+
# Each index takes at least one byte, so a count the payload cannot hold is rejected before
|
|
21
|
+
# anything is allocated for it.
|
|
22
|
+
raise Bitcoin::Message::Error, "Invalid getblocktxn payload. index count #{index_len} exceeds the payload." if index_len > (buf.size - buf.pos)
|
|
23
|
+
# index data differentially encoded. Bitcoin Core keeps every decoded index within 16 bits.
|
|
24
|
+
indexes = []
|
|
22
25
|
offset = 0
|
|
23
|
-
index_len.times do
|
|
24
|
-
index =
|
|
25
|
-
index
|
|
26
|
-
indexes
|
|
26
|
+
index_len.times do
|
|
27
|
+
index = Bitcoin.unpack_var_int_from_io(buf) + offset
|
|
28
|
+
raise Bitcoin::Message::Error, 'Invalid getblocktxn payload. index overflowed 16 bits.' if index > 0xffff
|
|
29
|
+
indexes << index
|
|
27
30
|
offset = index + 1
|
|
28
31
|
end
|
|
29
|
-
self.new(block_hash, indexes)
|
|
32
|
+
self.new(block_hash.bth, indexes)
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
def to_payload
|
|
@@ -15,8 +15,9 @@ module Bitcoin
|
|
|
15
15
|
|
|
16
16
|
def self.parse_from_payload(payload)
|
|
17
17
|
buf = StringIO.new(payload)
|
|
18
|
-
block_hash =
|
|
18
|
+
block_hash = Bitcoin.read_exact(buf, 32).bth
|
|
19
19
|
tx_count = Bitcoin.unpack_var_int_from_io(buf)
|
|
20
|
+
raise Bitcoin::Message::Error, "blocktxn tx count #{tx_count} exceeds #{MAX_COMPACT_BLOCK_TX_COUNT}." if tx_count > MAX_COMPACT_BLOCK_TX_COUNT
|
|
20
21
|
txn = tx_count.times.map{Bitcoin::Tx.parse_from_payload(buf)}
|
|
21
22
|
self.new(block_hash, txn)
|
|
22
23
|
end
|
|
@@ -19,10 +19,10 @@ module Bitcoin
|
|
|
19
19
|
|
|
20
20
|
def self.parse_from_payload(payload)
|
|
21
21
|
buf = StringIO.new(payload)
|
|
22
|
-
type =
|
|
23
|
-
hash =
|
|
22
|
+
type = Bitcoin.read_exact(buf, 1).unpack1('C')
|
|
23
|
+
hash = Bitcoin.read_exact(buf, 32).unpack1('H*')
|
|
24
24
|
count = Bitcoin.unpack_var_int_from_io(buf)
|
|
25
|
-
headers = count.times.map{
|
|
25
|
+
headers = count.times.map{Bitcoin.read_exact(buf, 32).bth}
|
|
26
26
|
self.new(type, hash, headers)
|
|
27
27
|
end
|
|
28
28
|
|
|
@@ -21,11 +21,12 @@ module Bitcoin
|
|
|
21
21
|
|
|
22
22
|
def self.parse_from_payload(payload)
|
|
23
23
|
buf = StringIO.new(payload)
|
|
24
|
-
type =
|
|
25
|
-
hash =
|
|
26
|
-
header =
|
|
24
|
+
type = Bitcoin.read_exact(buf, 1).unpack1("C")
|
|
25
|
+
hash = Bitcoin.read_exact(buf, 32).bth
|
|
26
|
+
header = Bitcoin.read_exact(buf, 32).bth
|
|
27
27
|
count = Bitcoin.unpack_var_int_from_io(buf)
|
|
28
|
-
|
|
28
|
+
raise Bitcoin::Message::Error, "cfheaders size #{count} exceeds #{MAX_CFHEADERS_RESULTS}." if count > MAX_CFHEADERS_RESULTS
|
|
29
|
+
hashes = count.times.map{Bitcoin.read_exact(buf, 32).bth}
|
|
29
30
|
self.new(type, hash, header, hashes)
|
|
30
31
|
end
|
|
31
32
|
|
|
@@ -19,10 +19,10 @@ module Bitcoin
|
|
|
19
19
|
|
|
20
20
|
def self.parse_from_payload(payload)
|
|
21
21
|
buf = StringIO.new(payload)
|
|
22
|
-
type =
|
|
23
|
-
hash =
|
|
22
|
+
type = Bitcoin.read_exact(buf, 1).unpack1("C")
|
|
23
|
+
hash = Bitcoin.read_exact(buf, 32).bth
|
|
24
24
|
len = Bitcoin.unpack_var_int_from_io(buf)
|
|
25
|
-
filter =
|
|
25
|
+
filter = Bitcoin.read_exact(buf, len).bth
|
|
26
26
|
self.new(type, hash, filter)
|
|
27
27
|
end
|
|
28
28
|
|
|
@@ -2,7 +2,9 @@ module Bitcoin
|
|
|
2
2
|
module Message
|
|
3
3
|
|
|
4
4
|
# P2P message layer Error
|
|
5
|
-
|
|
5
|
+
# It is an ArgumentError, like the errors the other parsers raise, so that every failure to parse
|
|
6
|
+
# data from a peer can be rescued as one.
|
|
7
|
+
class Error < ArgumentError
|
|
6
8
|
|
|
7
9
|
end
|
|
8
10
|
|
|
@@ -15,7 +15,9 @@ module Bitcoin
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def self.parse_from_payload(payload)
|
|
18
|
-
|
|
18
|
+
element = Bitcoin.unpack_var_string(payload).first
|
|
19
|
+
raise Bitcoin::Message::Error, "filteradd element size #{element.bytesize} exceeds #{Bitcoin::MAX_SCRIPT_ELEMENT_SIZE}." if element.bytesize > Bitcoin::MAX_SCRIPT_ELEMENT_SIZE
|
|
20
|
+
new(element.bth)
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def to_payload
|
|
@@ -28,14 +28,14 @@ module Bitcoin
|
|
|
28
28
|
raise Bitcoin::Message::Error,
|
|
29
29
|
"filter size must be less than or equal to #{Bitcoin::BloomFilter::MAX_BLOOM_FILTER_SIZE}."
|
|
30
30
|
end
|
|
31
|
-
filter =
|
|
32
|
-
func_count =
|
|
31
|
+
filter = Bitcoin.read_exact(buf, filter_count).unpack('C*')
|
|
32
|
+
func_count = Bitcoin.read_exact(buf, 4).unpack1('V')
|
|
33
33
|
if func_count > Bitcoin::BloomFilter::MAX_HASH_FUNCS
|
|
34
34
|
raise Bitcoin::Message::Error,
|
|
35
35
|
"hash funcs must be less than or equal to #{Bitcoin::BloomFilter::MAX_HASH_FUNCS}."
|
|
36
36
|
end
|
|
37
|
-
tweak =
|
|
38
|
-
flag =
|
|
37
|
+
tweak = Bitcoin.read_exact(buf, 4).unpack1('V')
|
|
38
|
+
flag = Bitcoin.read_exact(buf, 1).unpack1('C')
|
|
39
39
|
FilterLoad.new(Bitcoin::BloomFilter.new(filter, func_count, tweak), flag)
|
|
40
40
|
end
|
|
41
41
|
|
|
@@ -6,9 +6,9 @@ module Bitcoin
|
|
|
6
6
|
# https://github.com/bitcoin/bips/blob/master/bip-0152.mediawiki#HeaderAndShortIDs
|
|
7
7
|
class HeaderAndShortIDs
|
|
8
8
|
attr_accessor :header
|
|
9
|
-
attr_accessor :nonce
|
|
9
|
+
attr_accessor :nonce # 64 bit unsigned integer
|
|
10
10
|
attr_accessor :short_ids
|
|
11
|
-
attr_accessor :prefilled_txn
|
|
11
|
+
attr_accessor :prefilled_txn # Array of PrefilledTx, whose index is the position in the block.
|
|
12
12
|
attr_accessor :siphash_key
|
|
13
13
|
|
|
14
14
|
def initialize(header, nonce, short_ids = [], prefilled_txn = [])
|
|
@@ -16,31 +16,46 @@ module Bitcoin
|
|
|
16
16
|
@nonce = nonce
|
|
17
17
|
@short_ids = short_ids
|
|
18
18
|
@prefilled_txn = prefilled_txn
|
|
19
|
-
@siphash_key = Bitcoin.sha256(header.to_payload << [nonce].pack('
|
|
19
|
+
@siphash_key = Bitcoin.sha256(header.to_payload << [nonce].pack('Q<'))[0...16]
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def self.parse_from_payload(payload)
|
|
23
23
|
buf = StringIO.new(payload)
|
|
24
|
-
header = Bitcoin::BlockHeader.parse_from_payload(
|
|
25
|
-
nonce =
|
|
24
|
+
header = Bitcoin::BlockHeader.parse_from_payload(Bitcoin.read_exact(buf, 80))
|
|
25
|
+
nonce = Bitcoin.read_exact(buf, 8).unpack1('Q<')
|
|
26
26
|
short_ids_len = Bitcoin.unpack_var_int_from_io(buf)
|
|
27
|
+
raise Bitcoin::Message::Error, "cmpctblock tx count #{short_ids_len} exceeds #{MAX_COMPACT_BLOCK_TX_COUNT}." if short_ids_len > MAX_COMPACT_BLOCK_TX_COUNT
|
|
27
28
|
short_ids = short_ids_len.times.map do
|
|
28
|
-
|
|
29
|
+
Bitcoin.read_exact(buf, 6).reverse.bth.to_i(16)
|
|
29
30
|
end
|
|
30
31
|
prefilled_txn_len = Bitcoin.unpack_var_int_from_io(buf)
|
|
32
|
+
if short_ids_len + prefilled_txn_len > MAX_COMPACT_BLOCK_TX_COUNT
|
|
33
|
+
raise Bitcoin::Message::Error, "cmpctblock tx count #{short_ids_len + prefilled_txn_len} exceeds #{MAX_COMPACT_BLOCK_TX_COUNT}."
|
|
34
|
+
end
|
|
35
|
+
# The index of a prefilled tx is differentially encoded. Bitcoin Core keeps every decoded index within 16 bits.
|
|
36
|
+
offset = 0
|
|
31
37
|
prefilled_txn = prefilled_txn_len.times.map do
|
|
32
|
-
PrefilledTx.parse_from_io(buf)
|
|
38
|
+
prefilled = PrefilledTx.parse_from_io(buf)
|
|
39
|
+
prefilled.index += offset
|
|
40
|
+
raise Bitcoin::Message::Error, 'Invalid cmpctblock payload. prefilled tx index overflowed 16 bits.' if prefilled.index > 0xffff
|
|
41
|
+
offset = prefilled.index + 1
|
|
42
|
+
prefilled
|
|
33
43
|
end
|
|
34
44
|
self.new(header, nonce, short_ids, prefilled_txn)
|
|
35
45
|
end
|
|
36
46
|
|
|
37
47
|
def to_payload
|
|
38
48
|
p = header.to_payload
|
|
39
|
-
p << [nonce].pack('
|
|
49
|
+
p << [nonce].pack('Q<')
|
|
40
50
|
p << Bitcoin.pack_var_int(short_ids.size)
|
|
41
51
|
p << short_ids.map{|id|sprintf('%012x', id).htb.reverse}.join
|
|
42
52
|
p << Bitcoin.pack_var_int(prefilled_txn.size)
|
|
43
|
-
|
|
53
|
+
prev = -1
|
|
54
|
+
prefilled_txn.each do |prefilled|
|
|
55
|
+
raise ArgumentError, 'prefilled tx indexes must be in ascending order.' unless prefilled.index > prev
|
|
56
|
+
p << PrefilledTx.new(prefilled.index - prev - 1, prefilled.tx).to_payload
|
|
57
|
+
prev = prefilled.index
|
|
58
|
+
end
|
|
44
59
|
p
|
|
45
60
|
end
|
|
46
61
|
|
|
@@ -48,8 +63,8 @@ module Bitcoin
|
|
|
48
63
|
# @param [String] txid a transaction id
|
|
49
64
|
# @return [Integer] 6 bytes short transaction id.
|
|
50
65
|
def short_id(txid)
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
# The low 6 bytes of the 64 bit SipHash output, which may have leading zero bytes.
|
|
67
|
+
SipHash.digest(siphash_key, txid.htb.reverse) & 0xffff_ffff_ffff
|
|
53
68
|
end
|
|
54
69
|
|
|
55
70
|
end
|
|
@@ -7,6 +7,10 @@ module Bitcoin
|
|
|
7
7
|
|
|
8
8
|
COMMAND = 'headers'
|
|
9
9
|
|
|
10
|
+
# The most headers one message may carry. Bitcoin Core sends this many for one getheaders and
|
|
11
|
+
# rejects a message with more (MAX_HEADERS_RESULTS).
|
|
12
|
+
MAX_HEADERS_RESULTS = 2000
|
|
13
|
+
|
|
10
14
|
# Array[Bitcoin::BlockHeader]
|
|
11
15
|
attr_accessor :headers
|
|
12
16
|
|
|
@@ -14,13 +18,17 @@ module Bitcoin
|
|
|
14
18
|
@headers = headers
|
|
15
19
|
end
|
|
16
20
|
|
|
21
|
+
# @raise [Bitcoin::Message::Error] If the payload is truncated or carries more headers than allowed.
|
|
17
22
|
def self.parse_from_payload(payload)
|
|
18
23
|
buf = StringIO.new(payload)
|
|
19
24
|
header_count = Bitcoin.unpack_var_int_from_io(buf)
|
|
25
|
+
raise Bitcoin::Message::Error, "Invalid headers payload. header count #{header_count} exceeds #{MAX_HEADERS_RESULTS}." if header_count > MAX_HEADERS_RESULTS
|
|
20
26
|
h = new
|
|
21
27
|
header_count.times do
|
|
22
|
-
h.headers << Bitcoin::BlockHeader.parse_from_payload(
|
|
23
|
-
|
|
28
|
+
h.headers << Bitcoin::BlockHeader.parse_from_payload(Bitcoin.read_exact(buf, 80))
|
|
29
|
+
# The tx count is a CompactSize, which Bitcoin Core reads and ignores since a headers message
|
|
30
|
+
# carries no tx. Reading a single byte here misaligned every header behind a longer encoding.
|
|
31
|
+
Bitcoin.unpack_var_int_from_io(buf)
|
|
24
32
|
end
|
|
25
33
|
h
|
|
26
34
|
end
|
|
@@ -5,14 +5,16 @@ module Bitcoin
|
|
|
5
5
|
module HeadersParser
|
|
6
6
|
|
|
7
7
|
def parse_from_payload(payload)
|
|
8
|
+
raise Bitcoin::Message::Error, 'Payload is too short.' if payload.bytesize < 4
|
|
8
9
|
ver, payload = payload.unpack('Va*')
|
|
9
10
|
size, payload = Bitcoin.unpack_var_int(payload)
|
|
11
|
+
raise Bitcoin::Message::Error, "#{self::COMMAND} locator size #{size} exceeds #{MAX_LOCATOR_SZ}." if size > MAX_LOCATOR_SZ
|
|
10
12
|
hashes = []
|
|
11
13
|
buf = StringIO.new(payload)
|
|
12
14
|
size.times do
|
|
13
|
-
hashes <<
|
|
15
|
+
hashes << Bitcoin.read_exact(buf, 32).bth
|
|
14
16
|
end
|
|
15
|
-
new(ver, hashes,
|
|
17
|
+
new(ver, hashes, Bitcoin.read_exact(buf, 32).bth)
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def to_payload
|
|
@@ -6,10 +6,11 @@ module Bitcoin
|
|
|
6
6
|
|
|
7
7
|
def parse_from_payload(payload)
|
|
8
8
|
size, payload = Bitcoin.unpack_var_int(payload)
|
|
9
|
+
raise Bitcoin::Message::Error, "#{self::COMMAND} message size #{size} exceeds #{MAX_INV_SZ}." if size > MAX_INV_SZ
|
|
9
10
|
buf = StringIO.new(payload)
|
|
10
11
|
i = new
|
|
11
12
|
size.times do
|
|
12
|
-
i.inventories << Inventory.parse_from_payload(
|
|
13
|
+
i.inventories << Inventory.parse_from_payload(Bitcoin.read_exact(buf, 36))
|
|
13
14
|
end
|
|
14
15
|
i
|
|
15
16
|
end
|
|
@@ -10,6 +10,7 @@ module Bitcoin
|
|
|
10
10
|
MSG_BLOCK = 2
|
|
11
11
|
MSG_FILTERED_BLOCK = 3
|
|
12
12
|
MSG_CMPCT_BLOCK = 4
|
|
13
|
+
MSG_WTX = 5 # BIP-339, the hash is a wtxid.
|
|
13
14
|
MSG_WITNESS_TX = SEGWIT_FLAG | MSG_TX
|
|
14
15
|
MSG_WITNESS_BLOCK = SEGWIT_FLAG | MSG_BLOCK
|
|
15
16
|
MSG_FILTERED_WITNESS_BLOCK = SEGWIT_FLAG | MSG_FILTERED_BLOCK
|
|
@@ -17,8 +18,17 @@ module Bitcoin
|
|
|
17
18
|
attr_accessor :identifier
|
|
18
19
|
attr_accessor :hash
|
|
19
20
|
|
|
21
|
+
KNOWN_IDENTIFIERS = [MSG_TX, MSG_BLOCK, MSG_FILTERED_BLOCK, MSG_CMPCT_BLOCK, MSG_WTX, MSG_WITNESS_TX,
|
|
22
|
+
MSG_WITNESS_BLOCK, MSG_FILTERED_WITNESS_BLOCK].freeze
|
|
23
|
+
|
|
24
|
+
# A type which is not known is kept as it is, and is left for the receiver to ignore, as Bitcoin Core does.
|
|
25
|
+
# @param [Integer] identifier The type of the inventory, a 32 bit unsigned integer.
|
|
26
|
+
# @param [String] hash The hash with hex format.
|
|
27
|
+
# @raise [Bitcoin::Message::Error] If +identifier+ is not a 32 bit unsigned integer.
|
|
20
28
|
def initialize(identifier, hash)
|
|
21
|
-
|
|
29
|
+
unless identifier.is_a?(Integer) && identifier.between?(0, 0xffffffff)
|
|
30
|
+
raise Error, "invalid type identifier specified. identifier = #{identifier}"
|
|
31
|
+
end
|
|
22
32
|
@identifier = identifier
|
|
23
33
|
@hash = hash
|
|
24
34
|
end
|
|
@@ -39,11 +49,10 @@ module Bitcoin
|
|
|
39
49
|
[MSG_BLOCK, MSG_WITNESS_BLOCK, MSG_FILTERED_WITNESS_BLOCK].include?(identifier)
|
|
40
50
|
end
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
MSG_WITNESS_BLOCK, MSG_FILTERED_WITNESS_BLOCK].include?(identifier)
|
|
52
|
+
# Whether the type of this inventory is one this library knows.
|
|
53
|
+
# @return [Boolean]
|
|
54
|
+
def known?
|
|
55
|
+
KNOWN_IDENTIFIERS.include?(identifier)
|
|
47
56
|
end
|
|
48
57
|
|
|
49
58
|
end
|
|
@@ -19,15 +19,15 @@ module Bitcoin
|
|
|
19
19
|
def self.parse_from_payload(payload)
|
|
20
20
|
m = new
|
|
21
21
|
buf = StringIO.new(payload)
|
|
22
|
-
m.header = Bitcoin::BlockHeader.parse_from_payload(
|
|
23
|
-
m.tx_count =
|
|
22
|
+
m.header = Bitcoin::BlockHeader.parse_from_payload(Bitcoin.read_exact(buf, 80))
|
|
23
|
+
m.tx_count = Bitcoin.read_exact(buf, 4).unpack1('V')
|
|
24
24
|
hash_count = Bitcoin.unpack_var_int_from_io(buf)
|
|
25
25
|
hash_count.times do
|
|
26
|
-
m.hashes <<
|
|
26
|
+
m.hashes << Bitcoin.read_exact(buf, 32).bth
|
|
27
27
|
end
|
|
28
28
|
flag_count = Bitcoin.unpack_var_int_from_io(buf)
|
|
29
29
|
# A sequence of bits packed eight in a byte with the least significant bit first.
|
|
30
|
-
m.flags =
|
|
30
|
+
m.flags = Bitcoin.read_exact(buf, flag_count).bth
|
|
31
31
|
# Reject a tree an untrusted peer could use to exhaust memory or CPU in #partial_tree.
|
|
32
32
|
begin
|
|
33
33
|
Bitcoin::PartialTree.validate!(m.tx_count, m.hashes, Bitcoin.byte_to_bit(m.flags.htb))
|
|
@@ -106,46 +106,46 @@ module Bitcoin
|
|
|
106
106
|
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
|
107
107
|
has_time = buf.size > 26
|
|
108
108
|
addr = NetworkAddr.new(time: nil)
|
|
109
|
-
addr.time =
|
|
110
|
-
addr.services =
|
|
111
|
-
addr.addr = IPAddr::new_ntoh(
|
|
112
|
-
addr.port =
|
|
109
|
+
addr.time = Bitcoin.read_exact(buf, 4).unpack1('V') if has_time
|
|
110
|
+
addr.services = Bitcoin.read_exact(buf, 8).unpack1('Q<')
|
|
111
|
+
addr.addr = IPAddr::new_ntoh(Bitcoin.read_exact(buf, 16))
|
|
112
|
+
addr.port = Bitcoin.read_exact(buf, 2).unpack1('n')
|
|
113
113
|
addr
|
|
114
114
|
end
|
|
115
115
|
|
|
116
116
|
# Load addr payload with addr v2 format.
|
|
117
117
|
def self.load_addr_v2_payload(payload)
|
|
118
118
|
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
|
119
|
-
addr = NetworkAddr.new(time:
|
|
119
|
+
addr = NetworkAddr.new(time: Bitcoin.read_exact(buf, 4).unpack1('V'))
|
|
120
120
|
addr.services = Bitcoin.unpack_var_int_from_io(buf)
|
|
121
|
-
addr.net =
|
|
121
|
+
addr.net = Bitcoin.read_exact(buf, 1).unpack1('C')
|
|
122
122
|
raise Bitcoin::Message::Error, "Unknown network id: #{addr.net}" unless NETWORK_ID.value?(addr.net)
|
|
123
123
|
addr_len = Bitcoin.unpack_var_int_from_io(buf)
|
|
124
124
|
addr.addr = case addr.net
|
|
125
125
|
when NETWORK_ID[:ipv4]
|
|
126
126
|
raise Bitcoin::Message::Error, "Invalid IPv4 address." unless addr_len == 4
|
|
127
|
-
IPAddr::new_ntoh(
|
|
127
|
+
IPAddr::new_ntoh(Bitcoin.read_exact(buf, addr_len))
|
|
128
128
|
when NETWORK_ID[:ipv6]
|
|
129
129
|
raise Bitcoin::Message::Error, "Invalid IPv6 address." unless addr_len == 16
|
|
130
|
-
a = IPAddr::new_ntoh(
|
|
130
|
+
a = IPAddr::new_ntoh(Bitcoin.read_exact(buf, addr_len))
|
|
131
131
|
raise Bitcoin::Message::Error, "Invalid IPv6 address." if a.ipv4_mapped?
|
|
132
132
|
a
|
|
133
133
|
when NETWORK_ID[:tor_v2]
|
|
134
134
|
raise Bitcoin::Message::Error, "Invalid Tor v2 address." unless addr_len == 10
|
|
135
|
-
|
|
135
|
+
Bitcoin.read_exact(buf, addr_len).bth
|
|
136
136
|
when NETWORK_ID[:tor_v3]
|
|
137
137
|
raise Bitcoin::Message::Error, "Invalid Tor v3 address." unless addr_len == 32
|
|
138
|
-
|
|
138
|
+
Bitcoin.read_exact(buf, addr_len).bth
|
|
139
139
|
when NETWORK_ID[:i2p]
|
|
140
140
|
raise Bitcoin::Message::Error, "Invalid I2P address." unless addr_len == 32
|
|
141
|
-
|
|
141
|
+
Bitcoin.read_exact(buf, addr_len).bth
|
|
142
142
|
when NETWORK_ID[:cjdns]
|
|
143
143
|
raise Bitcoin::Message::Error, "Invalid CJDNS address." unless addr_len == 16
|
|
144
|
-
a = IPAddr::new_ntoh(
|
|
144
|
+
a = IPAddr::new_ntoh(Bitcoin.read_exact(buf, addr_len))
|
|
145
145
|
raise Bitcoin::Message::Error, "Invalid CJDNS address." unless a.to_s.start_with?('fc00:')
|
|
146
146
|
a
|
|
147
147
|
end
|
|
148
|
-
addr.port =
|
|
148
|
+
addr.port = Bitcoin.read_exact(buf, 2).unpack1('n')
|
|
149
149
|
addr
|
|
150
150
|
end
|
|
151
151
|
|