bitcoinrb 1.14.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 +76 -17
- 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 +67 -15
- 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 +42 -12
- data/lib/bitcoin/slip39/sss.rb +33 -12
- 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)
|
|
@@ -192,10 +193,17 @@ module Bitcoin
|
|
|
192
193
|
Bitcoin::Script.to_p2wpkh(hash160).to_addr
|
|
193
194
|
end
|
|
194
195
|
|
|
195
|
-
# Get pay to taproot address
|
|
196
|
+
# Get pay to taproot address.
|
|
197
|
+
# By default the x-only public key of this key becomes the output key as it is, which is
|
|
198
|
+
# what the rawtr() descriptor does. With +as_internal+ this key is the internal key P and
|
|
199
|
+
# the output key is Q = P + hash_TapTweak(P)G, which is what the tr() descriptor does and
|
|
200
|
+
# what BIP-86 defines for a single key address. Spending such an output needs the tweaked
|
|
201
|
+
# private key, see Bitcoin::Taproot.tweak_private_key.
|
|
202
|
+
# @param [Boolean] as_internal Whether to treat this key as the internal key. Default is false.
|
|
196
203
|
# @return [String] address
|
|
197
|
-
def to_p2tr
|
|
198
|
-
Bitcoin::
|
|
204
|
+
def to_p2tr(as_internal: false)
|
|
205
|
+
output_key = as_internal ? Bitcoin::Taproot.tweak_public_key(self, '') : self
|
|
206
|
+
Bitcoin::Script.to_p2tr(output_key).to_addr
|
|
199
207
|
end
|
|
200
208
|
|
|
201
209
|
# get p2wpkh address nested in p2sh.
|
|
@@ -352,7 +360,11 @@ module Bitcoin
|
|
|
352
360
|
if secp256k1_module.native?
|
|
353
361
|
Bitcoin::BIP324::EllSwiftPubkey.new(secp256k1_module.ellswift_create(priv_key))
|
|
354
362
|
else
|
|
355
|
-
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)
|
|
356
368
|
end
|
|
357
369
|
end
|
|
358
370
|
|
|
@@ -387,22 +399,69 @@ module Bitcoin
|
|
|
387
399
|
MIN_PRIV_KEY_MOD_ORDER <= value && value <= MAX_PRIV_KEY_MOD_ORDER
|
|
388
400
|
end
|
|
389
401
|
|
|
390
|
-
#
|
|
391
|
-
#
|
|
392
|
-
#
|
|
393
|
-
#
|
|
394
|
-
# 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.
|
|
395
410
|
def ecdsa_signature_parse_der_lax(sig)
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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]
|
|
402
461
|
end
|
|
403
462
|
|
|
404
463
|
def valid_pubkey?
|
|
405
|
-
!pubkey.nil? && pubkey.size > 0
|
|
464
|
+
!pubkey.nil? && pubkey.size > 0 && pubkey.valid_hex?
|
|
406
465
|
end
|
|
407
466
|
|
|
408
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))
|