bitcoinrb 1.12.1 → 1.14.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 +2 -2
- data/lib/bitcoin/base58.rb +15 -3
- data/lib/bitcoin/bip321_uri.rb +3 -6
- data/lib/bitcoin/bip324/fs_chacha20.rb +1 -1
- data/lib/bitcoin/bip324/fs_chacha_poly1305.rb +1 -0
- data/lib/bitcoin/bip324.rb +6 -1
- data/lib/bitcoin/block.rb +3 -3
- data/lib/bitcoin/descriptor/checksum.rb +6 -2
- data/lib/bitcoin/ext_key.rb +8 -4
- data/lib/bitcoin/key.rb +2 -2
- data/lib/bitcoin/message/base.rb +7 -1
- data/lib/bitcoin/message/cf_parser.rb +2 -2
- data/lib/bitcoin/message/fee_filter.rb +2 -2
- data/lib/bitcoin/message/filter_load.rb +10 -0
- data/lib/bitcoin/message/header_and_short_ids.rb +4 -4
- data/lib/bitcoin/message/inventory.rb +1 -1
- data/lib/bitcoin/message/merkle_block.rb +6 -0
- data/lib/bitcoin/message/network_addr.rb +7 -3
- data/lib/bitcoin/message/ping.rb +2 -2
- data/lib/bitcoin/message/pong.rb +2 -2
- data/lib/bitcoin/message/send_cmpct.rb +2 -2
- data/lib/bitcoin/message/version.rb +3 -3
- data/lib/bitcoin/message.rb +15 -2
- data/lib/bitcoin/mnemonic.rb +9 -4
- data/lib/bitcoin/partial_tree.rb +25 -0
- data/lib/bitcoin/psbt/input.rb +58 -33
- data/lib/bitcoin/psbt/key_origin_info.rb +2 -2
- data/lib/bitcoin/psbt/output.rb +9 -5
- data/lib/bitcoin/psbt/tx.rb +22 -15
- data/lib/bitcoin/psbt.rb +1 -1
- data/lib/bitcoin/rpc/bitcoin_core_client.rb +3 -2
- data/lib/bitcoin/script/script.rb +15 -5
- data/lib/bitcoin/script/script_interpreter.rb +1 -1
- data/lib/bitcoin/script/tx_checker.rb +7 -2
- data/lib/bitcoin/secp256k1/native.rb +50 -1
- data/lib/bitcoin/secp256k1/rfc6979.rb +6 -3
- data/lib/bitcoin/secp256k1/ruby.rb +115 -2
- data/lib/bitcoin/sighash_generator.rb +8 -2
- data/lib/bitcoin/silent_payment.rb +56 -90
- data/lib/bitcoin/taproot/control_block.rb +1 -0
- data/lib/bitcoin/taproot/custom_depth_builder.rb +2 -2
- data/lib/bitcoin/taproot/simple_builder.rb +9 -7
- data/lib/bitcoin/taproot.rb +6 -1
- data/lib/bitcoin/tx.rb +8 -6
- data/lib/bitcoin/tx_out.rb +2 -2
- data/lib/bitcoin/util.rb +4 -4
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/account.rb +2 -2
- data/lib/bitcoin/wallet/db.rb +3 -3
- data/lib/bitcoin/wallet/master_key.rb +83 -24
- data/lib/bitcoin/wallet/utxo.rb +2 -2
- data/lib/bitcoin.rb +1 -0
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 360a1a8533df9b68228d18279909e7bc5f45d4d5679caa1a02cda75e0a9140d0
|
|
4
|
+
data.tar.gz: ff86fcec32b3fe1394eea043080aaedafee61e724ec689cde5efdd7b9ca5c307
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9aeb17cc9dc42ce33c4c31ed1f6424569d5c0f4f20627015c930d11e5ba2bf9c15f43cd20a666257caba689ffc0010b08b5855c13a3cf61a724c05fe8bcd383d
|
|
7
|
+
data.tar.gz: 14848d9c37a69ec5b0d2f993ce92c3cad3bdfc6275151789ff768ad5989d101f9c5545f1dc668d372f847b574fdc60d10e5f90b81b377b23ac20bfb3705b0a3a
|
data/bitcoinrb.gemspec
CHANGED
|
@@ -27,8 +27,8 @@ Gem::Specification.new do |spec|
|
|
|
27
27
|
spec.add_runtime_dependency 'bip-schnorr', '>= 0.7.0'
|
|
28
28
|
spec.add_runtime_dependency 'base32', '>= 0.3.4'
|
|
29
29
|
spec.add_runtime_dependency 'base64', '~> 0.2.0'
|
|
30
|
-
spec.add_runtime_dependency 'secp256k1rb', '0.
|
|
30
|
+
spec.add_runtime_dependency 'secp256k1rb', '0.8.0'
|
|
31
31
|
spec.add_runtime_dependency 'logger'
|
|
32
|
-
spec.add_runtime_dependency 'merkle', '0.
|
|
32
|
+
spec.add_runtime_dependency 'merkle', '1.0.0'
|
|
33
33
|
spec.add_runtime_dependency 'dnsruby', '1.73.0'
|
|
34
34
|
end
|
data/lib/bitcoin/base58.rb
CHANGED
|
@@ -8,6 +8,10 @@ module Bitcoin
|
|
|
8
8
|
ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
|
9
9
|
SIZE = ALPHABET.size
|
|
10
10
|
|
|
11
|
+
# Upper bound for #decode. Decoding is quadratic in the length of the input, and the
|
|
12
|
+
# longest value Bitcoin encodes with Base58 is an extended key, at 111 characters.
|
|
13
|
+
MAX_LENGTH = 256
|
|
14
|
+
|
|
11
15
|
# encode hex value to base58 string.
|
|
12
16
|
def encode(hex)
|
|
13
17
|
leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2
|
|
@@ -21,11 +25,19 @@ module Bitcoin
|
|
|
21
25
|
end
|
|
22
26
|
|
|
23
27
|
# decode base58 string to hex value.
|
|
28
|
+
# @param [String] base58_val Base58 string.
|
|
29
|
+
# @return [String] Decoded value with hex format.
|
|
30
|
+
# @raise [ArgumentError] If +base58_val+ is longer than MAX_LENGTH or holds a character
|
|
31
|
+
# which is not in the alphabet.
|
|
24
32
|
def decode(base58_val)
|
|
33
|
+
if base58_val.length > MAX_LENGTH
|
|
34
|
+
raise ArgumentError, "Base58 string must not be longer than #{MAX_LENGTH} characters."
|
|
35
|
+
end
|
|
25
36
|
int_val = 0
|
|
26
|
-
base58_val.
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
base58_val.each_char do |char|
|
|
38
|
+
char_index = ALPHABET.index(char)
|
|
39
|
+
raise ArgumentError, 'Value passed not a valid Base58 String.' if char_index.nil?
|
|
40
|
+
int_val = int_val * SIZE + char_index
|
|
29
41
|
end
|
|
30
42
|
s = int_val.to_even_length_hex
|
|
31
43
|
s = '' if s == '00'
|
data/lib/bitcoin/bip321_uri.rb
CHANGED
|
@@ -105,7 +105,7 @@ module Bitcoin
|
|
|
105
105
|
req_pop = true
|
|
106
106
|
k = 'pop'
|
|
107
107
|
end
|
|
108
|
-
if %w[bc tb].include?(k.downcase)
|
|
108
|
+
if %w[bc tb bcrt].include?(k.downcase)
|
|
109
109
|
raise ArgumentError, "#{k} not allowed in current network." unless Bitcoin.chain_params.bech32_hrp == k.downcase
|
|
110
110
|
query_addrs << v
|
|
111
111
|
nil
|
|
@@ -160,12 +160,9 @@ module Bitcoin
|
|
|
160
160
|
all_params = base_params.merge other_params
|
|
161
161
|
params = all_params.map do |k, v|
|
|
162
162
|
"#{k}=#{CGI.escape(v).gsub('+', '%20')}"
|
|
163
|
-
end.join('&')
|
|
164
|
-
uri << "?#{params}" unless params.empty?
|
|
165
|
-
unless query_addrs.empty?
|
|
166
|
-
uri << '?' unless uri.include?('?')
|
|
167
|
-
uri << query_addrs.map {|addr| "#{Bitcoin.chain_params.bech32_hrp}=#{addr}"}.join('&')
|
|
168
163
|
end
|
|
164
|
+
params += query_addrs.map {|addr| "#{Bitcoin.chain_params.bech32_hrp}=#{addr}"}
|
|
165
|
+
uri << "?#{params.join('&')}" unless params.empty?
|
|
169
166
|
uri
|
|
170
167
|
end
|
|
171
168
|
end
|
|
@@ -101,7 +101,7 @@ module Bitcoin
|
|
|
101
101
|
|
|
102
102
|
def key_stream_bytes(n_bytes)
|
|
103
103
|
while key_stream.bytesize < n_bytes
|
|
104
|
-
nonce = [0, (chunk_counter /
|
|
104
|
+
nonce = [0, (chunk_counter / rekey_interval)].pack("VQ<")
|
|
105
105
|
self.key_stream << ChaCha20.block(key, nonce, block_counter)
|
|
106
106
|
self.block_counter += 1
|
|
107
107
|
end
|
data/lib/bitcoin/bip324.rb
CHANGED
|
@@ -10,7 +10,9 @@ module Bitcoin
|
|
|
10
10
|
|
|
11
11
|
autoload :EllSwiftPubkey, 'bitcoin/bip324/ell_swift_pubkey'
|
|
12
12
|
autoload :Cipher, 'bitcoin/bip324/cipher'
|
|
13
|
+
autoload :ChaCha20, 'bitcoin/bip324/fs_chacha20'
|
|
13
14
|
autoload :FSChaCha20, 'bitcoin/bip324/fs_chacha20'
|
|
15
|
+
autoload :Poly1305, 'bitcoin/bip324/fs_chacha_poly1305'
|
|
14
16
|
autoload :FSChaCha20Poly1305, 'bitcoin/bip324/fs_chacha_poly1305'
|
|
15
17
|
|
|
16
18
|
FIELD_SIZE = 2**256 - 2**32 - 977
|
|
@@ -89,13 +91,16 @@ module Bitcoin
|
|
|
89
91
|
ECDSA::Format::IntegerOctetString.encode(result, 32).bth
|
|
90
92
|
end
|
|
91
93
|
|
|
94
|
+
# Number of cases xswiftec_inv selects between. It reads c as 3 bits, c & 1, c & 2 and c & 4.
|
|
95
|
+
CASE_COUNT = 8
|
|
96
|
+
|
|
92
97
|
# Given a field element X on the curve, find (u, t) that encode them.
|
|
93
98
|
# @param [String] x coordinate with hex format.
|
|
94
99
|
# @return [String] ElligatorSwift public key with hex format.
|
|
95
100
|
def xelligatorswift(x)
|
|
96
101
|
loop do
|
|
97
102
|
u = SecureRandom.random_number(1..ECDSA::Group::Secp256k1.order).to_s(16)
|
|
98
|
-
c = SecureRandom.random_number(
|
|
103
|
+
c = SecureRandom.random_number(CASE_COUNT)
|
|
99
104
|
t = xswiftec_inv(x, u, c)
|
|
100
105
|
unless t.nil?
|
|
101
106
|
return (ECDSA::Format::IntegerOctetString.encode(u.hex, 32) +
|
data/lib/bitcoin/block.rb
CHANGED
|
@@ -28,7 +28,7 @@ module Bitcoin
|
|
|
28
28
|
header = BlockHeader.new(
|
|
29
29
|
version,
|
|
30
30
|
'00' * 32,
|
|
31
|
-
Merkle::BinaryTree.new(config: Merkle::Config.bitcoin, leaves: [coinbase.txid]).compute_root.rhex,
|
|
31
|
+
Merkle::BinaryTree.new(config: Merkle::Config.bitcoin(element_encoding: :hex), leaves: [coinbase.txid]).compute_root.rhex,
|
|
32
32
|
time,
|
|
33
33
|
bits,
|
|
34
34
|
nonce
|
|
@@ -72,7 +72,7 @@ module Bitcoin
|
|
|
72
72
|
|
|
73
73
|
# calculate merkle root from tx list.
|
|
74
74
|
def calculate_merkle_root
|
|
75
|
-
tree = Merkle::BinaryTree.new(config: Merkle::Config.bitcoin, leaves: transactions.map(&:tx_hash))
|
|
75
|
+
tree = Merkle::BinaryTree.new(config: Merkle::Config.bitcoin(element_encoding: :hex), leaves: transactions.map(&:tx_hash))
|
|
76
76
|
tree.compute_root
|
|
77
77
|
end
|
|
78
78
|
|
|
@@ -86,7 +86,7 @@ module Bitcoin
|
|
|
86
86
|
witness_hashes = [COINBASE_WTXID]
|
|
87
87
|
witness_hashes += (transactions[1..-1].map(&:witness_hash))
|
|
88
88
|
reserved_value = transactions[0].inputs[0].script_witness.stack.map(&:bth).join
|
|
89
|
-
tree = Merkle::BinaryTree.new(config: Merkle::Config.bitcoin, leaves: witness_hashes)
|
|
89
|
+
tree = Merkle::BinaryTree.new(config: Merkle::Config.bitcoin(element_encoding: :hex), leaves: witness_hashes)
|
|
90
90
|
root_hash = tree.compute_root
|
|
91
91
|
Bitcoin.double_sha256([root_hash + reserved_value].pack('H*')).bth
|
|
92
92
|
end
|
|
@@ -18,7 +18,9 @@ module Bitcoin
|
|
|
18
18
|
s[-8..-1].each_char do |c|
|
|
19
19
|
return false unless CHECKSUM_CHARSET.include?(c)
|
|
20
20
|
end
|
|
21
|
-
|
|
21
|
+
expanded = descsum_expand(s[0...-9])
|
|
22
|
+
return false unless expanded
|
|
23
|
+
symbols = expanded + s[-8..-1].each_char.map{|c|CHECKSUM_CHARSET.index(c)}
|
|
22
24
|
descsum_polymod(symbols) == 1
|
|
23
25
|
end
|
|
24
26
|
|
|
@@ -26,7 +28,9 @@ module Bitcoin
|
|
|
26
28
|
# @param [String] s Descriptor string without checksum.
|
|
27
29
|
# @return [String] Descriptor string with checksum.
|
|
28
30
|
def descsum_create(s)
|
|
29
|
-
|
|
31
|
+
expanded = descsum_expand(s)
|
|
32
|
+
raise ArgumentError, 'Descriptor contains a character which is not in the input charset.' unless expanded
|
|
33
|
+
symbols = expanded + [0, 0, 0, 0, 0, 0, 0, 0]
|
|
30
34
|
checksum = descsum_polymod(symbols) ^ 1
|
|
31
35
|
result = 8.times.map do |i|
|
|
32
36
|
CHECKSUM_CHARSET[(checksum >> (5 * (7 - i))) & 31]
|
data/lib/bitcoin/ext_key.rb
CHANGED
|
@@ -114,14 +114,16 @@ module Bitcoin
|
|
|
114
114
|
child_priv = ECDSA::Format::IntegerOctetString.encode(child_priv, 32)
|
|
115
115
|
new_key.key = Bitcoin::Key.new(priv_key: child_priv.bth, key_type: key_type)
|
|
116
116
|
new_key.chain_code = l[32..-1]
|
|
117
|
-
|
|
117
|
+
# A depth 1 key derives its version bytes from the purpose, see #version.
|
|
118
|
+
new_key.ver = new_key.depth == 1 ? nil : version
|
|
118
119
|
new_key
|
|
119
120
|
end
|
|
120
121
|
|
|
121
122
|
# get version bytes using serialization format
|
|
122
123
|
def version
|
|
124
|
+
return ver if ver
|
|
123
125
|
return ExtKey.version_from_purpose(number) if depth == 1
|
|
124
|
-
|
|
126
|
+
Bitcoin.chain_params.extended_privkey_version
|
|
125
127
|
end
|
|
126
128
|
|
|
127
129
|
# get key type defined by BIP-178 using version.
|
|
@@ -286,14 +288,16 @@ module Bitcoin
|
|
|
286
288
|
p2 = Bitcoin::Key.new(pubkey: pubkey, key_type: key_type).to_point
|
|
287
289
|
new_key.pubkey = (p1 + p2).to_hex
|
|
288
290
|
new_key.chain_code = l[32..-1]
|
|
289
|
-
|
|
291
|
+
# A depth 1 key derives its version bytes from the purpose, see #version.
|
|
292
|
+
new_key.ver = new_key.depth == 1 ? nil : version
|
|
290
293
|
new_key
|
|
291
294
|
end
|
|
292
295
|
|
|
293
296
|
# get version bytes using serialization format
|
|
294
297
|
def version
|
|
298
|
+
return ver if ver
|
|
295
299
|
return ExtPubkey.version_from_purpose(number) if depth == 1
|
|
296
|
-
|
|
300
|
+
Bitcoin.chain_params.extended_pubkey_version
|
|
297
301
|
end
|
|
298
302
|
|
|
299
303
|
# get key type defined by BIP-178 using version.
|
data/lib/bitcoin/key.rb
CHANGED
|
@@ -120,7 +120,7 @@ module Bitcoin
|
|
|
120
120
|
if low_r && !sig_has_low_r?(sig)
|
|
121
121
|
counter = 1
|
|
122
122
|
until sig_has_low_r?(sig)
|
|
123
|
-
extra_entropy = [counter].pack('
|
|
123
|
+
extra_entropy = [counter].pack('V').bth.ljust(64, '0').htb
|
|
124
124
|
sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
|
|
125
125
|
counter += 1
|
|
126
126
|
end
|
|
@@ -170,7 +170,7 @@ module Bitcoin
|
|
|
170
170
|
else
|
|
171
171
|
false
|
|
172
172
|
end
|
|
173
|
-
rescue
|
|
173
|
+
rescue StandardError
|
|
174
174
|
false
|
|
175
175
|
end
|
|
176
176
|
end
|
data/lib/bitcoin/message/base.rb
CHANGED
|
@@ -32,11 +32,17 @@ module Bitcoin
|
|
|
32
32
|
magic = buf.read(4)
|
|
33
33
|
raise ArgumentError, 'Invalid magic.' unless magic == Bitcoin.chain_params.magic_head.htb
|
|
34
34
|
command = buf.read(12).delete("\x00")
|
|
35
|
+
# The length is announced by the peer, so it is checked before it is used to read.
|
|
35
36
|
length = buf.read(4).unpack1('V')
|
|
37
|
+
if length > MAX_PROTOCOL_MESSAGE_LENGTH
|
|
38
|
+
raise ArgumentError, "Payload must not be longer than #{MAX_PROTOCOL_MESSAGE_LENGTH} bytes."
|
|
39
|
+
end
|
|
36
40
|
checksum = buf.read(4)
|
|
41
|
+
# read returns "" for a length of 0 and nil once the buffer is exhausted.
|
|
37
42
|
payload = buf.read(length)
|
|
43
|
+
raise ArgumentError, 'Payload is shorter than the announced length.' unless payload&.bytesize == length
|
|
38
44
|
raise ArgumentError, 'Checksum do not match.' unless checksum == Bitcoin.double_sha256(payload)[0...4]
|
|
39
|
-
Bitcoin::Message.decode(command, payload
|
|
45
|
+
Bitcoin::Message.decode(command, payload.bth)
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
end
|
|
@@ -3,12 +3,12 @@ module Bitcoin
|
|
|
3
3
|
module CFParser
|
|
4
4
|
|
|
5
5
|
def parse_from_payload(payload)
|
|
6
|
-
type, start, hash = payload.unpack('
|
|
6
|
+
type, start, hash = payload.unpack('CVH*')
|
|
7
7
|
self.new(type, start, hash)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def to_payload
|
|
11
|
-
[filter_type, start_height, stop_hash].pack('
|
|
11
|
+
[filter_type, start_height, stop_hash].pack('CVH*')
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
end
|
|
@@ -22,8 +22,18 @@ module Bitcoin
|
|
|
22
22
|
def self.parse_from_payload(payload)
|
|
23
23
|
buf = StringIO.new(payload)
|
|
24
24
|
filter_count = Bitcoin.unpack_var_int_from_io(buf)
|
|
25
|
+
# A filterload message is received from an untrusted peer, so the limits which bound
|
|
26
|
+
# the work BloomFilter#add and #contains? perform have to be enforced here.
|
|
27
|
+
if filter_count > Bitcoin::BloomFilter::MAX_BLOOM_FILTER_SIZE
|
|
28
|
+
raise Bitcoin::Message::Error,
|
|
29
|
+
"filter size must be less than or equal to #{Bitcoin::BloomFilter::MAX_BLOOM_FILTER_SIZE}."
|
|
30
|
+
end
|
|
25
31
|
filter = buf.read(filter_count).unpack('C*')
|
|
26
32
|
func_count = buf.read(4).unpack1('V')
|
|
33
|
+
if func_count > Bitcoin::BloomFilter::MAX_HASH_FUNCS
|
|
34
|
+
raise Bitcoin::Message::Error,
|
|
35
|
+
"hash funcs must be less than or equal to #{Bitcoin::BloomFilter::MAX_HASH_FUNCS}."
|
|
36
|
+
end
|
|
27
37
|
tweak = buf.read(4).unpack1('V')
|
|
28
38
|
flag = buf.read(1).unpack1('C')
|
|
29
39
|
FilterLoad.new(Bitcoin::BloomFilter.new(filter, func_count, tweak), flag)
|
|
@@ -16,13 +16,13 @@ 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('q
|
|
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
24
|
header = Bitcoin::BlockHeader.parse_from_payload(buf.read(80))
|
|
25
|
-
nonce = buf.read(8).unpack1('q
|
|
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)
|
|
@@ -36,9 +36,9 @@ module Bitcoin
|
|
|
36
36
|
|
|
37
37
|
def to_payload
|
|
38
38
|
p = header.to_payload
|
|
39
|
-
p << [nonce].pack('q
|
|
39
|
+
p << [nonce].pack('q<*')
|
|
40
40
|
p << Bitcoin.pack_var_int(short_ids.size)
|
|
41
|
-
p << short_ids.map{|id|sprintf('%
|
|
41
|
+
p << short_ids.map{|id|sprintf('%012x', id).htb.reverse}.join
|
|
42
42
|
p << Bitcoin.pack_var_int(prefilled_txn.size)
|
|
43
43
|
p << prefilled_txn.map(&:to_payload).join
|
|
44
44
|
p
|
|
@@ -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..
|
|
29
|
+
identifier = payload[0..3].unpack1('V')
|
|
30
30
|
hash = payload[4..-1].bth # internal byte order
|
|
31
31
|
new(identifier, hash)
|
|
32
32
|
end
|
|
@@ -28,6 +28,12 @@ module Bitcoin
|
|
|
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
30
|
m.flags = buf.read(flag_count).bth
|
|
31
|
+
# Reject a tree an untrusted peer could use to exhaust memory or CPU in #partial_tree.
|
|
32
|
+
begin
|
|
33
|
+
Bitcoin::PartialTree.validate!(m.tx_count, m.hashes, Bitcoin.byte_to_bit(m.flags.htb))
|
|
34
|
+
rescue ArgumentError => e
|
|
35
|
+
raise Bitcoin::Message::Error, e.message
|
|
36
|
+
end
|
|
31
37
|
m
|
|
32
38
|
end
|
|
33
39
|
|
|
@@ -107,7 +107,7 @@ module Bitcoin
|
|
|
107
107
|
has_time = buf.size > 26
|
|
108
108
|
addr = NetworkAddr.new(time: nil)
|
|
109
109
|
addr.time = buf.read(4).unpack1('V') if has_time
|
|
110
|
-
addr.services = buf.read(8).unpack1('Q')
|
|
110
|
+
addr.services = buf.read(8).unpack1('Q<')
|
|
111
111
|
addr.addr = IPAddr::new_ntoh(buf.read(16))
|
|
112
112
|
addr.port = buf.read(2).unpack1('n')
|
|
113
113
|
addr
|
|
@@ -153,7 +153,7 @@ module Bitcoin
|
|
|
153
153
|
p = ''
|
|
154
154
|
p << [time].pack('V') unless skip_time
|
|
155
155
|
ip = addr.ipv4? ? addr.ipv4_mapped : addr
|
|
156
|
-
p << [services].pack('Q') << ip.hton << [port].pack('n')
|
|
156
|
+
p << [services].pack('Q<') << ip.hton << [port].pack('n')
|
|
157
157
|
end
|
|
158
158
|
|
|
159
159
|
def v2_payload
|
|
@@ -163,18 +163,22 @@ module Bitcoin
|
|
|
163
163
|
case net
|
|
164
164
|
when NETWORK_ID[:ipv4]
|
|
165
165
|
p << Bitcoin.pack_var_int(4)
|
|
166
|
-
p << addr.
|
|
166
|
+
p << addr.hton
|
|
167
167
|
when NETWORK_ID[:ipv6]
|
|
168
168
|
p << Bitcoin.pack_var_int(16)
|
|
169
169
|
p << addr.hton
|
|
170
170
|
when NETWORK_ID[:tor_v2]
|
|
171
171
|
p << Bitcoin.pack_var_int(10)
|
|
172
|
+
p << addr.htb
|
|
172
173
|
when NETWORK_ID[:tor_v3]
|
|
173
174
|
p << Bitcoin.pack_var_int(32)
|
|
175
|
+
p << addr.htb
|
|
174
176
|
when NETWORK_ID[:i2p]
|
|
175
177
|
p << Bitcoin.pack_var_int(32)
|
|
178
|
+
p << addr.htb
|
|
176
179
|
when NETWORK_ID[:cjdns]
|
|
177
180
|
p << Bitcoin.pack_var_int(16)
|
|
181
|
+
p << addr.hton
|
|
178
182
|
end
|
|
179
183
|
p << [port].pack('n')
|
|
180
184
|
p
|
data/lib/bitcoin/message/ping.rb
CHANGED
|
@@ -14,11 +14,11 @@ module Bitcoin
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def self.parse_from_payload(payload)
|
|
17
|
-
new(payload.unpack1('Q'))
|
|
17
|
+
new(payload.unpack1('Q<'))
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def to_payload
|
|
21
|
-
nonce ? [nonce].pack('Q') : ''
|
|
21
|
+
nonce ? [nonce].pack('Q<') : ''
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# Generate pong message as a response. Return nil if the ping has no nonce
|
data/lib/bitcoin/message/pong.rb
CHANGED
|
@@ -22,12 +22,12 @@ module Bitcoin
|
|
|
22
22
|
def self.parse_from_payload(payload)
|
|
23
23
|
buf = StringIO.new(payload)
|
|
24
24
|
mode = buf.read(1).unpack1('c')
|
|
25
|
-
version = buf.read(8).unpack1('Q')
|
|
25
|
+
version = buf.read(8).unpack1('Q<')
|
|
26
26
|
new(mode, version)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def to_payload
|
|
30
|
-
[mode, version].pack('cQ')
|
|
30
|
+
[mode, version].pack('cQ<')
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def high?
|
|
@@ -32,7 +32,7 @@ module Bitcoin
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def self.parse_from_payload(payload)
|
|
35
|
-
version, services, timestamp, local_addr, remote_addr, nonce, rest = payload.unpack('
|
|
35
|
+
version, services, timestamp, local_addr, remote_addr, nonce, rest = payload.unpack('VQ<Q<a26a26Q<a*')
|
|
36
36
|
v = new
|
|
37
37
|
v.version = version
|
|
38
38
|
v.services = services
|
|
@@ -50,10 +50,10 @@ module Bitcoin
|
|
|
50
50
|
|
|
51
51
|
def to_payload
|
|
52
52
|
[
|
|
53
|
-
[version, services, timestamp].pack('
|
|
53
|
+
[version, services, timestamp].pack('VQ<Q<'),
|
|
54
54
|
local_addr.to_payload(true),
|
|
55
55
|
remote_addr.to_payload(true),
|
|
56
|
-
[nonce].pack('Q'),
|
|
56
|
+
[nonce].pack('Q<'),
|
|
57
57
|
pack_var_string(user_agent),
|
|
58
58
|
[start_height].pack('V'),
|
|
59
59
|
pack_boolean(relay)
|
data/lib/bitcoin/message.rb
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
module Bitcoin
|
|
2
2
|
module Message
|
|
3
3
|
|
|
4
|
-
class Error < StandardError; end
|
|
5
|
-
|
|
6
4
|
autoload :Base, 'bitcoin/message/base'
|
|
7
5
|
autoload :Inventory, 'bitcoin/message/inventory'
|
|
8
6
|
autoload :InventoriesParser, 'bitcoin/message/inventories_parser'
|
|
@@ -53,6 +51,9 @@ module Bitcoin
|
|
|
53
51
|
|
|
54
52
|
USER_AGENT = "/bitcoinrb:#{Bitcoin::VERSION}/"
|
|
55
53
|
|
|
54
|
+
# The maximum payload size a peer may announce in a message header.
|
|
55
|
+
MAX_PROTOCOL_MESSAGE_LENGTH = 4_000_000
|
|
56
|
+
|
|
56
57
|
SERVICE_FLAGS = {
|
|
57
58
|
none: 0,
|
|
58
59
|
network: 1 << 0, # the node is capable of serving the block chain. It is currently set by all Bitcoin Core node, and is unset by SPV clients or other peers that just want network services but don't provide them.
|
|
@@ -107,6 +108,8 @@ module Bitcoin
|
|
|
107
108
|
Pong.parse_from_payload(payload)
|
|
108
109
|
when GetHeaders::COMMAND
|
|
109
110
|
GetHeaders.parse_from_payload(payload)
|
|
111
|
+
when GetBlocks::COMMAND
|
|
112
|
+
GetBlocks.parse_from_payload(payload)
|
|
110
113
|
when Headers::COMMAND
|
|
111
114
|
Headers.parse_from_payload(payload)
|
|
112
115
|
when Block::COMMAND
|
|
@@ -125,8 +128,18 @@ module Bitcoin
|
|
|
125
128
|
Inv.parse_from_payload(payload)
|
|
126
129
|
when MerkleBlock::COMMAND
|
|
127
130
|
MerkleBlock.parse_from_payload(payload)
|
|
131
|
+
when FilterLoad::COMMAND
|
|
132
|
+
FilterLoad.parse_from_payload(payload)
|
|
133
|
+
when FilterAdd::COMMAND
|
|
134
|
+
FilterAdd.parse_from_payload(payload)
|
|
135
|
+
when FilterClear::COMMAND
|
|
136
|
+
FilterClear.new
|
|
128
137
|
when CmpctBlock::COMMAND
|
|
129
138
|
CmpctBlock.parse_from_payload(payload)
|
|
139
|
+
when GetBlockTxn::COMMAND
|
|
140
|
+
GetBlockTxn.parse_from_payload(payload)
|
|
141
|
+
when BlockTxn::COMMAND
|
|
142
|
+
BlockTxn.parse_from_payload(payload)
|
|
130
143
|
when GetData::COMMAND
|
|
131
144
|
GetData.parse_from_payload(payload)
|
|
132
145
|
when GetCFHeaders::COMMAND
|
data/lib/bitcoin/mnemonic.rb
CHANGED
|
@@ -24,7 +24,7 @@ module Bitcoin
|
|
|
24
24
|
def to_entropy(words)
|
|
25
25
|
word_master = load_words
|
|
26
26
|
mnemonic = words.map do |w|
|
|
27
|
-
index = word_master.index(w.downcase)
|
|
27
|
+
index = word_master.index(normalize(w).downcase)
|
|
28
28
|
raise IndexError, 'word not found in words list.' unless index
|
|
29
29
|
index.to_s(2).rjust(11, '0')
|
|
30
30
|
end.join
|
|
@@ -53,8 +53,8 @@ module Bitcoin
|
|
|
53
53
|
# @return [String] seed
|
|
54
54
|
def to_seed(mnemonic, passphrase: '')
|
|
55
55
|
to_entropy(mnemonic)
|
|
56
|
-
OpenSSL::PKCS5.pbkdf2_hmac(mnemonic.join(' ').downcase,
|
|
57
|
-
'mnemonic' + passphrase, 2048, 64, OpenSSL::Digest::SHA512.new).bth
|
|
56
|
+
OpenSSL::PKCS5.pbkdf2_hmac(normalize(mnemonic.join(' ')).downcase,
|
|
57
|
+
'mnemonic' + normalize(passphrase), 2048, 64, OpenSSL::Digest::SHA512.new).bth
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
# calculate entropy checksum
|
|
@@ -69,7 +69,12 @@ module Bitcoin
|
|
|
69
69
|
|
|
70
70
|
# load word list contents
|
|
71
71
|
def load_words
|
|
72
|
-
File.readlines("#{WORD_DIR}/#{language}.txt").map(
|
|
72
|
+
File.readlines("#{WORD_DIR}/#{language}.txt", encoding: Encoding::UTF_8).map{|w| normalize(w.strip)}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# BIP-39 requires that mnemonic sentences and passphrases are normalized using NFKD.
|
|
76
|
+
def normalize(str)
|
|
77
|
+
str.unicode_normalize(:nfkd)
|
|
73
78
|
end
|
|
74
79
|
|
|
75
80
|
end
|
data/lib/bitcoin/partial_tree.rb
CHANGED
|
@@ -4,6 +4,10 @@ module Bitcoin
|
|
|
4
4
|
# For a complete Merkle tree implementation, migrate to the merkle gem.
|
|
5
5
|
class PartialTree
|
|
6
6
|
|
|
7
|
+
# The maximum number of transactions a block can contain, so the maximum tx_count
|
|
8
|
+
# a valid merkleblock message can commit to.
|
|
9
|
+
MAX_TX_COUNT = MAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT
|
|
10
|
+
|
|
7
11
|
attr_accessor :root
|
|
8
12
|
|
|
9
13
|
def initialize(root = nil)
|
|
@@ -15,7 +19,13 @@ module Bitcoin
|
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
# https://bitcoin.org/en/developer-reference#creating-a-merkleblock-message
|
|
22
|
+
# @param [Integer] tx_count The number of transactions in the block.
|
|
23
|
+
# @param [Array] hashes Array of hash values with hex format.
|
|
24
|
+
# @param [String] flags The sequence of bits, a string of '0' and '1'.
|
|
25
|
+
# @return [Bitcoin::PartialTree]
|
|
26
|
+
# @raise [ArgumentError] If the given parameters are out of range or inconsistent.
|
|
18
27
|
def self.build(tx_count, hashes, flags)
|
|
28
|
+
validate!(tx_count, hashes, flags)
|
|
19
29
|
flags = flags.each_char.map(&:to_i)
|
|
20
30
|
root = build_initial_tree( Array.new(tx_count) { Node.new })
|
|
21
31
|
current_node = root
|
|
@@ -37,7 +47,22 @@ module Bitcoin
|
|
|
37
47
|
new(root)
|
|
38
48
|
end
|
|
39
49
|
|
|
50
|
+
# Validate the parameters of a partial merkle tree.
|
|
51
|
+
# A merkleblock message is received from an untrusted peer, so these bounds are required
|
|
52
|
+
# to stop it from making #build allocate an unbounded number of nodes or loop forever.
|
|
53
|
+
# See Bitcoin Core's CPartialMerkleTree::ExtractMatches.
|
|
54
|
+
# @raise [ArgumentError]
|
|
55
|
+
def self.validate!(tx_count, hashes, flags)
|
|
56
|
+
raise ArgumentError, 'tx_count must be greater than 0.' unless tx_count.is_a?(Integer) && tx_count > 0
|
|
57
|
+
raise ArgumentError, "tx_count must be less than or equal to #{MAX_TX_COUNT}." if tx_count > MAX_TX_COUNT
|
|
58
|
+
# There can never be more hashes provided than one for every txid.
|
|
59
|
+
raise ArgumentError, 'hashes must not be greater than tx_count.' if hashes.size > tx_count
|
|
60
|
+
# There must be at least one bit per node in the partial tree, and at least one node per hash.
|
|
61
|
+
raise ArgumentError, 'flags must have at least one bit per hash.' if flags.size < hashes.size
|
|
62
|
+
end
|
|
63
|
+
|
|
40
64
|
def self.build_initial_tree(nodes)
|
|
65
|
+
raise ArgumentError, 'nodes must not be empty.' if nodes.empty?
|
|
41
66
|
while nodes.size != 1
|
|
42
67
|
nodes = nodes.each_slice(2).map { |m|
|
|
43
68
|
parent = Node.new
|