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/constants.rb
CHANGED
|
@@ -60,7 +60,16 @@ module Bitcoin
|
|
|
60
60
|
SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS = (1 << 19) # Making unknown OP_SUCCESS non-standard
|
|
61
61
|
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE = (1 << 20) # Making unknown public key versions (in BIP 342 scripts) non-standard
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
# Script verification flags of the rules which every block has to follow, the soft forks which are now consensus.
|
|
64
|
+
MANDATORY_SCRIPT_VERIFY_FLAGS = [
|
|
65
|
+
SCRIPT_VERIFY_P2SH,
|
|
66
|
+
SCRIPT_VERIFY_DERSIG,
|
|
67
|
+
SCRIPT_VERIFY_NULLDUMMY,
|
|
68
|
+
SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY,
|
|
69
|
+
SCRIPT_VERIFY_CHECKSEQUENCEVERIFY,
|
|
70
|
+
SCRIPT_VERIFY_WITNESS,
|
|
71
|
+
SCRIPT_VERIFY_TAPROOT
|
|
72
|
+
].inject(SCRIPT_VERIFY_NONE){|flags, f| flags |= f}
|
|
64
73
|
|
|
65
74
|
# Standard script verification flags that standard transactions will comply with.
|
|
66
75
|
STANDARD_SCRIPT_VERIFY_FLAGS = [
|
|
@@ -127,8 +136,9 @@ module Bitcoin
|
|
|
127
136
|
# Maximum number length in bytes
|
|
128
137
|
DEFAULT_MAX_NUM_SIZE = 4
|
|
129
138
|
|
|
130
|
-
#
|
|
131
|
-
|
|
139
|
+
# The default -datacarriersize of Bitcoin Core, the total size of the OP_RETURN scriptPubKeys a standard tx may have.
|
|
140
|
+
# It is MAX_STANDARD_TX_WEIGHT / WITNESS_SCALE_FACTOR, so that the size of a standard tx is the only limit.
|
|
141
|
+
MAX_OP_RETURN_RELAY = 100_000
|
|
132
142
|
|
|
133
143
|
# for script error
|
|
134
144
|
SCRIPT_ERR_OK = 0
|
|
@@ -7,8 +7,20 @@ module Bitcoin
|
|
|
7
7
|
:combo
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def initialize(key)
|
|
11
|
+
super(key)
|
|
12
|
+
validate_not_xonly!(key)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# combo() stands for several scripts, see #to_scripts.
|
|
16
|
+
# @raise [ArgumentError] always.
|
|
17
|
+
def to_script
|
|
11
18
|
raise ArgumentError, 'musig() is not allowed in top-level combo().' if musig?
|
|
19
|
+
raise ArgumentError, 'combo() expands to several scripts. Use #to_scripts.'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_hex
|
|
23
|
+
to_script
|
|
12
24
|
end
|
|
13
25
|
|
|
14
26
|
def to_scripts
|
|
@@ -54,16 +54,17 @@ module Bitcoin
|
|
|
54
54
|
if key.start_with?('[') # BIP32 fingerprint
|
|
55
55
|
raise ArgumentError, "Multiple ']' characters found for a single pubkey." if key.count('[') > 1 || key.count(']') > 1
|
|
56
56
|
info = key[1...key.index(']')]
|
|
57
|
-
fingerprint, *paths = info.split('/')
|
|
58
|
-
raise ArgumentError, "Fingerprint '#{fingerprint}' is not hex." unless fingerprint.valid_hex?
|
|
57
|
+
fingerprint, *paths = info.split('/', -1) # keep a trailing empty element, so that a trailing '/' is rejected
|
|
59
58
|
raise ArgumentError, "Fingerprint '#{fingerprint}' is not 4 bytes." unless fingerprint.size == 8
|
|
59
|
+
raise ArgumentError, "Fingerprint '#{fingerprint}' is not hex." unless fingerprint.valid_hex?
|
|
60
|
+
paths.each { |path| parse_path_element(path) } # The origin path is not derived, but it has to be a path.
|
|
60
61
|
key = key[(key.index(']') + 1)..-1]
|
|
61
62
|
else
|
|
62
63
|
raise ArgumentError, 'Invalid key origin.' if key.include?(']')
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
# check BIP32 derivation path
|
|
66
|
-
key, *paths = key.split('/')
|
|
67
|
+
key, *paths = key.split('/', -1)
|
|
67
68
|
|
|
68
69
|
raise ArgumentError, "No key provided." unless key
|
|
69
70
|
|
|
@@ -97,24 +98,43 @@ module Bitcoin
|
|
|
97
98
|
def derive_path(key, paths)
|
|
98
99
|
is_private = key.is_a?(Bitcoin::ExtKey)
|
|
99
100
|
paths.each do |path|
|
|
100
|
-
raise ArgumentError, 'xpub can not derive hardened key.' if !is_private && path.end_with?("'")
|
|
101
101
|
raise ArgumentError, 'Key ranges are not supported.' if path.include?("*")
|
|
102
102
|
raise ArgumentError, 'Key multipath are not supported.' if path.include?("<")
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
raise ArgumentError, 'Key path value is not a valid value.' unless path =~ /^[0-9]+$/
|
|
107
|
-
raise ArgumentError, 'Key path value is out of range.' if !hardened && path.to_i >= Bitcoin::HARDENED_THRESHOLD
|
|
108
|
-
key = key.derive(path.to_i, hardened)
|
|
109
|
-
else
|
|
110
|
-
raise ArgumentError, 'Key path value is not a valid value.' unless path =~ /^[0-9]+$/
|
|
111
|
-
raise ArgumentError, 'Key path value is out of range.' if path.to_i >= Bitcoin::HARDENED_THRESHOLD
|
|
112
|
-
key = key.derive(path.to_i)
|
|
113
|
-
end
|
|
103
|
+
index, hardened = parse_path_element(path)
|
|
104
|
+
raise ArgumentError, 'xpub can not derive hardened key.' if hardened && !is_private
|
|
105
|
+
key = is_private ? key.derive(index, hardened) : key.derive(index)
|
|
114
106
|
end
|
|
115
107
|
key
|
|
116
108
|
end
|
|
117
109
|
|
|
110
|
+
# Parse one element of a derivation path. A hardened index is marked with ' or h, as BIP-380 allows.
|
|
111
|
+
# @param [String] path The element, e.g. "0", "0'" or "0h".
|
|
112
|
+
# @return [Array(Integer, Boolean)] the index and whether it is hardened.
|
|
113
|
+
# @raise [ArgumentError] If it is not a number below 2^31.
|
|
114
|
+
def parse_path_element(path)
|
|
115
|
+
hardened = path.end_with?("'", 'h')
|
|
116
|
+
index = hardened ? path[0..-2] : path
|
|
117
|
+
raise ArgumentError, 'Key path value is not a valid value.' unless index =~ /\A[0-9]+\z/
|
|
118
|
+
raise ArgumentError, 'Key path value is out of range.' if index.to_i >= Bitcoin::HARDENED_THRESHOLD
|
|
119
|
+
[index.to_i, hardened]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Whether +key+ is given as a 32 byte x-only public key. Such a key is only valid inside tr().
|
|
123
|
+
# @param [String, MuSig] key KEY string.
|
|
124
|
+
# @return [Boolean]
|
|
125
|
+
def xonly_key?(key)
|
|
126
|
+
return false unless key.is_a?(String)
|
|
127
|
+
key = key[(key.index(']') + 1)..-1] if key.include?(']')
|
|
128
|
+
key = key.split('/').first.to_s
|
|
129
|
+
key.length == 64 && key.valid_hex?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Raise unless +key+ is usable outside tr().
|
|
133
|
+
# @raise [ArgumentError]
|
|
134
|
+
def validate_not_xonly!(key)
|
|
135
|
+
raise ArgumentError, 'x-only public key is only allowed inside tr().' if xonly_key?(key)
|
|
136
|
+
end
|
|
137
|
+
|
|
118
138
|
def ==(other)
|
|
119
139
|
return false unless other.is_a?(Expression)
|
|
120
140
|
type == other.type && to_script == other.to_script
|
|
@@ -37,12 +37,20 @@ module Bitcoin
|
|
|
37
37
|
|
|
38
38
|
private
|
|
39
39
|
|
|
40
|
+
# Whether the keys may be x-only. Only the tapscript variants allow it.
|
|
41
|
+
def xonly_allowed?
|
|
42
|
+
false
|
|
43
|
+
end
|
|
44
|
+
|
|
40
45
|
def validate!(threshold, keys)
|
|
41
46
|
raise ArgumentError, "Multisig threshold '#{threshold}' is not valid." unless threshold.is_a?(Integer)
|
|
42
47
|
raise ArgumentError, 'Multisig threshold cannot be 0, must be at least 1.' unless threshold > 0
|
|
43
48
|
raise ArgumentError, 'Multisig threshold cannot be larger than the number of keys.' if threshold > keys.size
|
|
44
49
|
raise ArgumentError, "Multisig must have between 1 and #{Bitcoin::MAX_PUBKEYS_PER_MULTISIG} keys, inclusive." if keys.size > Bitcoin::MAX_PUBKEYS_PER_MULTISIG
|
|
45
|
-
keys.each
|
|
50
|
+
keys.each do |key|
|
|
51
|
+
validate_not_xonly!(key) unless xonly_allowed?
|
|
52
|
+
extract_pubkey(key)
|
|
53
|
+
end
|
|
46
54
|
end
|
|
47
55
|
end
|
|
48
56
|
end
|
|
@@ -18,6 +18,7 @@ module Bitcoin
|
|
|
18
18
|
# @return [Bitcoin::Descriptor::MuSig]
|
|
19
19
|
def initialize(keys, path = nil)
|
|
20
20
|
raise ArgumentError, "keys must be an array." unless keys.is_a?(Array)
|
|
21
|
+
raise ArgumentError, 'musig() requires at least one key.' if keys.empty?
|
|
21
22
|
unless path.nil?
|
|
22
23
|
raise ArgumentError, "path must be String." unless path.is_a?(String)
|
|
23
24
|
raise ArgumentError, "path must be start with /." unless path.start_with?("/")
|
|
@@ -17,9 +17,16 @@ module Bitcoin
|
|
|
17
17
|
|
|
18
18
|
def to_hex
|
|
19
19
|
raise ArgumentError, 'musig() is not allowed in top-level pk().' if musig?
|
|
20
|
+
validate_not_xonly!(key)
|
|
20
21
|
super
|
|
21
22
|
end
|
|
22
23
|
|
|
24
|
+
# Whether the key is given as an x-only key, which only a tr() tree can hold.
|
|
25
|
+
# @return [Boolean]
|
|
26
|
+
def xonly_key?(k = key)
|
|
27
|
+
super(k)
|
|
28
|
+
end
|
|
29
|
+
|
|
23
30
|
# Convert to bitcoin script.
|
|
24
31
|
# @return [Bitcoin::Script]
|
|
25
32
|
def to_script
|
|
@@ -8,7 +8,7 @@ module Bitcoin
|
|
|
8
8
|
# @param [String] hex
|
|
9
9
|
def initialize(hex)
|
|
10
10
|
raise ArgumentError, "Raw script must be string." unless hex.is_a?(String)
|
|
11
|
-
raise ArgumentError, "Raw script is not hex."
|
|
11
|
+
raise ArgumentError, "Raw script is not hex." if hex.empty? || !hex.valid_hex?
|
|
12
12
|
@hex = hex
|
|
13
13
|
end
|
|
14
14
|
|
|
@@ -20,6 +20,7 @@ module Bitcoin
|
|
|
20
20
|
def validate!(script)
|
|
21
21
|
super(script)
|
|
22
22
|
raise ArgumentError, 'A function is needed within P2SH.' unless script.is_a?(Expression)
|
|
23
|
+
raise ArgumentError, 'x-only public key is only allowed inside tr().' if script.is_a?(Pk) && script.xonly_key?
|
|
23
24
|
script_size = script.to_script.size
|
|
24
25
|
if script_size > Bitcoin::MAX_SCRIPT_ELEMENT_SIZE
|
|
25
26
|
raise ArgumentError,
|
|
@@ -92,10 +92,10 @@ module Bitcoin
|
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
# Silent payment descriptors do not produce a single script.
|
|
95
|
-
# Use
|
|
95
|
+
# Use to_addr to get the silent payment address.
|
|
96
96
|
# @raise [RuntimeError]
|
|
97
97
|
def to_script
|
|
98
|
-
raise RuntimeError, "sp() descriptor does not produce a fixed script. Use
|
|
98
|
+
raise RuntimeError, "sp() descriptor does not produce a fixed script. Use to_addr instead."
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
private
|
|
@@ -55,7 +55,7 @@ module Bitcoin
|
|
|
55
55
|
if item.is_a?(Array)
|
|
56
56
|
items << parse_tree_items(item)
|
|
57
57
|
elsif item.is_a?(Expression)
|
|
58
|
-
item.xonly = true
|
|
58
|
+
item.xonly = true if item.respond_to?(:xonly=)
|
|
59
59
|
items << Bitcoin::Taproot::LeafNode.new(item.to_script)
|
|
60
60
|
else
|
|
61
61
|
raise RuntimeError, "Unsupported item #{item}"
|
|
@@ -65,8 +65,10 @@ module Bitcoin
|
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def validate_tree!(tree)
|
|
68
|
-
return if tree.nil?
|
|
69
|
-
if tree.is_a?(
|
|
68
|
+
return if tree.nil?
|
|
69
|
+
if tree.is_a?(Expression)
|
|
70
|
+
validate_leaf!(tree)
|
|
71
|
+
elsif tree.is_a?(Array)
|
|
70
72
|
tree.each do |item|
|
|
71
73
|
validate_tree!(item)
|
|
72
74
|
end
|
|
@@ -75,6 +77,30 @@ module Bitcoin
|
|
|
75
77
|
end
|
|
76
78
|
end
|
|
77
79
|
|
|
80
|
+
# Check that +leaf+ can be a tapscript leaf. Bitcoin Core allows pk(), multi_a(), sortedmulti_a() and
|
|
81
|
+
# miniscript inside tr(); miniscript is not supported here. Any other expression, such as a wrapper
|
|
82
|
+
# which produces a scriptPubKey, would end up as a tapleaf nobody can spend.
|
|
83
|
+
# @param [Bitcoin::Descriptor::Expression] leaf
|
|
84
|
+
# @raise [ArgumentError]
|
|
85
|
+
def validate_leaf!(leaf)
|
|
86
|
+
message = case leaf.type
|
|
87
|
+
when :pk, :multi_a, :sortedmulti_a then nil
|
|
88
|
+
when :multi, :sortedmulti then 'Can only have multi/sortedmulti at top level, in sh(), or in wsh().'
|
|
89
|
+
when :wpkh then 'Can only have wpkh() at top level or inside sh().'
|
|
90
|
+
when :sh then 'Can only have sh() at top level.'
|
|
91
|
+
when :wsh then 'Can only have wsh() at top level or inside sh().'
|
|
92
|
+
when :addr then 'Can only have addr() at top level.'
|
|
93
|
+
when :combo then 'Can only have combo() at top level.'
|
|
94
|
+
when :raw then 'Can only have raw() at top level.'
|
|
95
|
+
when :tr then 'Can only have tr() at top level.'
|
|
96
|
+
when :rawtr then 'Can only have rawtr() at top level.'
|
|
97
|
+
else "#{leaf.type}() is not allowed in a tr() tree."
|
|
98
|
+
end
|
|
99
|
+
raise ArgumentError, message if message
|
|
100
|
+
keys = leaf.is_a?(KeyExpression) ? [leaf.extracted_key] : leaf.keys.map { |k| leaf.extract_pubkey(k) }
|
|
101
|
+
raise ArgumentError, "Uncompressed key are not allowed." unless keys.all?(&:compressed?)
|
|
102
|
+
end
|
|
103
|
+
|
|
78
104
|
def tree_string(tree)
|
|
79
105
|
buffer = '{'
|
|
80
106
|
left, right = tree
|
|
@@ -18,6 +18,7 @@ module Bitcoin
|
|
|
18
18
|
def validate!(script)
|
|
19
19
|
super(script)
|
|
20
20
|
raise ArgumentError, 'A function is needed within P2WSH.' unless script.is_a?(Expression)
|
|
21
|
+
raise ArgumentError, 'x-only public key is only allowed inside tr().' if script.is_a?(Pk) && script.xonly_key?
|
|
21
22
|
if script.is_a?(Wpkh) || script.is_a?(Wsh)
|
|
22
23
|
raise ArgumentError, "Can only have #{script.type}() at top level or inside sh()."
|
|
23
24
|
end
|
data/lib/bitcoin/descriptor.rb
CHANGED
|
@@ -153,8 +153,13 @@ module Bitcoin
|
|
|
153
153
|
def parse(string, top_level = true)
|
|
154
154
|
validate_checksum!(string)
|
|
155
155
|
content, _ = string.split('#')
|
|
156
|
-
|
|
156
|
+
matched = content.to_s.match(/\A(\w+)\((.*)\)(.*)\z/) # split "tag(10, 20)/0/1"
|
|
157
|
+
raise ArgumentError, "Invalid descriptor format: '#{string}'." unless matched
|
|
158
|
+
exp, args_str, path = matched.captures
|
|
157
159
|
raise ArgumentError, 'Invalid format.' if exp != 'musig' && !path.empty?
|
|
160
|
+
if %w[pk pkh wpkh sh wsh combo raw addr rawtr].include?(exp) && split_two(args_str)[1]
|
|
161
|
+
raise ArgumentError, "#{exp}() takes one argument."
|
|
162
|
+
end
|
|
158
163
|
|
|
159
164
|
case exp
|
|
160
165
|
when 'pk'
|
|
@@ -175,6 +180,8 @@ module Bitcoin
|
|
|
175
180
|
combo(args_str)
|
|
176
181
|
when 'multi', 'sortedmulti', 'multi_a', 'sortedmulti_a'
|
|
177
182
|
args = args_str.split(',')
|
|
183
|
+
# Only a plain decimal number, as ToIntegral in Bitcoin Core: no sign, no space, no other characters.
|
|
184
|
+
raise ArgumentError, "Multisig threshold '#{args[0]}' is not valid." unless args[0] =~ /\A[0-9]+\z/
|
|
178
185
|
threshold = args[0].to_i
|
|
179
186
|
keys = args[1..-1]
|
|
180
187
|
case exp
|
|
@@ -196,13 +203,7 @@ module Bitcoin
|
|
|
196
203
|
when 'tr'
|
|
197
204
|
key, tree = split_two(args_str)
|
|
198
205
|
key = parse(key) if key.include?('(')
|
|
199
|
-
|
|
200
|
-
tr(key)
|
|
201
|
-
elsif tree.start_with?('{')
|
|
202
|
-
tr(key, parse_nested_string(tree))
|
|
203
|
-
else
|
|
204
|
-
tr(key, parse(tree, false))
|
|
205
|
-
end
|
|
206
|
+
tree.nil? ? tr(key) : tr(key, parse_tree_string(tree))
|
|
206
207
|
when 'rawtr'
|
|
207
208
|
if args_str.include?('(')
|
|
208
209
|
rawtr(parse(args_str, false))
|
|
@@ -242,35 +243,58 @@ module Bitcoin
|
|
|
242
243
|
end
|
|
243
244
|
end
|
|
244
245
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
246
|
+
# Parse the script tree of a tr() expression the way Bitcoin Core does. A tree is a script expression or
|
|
247
|
+
# '{' tree ',' tree '}', and nothing may follow it.
|
|
248
|
+
# @param [String] string The arguments of tr() after the key.
|
|
249
|
+
# @return [Bitcoin::Descriptor::Expression, Array] A leaf, or a two element Array of subtrees.
|
|
250
|
+
# @raise [ArgumentError] If +string+ is not a tree.
|
|
251
|
+
def parse_tree_string(string)
|
|
252
|
+
rest = string
|
|
253
|
+
branches = [] # one entry per open brace: false while in its left subtree, true while in its right one
|
|
254
|
+
lefts = [] # the finished left subtree of every brace which is in its right subtree
|
|
255
|
+
tree = loop do
|
|
256
|
+
while rest.start_with?('{')
|
|
257
|
+
rest = rest[1..]
|
|
258
|
+
branches << false
|
|
259
|
+
raise ArgumentError, "tr() supports at most #{TAPROOT_CONTROL_MAX_NODE_COUNT} nesting levels." if branches.size > TAPROOT_CONTROL_MAX_NODE_COUNT
|
|
260
|
+
end
|
|
261
|
+
leaf, rest = take_expression(rest)
|
|
262
|
+
raise ArgumentError, 'tr(): expected script expression.' if leaf.empty?
|
|
263
|
+
node = parse(leaf, false)
|
|
264
|
+
# Close every brace whose right subtree this finishes.
|
|
265
|
+
while branches.any? && branches.last
|
|
266
|
+
raise ArgumentError, "tr(): expected '}' after script expression." unless rest.start_with?('}')
|
|
267
|
+
rest = rest[1..]
|
|
268
|
+
branches.pop
|
|
269
|
+
node = [lefts.pop, node]
|
|
270
|
+
end
|
|
271
|
+
break node if branches.empty?
|
|
272
|
+
# This finishes the left subtree of a brace, so its right one follows a comma.
|
|
273
|
+
raise ArgumentError, "tr(): expected ',' after script expression." unless rest.start_with?(',')
|
|
274
|
+
rest = rest[1..]
|
|
275
|
+
branches[-1] = true
|
|
276
|
+
lefts << node
|
|
277
|
+
end
|
|
278
|
+
raise ArgumentError, "tr(): expected ')' after script expression." unless rest.empty?
|
|
279
|
+
tree
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Split off the leading script or key expression of +string+. It ends at the first ',', ')' or '}'
|
|
283
|
+
# outside any parentheses or braces, as Expr does in Bitcoin Core.
|
|
284
|
+
# @param [String] string
|
|
285
|
+
# @return [Array] The expression and the rest of +string+.
|
|
286
|
+
def take_expression(string)
|
|
287
|
+
level = 0
|
|
288
|
+
string.each_char.with_index do |c, i|
|
|
289
|
+
if c == '(' || c == '{'
|
|
290
|
+
level += 1
|
|
291
|
+
elsif level > 0 && (c == ')' || c == '}')
|
|
292
|
+
level -= 1
|
|
293
|
+
elsif level.zero? && (c == ')' || c == '}' || c == ',')
|
|
294
|
+
return [string[0...i], string[i..]]
|
|
270
295
|
end
|
|
271
296
|
end
|
|
272
|
-
|
|
273
|
-
current.first
|
|
297
|
+
[string, '']
|
|
274
298
|
end
|
|
275
299
|
|
|
276
300
|
def split_two(content)
|
|
@@ -6,7 +6,10 @@ module Bitcoin
|
|
|
6
6
|
|
|
7
7
|
# resize array content with +initial_value+.
|
|
8
8
|
# expect to behave like vec#resize in c++.
|
|
9
|
+
# @raise [ArgumentError] If +new_size+ is not a non-negative integer. A negative size would
|
|
10
|
+
# otherwise empty the array.
|
|
9
11
|
def resize!(new_size, initial_value = 0)
|
|
12
|
+
raise ArgumentError, 'new_size must be a non-negative integer.' unless new_size.is_a?(Integer) && new_size >= 0
|
|
10
13
|
if size < new_size
|
|
11
14
|
(new_size - size).times{self.<< initial_value}
|
|
12
15
|
elsif size > new_size
|
data/lib/bitcoin/ext/ecdsa.rb
CHANGED
|
@@ -21,7 +21,13 @@ module ::ECDSA::Format::PointOctetString
|
|
|
21
21
|
raise ECDSA::Format::DecodeError, 'Point octet string is empty.' if string.empty?
|
|
22
22
|
if [6, 7].include?(string[0].ord)
|
|
23
23
|
raise ECDSA::Format::DecodeError, 'Unrecognized start byte for point octet string: 0x%x' % string[0].ord unless allow_hybrid
|
|
24
|
-
decode_uncompressed
|
|
24
|
+
point = decode_uncompressed(string, group)
|
|
25
|
+
# A hybrid encoding carries the parity of y in its first byte, 0x06 for even and 0x07 for odd, and
|
|
26
|
+
# libsecp256k1 rejects the point when the byte does not match the y it carries.
|
|
27
|
+
unless point.infinity? || (string[0].ord == 6) == point.y.even?
|
|
28
|
+
raise ECDSA::Format::DecodeError, 'Hybrid point octet string has a parity byte which does not match y.'
|
|
29
|
+
end
|
|
30
|
+
point
|
|
25
31
|
else
|
|
26
32
|
base_decode(string, group)
|
|
27
33
|
end
|
data/lib/bitcoin/ext_key.rb
CHANGED
|
@@ -21,6 +21,9 @@ module Bitcoin
|
|
|
21
21
|
# generate master key from seed.
|
|
22
22
|
# @params [String] seed a seed data with hex format.
|
|
23
23
|
def self.generate_master(seed)
|
|
24
|
+
# BIP-32: the seed is between 128 and 512 bits.
|
|
25
|
+
raise ArgumentError, 'The seed must be a hex string.' unless seed.is_a?(String)
|
|
26
|
+
raise ArgumentError, 'The seed must be 16 to 64 bytes.' unless (16..64).cover?(seed.htb.bytesize)
|
|
24
27
|
ext_key = ExtKey.new
|
|
25
28
|
ext_key.depth = ext_key.number = 0
|
|
26
29
|
ext_key.parent_fingerprint = MASTER_FINGERPRINT
|
|
@@ -95,6 +98,10 @@ module Bitcoin
|
|
|
95
98
|
# @param [Boolean] harden whether hardened key or not. If true, 2^31 is added to +number+.
|
|
96
99
|
# @return [Bitcoin::ExtKey] derived new key.
|
|
97
100
|
def derive(number, harden = false)
|
|
101
|
+
# A child index is 31 bits plus the hardened bit. Anything else was packed with 'N' and silently
|
|
102
|
+
# wrapped, giving a key whose serialized index differs from the one it was derived with.
|
|
103
|
+
limit = harden ? Bitcoin::HARDENED_THRESHOLD : 2**32
|
|
104
|
+
raise ArgumentError, 'Child index is out of range.' unless number.is_a?(Integer) && number >= 0 && number < limit
|
|
98
105
|
number += Bitcoin::HARDENED_THRESHOLD if harden
|
|
99
106
|
new_key = ExtKey.new
|
|
100
107
|
new_key.depth = depth + 1
|
|
@@ -144,6 +151,7 @@ module Bitcoin
|
|
|
144
151
|
end
|
|
145
152
|
|
|
146
153
|
def self.parse_from_payload(payload)
|
|
154
|
+
raise ArgumentError, 'Invalid extended key length.' unless payload.bytesize == BIP32_EXTKEY_WITH_VERSION_SIZE
|
|
147
155
|
buf = StringIO.new(payload)
|
|
148
156
|
ext_key = ExtKey.new
|
|
149
157
|
ext_key.ver = buf.read(4).bth # version
|
|
@@ -273,6 +281,7 @@ module Bitcoin
|
|
|
273
281
|
|
|
274
282
|
# derive child key
|
|
275
283
|
def derive(number)
|
|
284
|
+
raise ArgumentError, 'Child index is out of range.' unless number.is_a?(Integer) && number >= 0 && number < 2**32
|
|
276
285
|
new_key = ExtPubkey.new
|
|
277
286
|
new_key.depth = depth + 1
|
|
278
287
|
raise IndexError, 'Depth over 255.' if new_key.depth > Bitcoin::ExtKey::MAX_DEPTH
|
|
@@ -318,6 +327,7 @@ module Bitcoin
|
|
|
318
327
|
end
|
|
319
328
|
|
|
320
329
|
def self.parse_from_payload(payload)
|
|
330
|
+
raise ArgumentError, 'Invalid extended key length.' unless payload.bytesize == BIP32_EXTKEY_WITH_VERSION_SIZE
|
|
321
331
|
buf = StringIO.new(payload)
|
|
322
332
|
ext_pubkey = ExtPubkey.new
|
|
323
333
|
ext_pubkey.ver = buf.read(4).bth # version
|
data/lib/bitcoin/gcs_filter.rb
CHANGED
|
@@ -21,14 +21,18 @@ module Bitcoin
|
|
|
21
21
|
# @param [Array] elements the filter elements.
|
|
22
22
|
# @param [String] encoded_filter encoded filter with hex format.
|
|
23
23
|
# @return [Bitcoin::GCSFilter]
|
|
24
|
+
# @raise [ArgumentError] If a parameter is out of range, or +encoded_filter+ is not the encoding of
|
|
25
|
+
# exactly N elements, as Bitcoin Core's GCSFilter checks it.
|
|
24
26
|
def initialize(key, p, m, elements: nil, encoded_filter: nil)
|
|
25
|
-
raise 'specify either elements or encoded_filter.' if elements.nil? && encoded_filter.nil?
|
|
26
|
-
raise '
|
|
27
|
-
|
|
27
|
+
raise ArgumentError, 'specify either elements or encoded_filter.' if elements.nil? && encoded_filter.nil?
|
|
28
|
+
raise ArgumentError, 'key must be 16 bytes.' unless key.is_a?(String) && key.bytesize == 16
|
|
29
|
+
raise ArgumentError, 'p must be between 0 and 32.' unless p.is_a?(Integer) && p.between?(0, 32)
|
|
30
|
+
raise ArgumentError, 'm must be a positive integer.' unless m.is_a?(Integer) && m > 0
|
|
31
|
+
@key = key.b
|
|
28
32
|
@p = p
|
|
29
33
|
@m = m
|
|
30
34
|
if elements
|
|
31
|
-
raise 'elements size must be < 2**32.' if elements.size >= MAX_ELEMENTS_SIZE
|
|
35
|
+
raise ArgumentError, 'elements size must be < 2**32.' if elements.size >= MAX_ELEMENTS_SIZE
|
|
32
36
|
@n = elements.size
|
|
33
37
|
encoded = Bitcoin.pack_var_int(@n)
|
|
34
38
|
bit_writer = Bitcoin::BitStreamWriter.new
|
|
@@ -45,8 +49,19 @@ module Bitcoin
|
|
|
45
49
|
encoded << bit_writer.stream
|
|
46
50
|
@encoded = encoded.bth
|
|
47
51
|
else
|
|
52
|
+
raise ArgumentError, 'encoded_filter must be a hex string.' unless encoded_filter.is_a?(String) && encoded_filter.valid_hex?
|
|
48
53
|
@encoded = encoded_filter
|
|
49
|
-
|
|
54
|
+
buf = StringIO.new(encoded_filter.htb)
|
|
55
|
+
@n = Bitcoin.unpack_var_int_from_io(buf)
|
|
56
|
+
raise ArgumentError, 'N must be < 2**32.' if @n >= MAX_ELEMENTS_SIZE
|
|
57
|
+
# The filter has to hold exactly N elements: neither fewer, nor bytes after the last one.
|
|
58
|
+
bit_reader = Bitcoin::BitStreamReader.new(buf.read)
|
|
59
|
+
begin
|
|
60
|
+
@n.times { golomb_rice_decode(bit_reader, p) }
|
|
61
|
+
rescue IOError
|
|
62
|
+
raise ArgumentError, 'encoded_filter is truncated.'
|
|
63
|
+
end
|
|
64
|
+
raise ArgumentError, 'encoded_filter contains excess data.' unless bit_reader.stream.eof?
|
|
50
65
|
end
|
|
51
66
|
end
|
|
52
67
|
|
|
@@ -59,7 +74,7 @@ module Bitcoin
|
|
|
59
74
|
# @param [String] element with binary format.
|
|
60
75
|
# @return [Integer]
|
|
61
76
|
def hash_to_range(element)
|
|
62
|
-
hash = SipHash.digest(key, element)
|
|
77
|
+
hash = SipHash.digest(key, element.b) # bytes, whatever the encoding of the string says
|
|
63
78
|
map_into_range(hash, f)
|
|
64
79
|
end
|
|
65
80
|
|