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/message/ping.rb
CHANGED
data/lib/bitcoin/message/pong.rb
CHANGED
|
@@ -31,6 +31,7 @@ module Bitcoin
|
|
|
31
31
|
def self.parse_from_payload(payload)
|
|
32
32
|
message, payload = Bitcoin.unpack_var_string(payload)
|
|
33
33
|
code, payload = payload.unpack('Ca*')
|
|
34
|
+
raise Bitcoin::Message::Error, 'Reject payload is too short.' if code.nil?
|
|
34
35
|
reason, payload = Bitcoin.unpack_var_string(payload)
|
|
35
36
|
extra = ['tx', 'block'].include?(message) ? payload.bth : payload
|
|
36
37
|
new(message, code, reason, extra)
|
|
@@ -21,8 +21,8 @@ module Bitcoin
|
|
|
21
21
|
|
|
22
22
|
def self.parse_from_payload(payload)
|
|
23
23
|
buf = StringIO.new(payload)
|
|
24
|
-
mode =
|
|
25
|
-
version =
|
|
24
|
+
mode = Bitcoin.read_exact(buf, 1).unpack1('c')
|
|
25
|
+
version = Bitcoin.read_exact(buf, 8).unpack1('Q<')
|
|
26
26
|
new(mode, version)
|
|
27
27
|
end
|
|
28
28
|
|
|
@@ -31,19 +31,34 @@ module Bitcoin
|
|
|
31
31
|
@relay = opts[:relay] || false
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
# The part every version message has: version, services, timestamp and the receiving address.
|
|
35
|
+
MIN_PAYLOAD_SIZE = 4 + 8 + 8 + 26
|
|
36
|
+
|
|
37
|
+
# Parse a version message. As Bitcoin Core does, the sending address and nonce, the user agent, the
|
|
38
|
+
# start height and the relay flag are each read only when the payload still has data, so a message
|
|
39
|
+
# from an old protocol version which stops early is accepted with the remaining fields empty.
|
|
34
40
|
def self.parse_from_payload(payload)
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
raise Bitcoin::Message::Error, 'Version payload is too short.' if payload.bytesize < MIN_PAYLOAD_SIZE
|
|
42
|
+
version, services, timestamp, local_addr, rest = payload.unpack('VQ<Q<a26a*')
|
|
43
|
+
v = new(remote_addr: NetworkAddr.new(time: nil), nonce: 0, user_agent: '', start_height: 0)
|
|
37
44
|
v.version = version
|
|
38
45
|
v.services = services
|
|
39
46
|
v.timestamp = timestamp
|
|
40
47
|
v.local_addr = NetworkAddr.parse_from_payload(local_addr)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
unless rest.empty?
|
|
49
|
+
raise Bitcoin::Message::Error, 'Version payload is truncated.' if rest.bytesize < 26 + 8
|
|
50
|
+
remote_addr, nonce, rest = rest.unpack('a26Q<a*')
|
|
51
|
+
v.remote_addr = NetworkAddr.parse_from_payload(remote_addr)
|
|
52
|
+
v.nonce = nonce
|
|
53
|
+
end
|
|
54
|
+
unless rest.empty?
|
|
55
|
+
v.user_agent, rest = unpack_var_string(rest)
|
|
56
|
+
raise Bitcoin::Message::Error, "user agent size #{v.user_agent.bytesize} exceeds #{MAX_SUBVERSION_LENGTH}." if v.user_agent.bytesize > MAX_SUBVERSION_LENGTH
|
|
57
|
+
end
|
|
58
|
+
unless rest.empty?
|
|
59
|
+
raise Bitcoin::Message::Error, 'Version payload is truncated.' if rest.bytesize < 4
|
|
60
|
+
v.start_height, rest = rest.unpack('Va*')
|
|
61
|
+
end
|
|
47
62
|
v.relay = v.unpack_relay_field(rest).first
|
|
48
63
|
v
|
|
49
64
|
end
|
data/lib/bitcoin/message.rb
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
module Bitcoin
|
|
2
2
|
module Message
|
|
3
3
|
|
|
4
|
+
# The most addresses an addr or addrv2 message may carry (MAX_ADDR_TO_SEND in Bitcoin Core).
|
|
5
|
+
MAX_ADDR_TO_SEND = 1000
|
|
6
|
+
# The most inventories an inv, getdata or notfound message may carry (MAX_INV_SZ).
|
|
7
|
+
MAX_INV_SZ = 50_000
|
|
8
|
+
# The most hashes a block locator may carry (MAX_LOCATOR_SZ).
|
|
9
|
+
MAX_LOCATOR_SZ = 101
|
|
10
|
+
# The longest user agent of a version message (MAX_SUBVERSION_LENGTH).
|
|
11
|
+
MAX_SUBVERSION_LENGTH = 256
|
|
12
|
+
# The most filter headers a cfheaders message may carry (MAX_GETCFHEADERS_SIZE).
|
|
13
|
+
MAX_CFHEADERS_RESULTS = 2000
|
|
14
|
+
# The most transactions a compact block or its blocktxn may refer to. No block holds more, since a
|
|
15
|
+
# transaction serializes to at least MIN_SERIALIZABLE_TRANSACTION_WEIGHT.
|
|
16
|
+
MAX_COMPACT_BLOCK_TX_COUNT = MAX_BLOCK_WEIGHT / MIN_SERIALIZABLE_TRANSACTION_WEIGHT
|
|
17
|
+
|
|
18
|
+
|
|
4
19
|
autoload :Base, 'bitcoin/message/base'
|
|
5
20
|
autoload :Inventory, 'bitcoin/message/inventory'
|
|
6
21
|
autoload :InventoriesParser, 'bitcoin/message/inventories_parser'
|
data/lib/bitcoin/message_sign.rb
CHANGED
|
@@ -4,6 +4,22 @@ module Bitcoin
|
|
|
4
4
|
|
|
5
5
|
class Error < StandardError; end
|
|
6
6
|
|
|
7
|
+
# A checker which enforces the BIP-322 rule that all signatures use SIGHASH_ALL, or SIGHASH_DEFAULT
|
|
8
|
+
# where the output type supports it. A signature with any other hash type fails, as if it did not verify.
|
|
9
|
+
class BIP322Checker < Bitcoin::TxChecker
|
|
10
|
+
|
|
11
|
+
def check_sig(sig, pubkey, script_code, sig_version, allow_hybrid: false)
|
|
12
|
+
return false unless sig.empty? || sig.htb[-1].unpack1('C') == SIGHASH_TYPE[:all]
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def check_schnorr_sig(sig, pubkey, sig_version, opts = {})
|
|
17
|
+
bytes = sig.htb
|
|
18
|
+
return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE) if bytes.bytesize == 65 && bytes[-1].unpack1('C') != SIGHASH_TYPE[:all]
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
7
23
|
module_function
|
|
8
24
|
|
|
9
25
|
FORMAT_LEGACY = :legacy
|
|
@@ -15,7 +31,9 @@ module Bitcoin
|
|
|
15
31
|
SIGNATURE_PREFIX_FULL = 'ful'
|
|
16
32
|
SIGNATURE_PREFIX_POF = 'pof'
|
|
17
33
|
|
|
18
|
-
# BIP-322
|
|
34
|
+
# BIP-322 required rules, and its upgradeable rules: a proof which relies on a reserved NOP, a witness
|
|
35
|
+
# version above 1, an unknown taproot leaf version, an OP_SUCCESSx or an unknown public key type is not
|
|
36
|
+
# valid, since such a script succeeds without any signature.
|
|
19
37
|
BIP322_VERIFY_FLAGS = [
|
|
20
38
|
SCRIPT_VERIFY_P2SH, SCRIPT_VERIFY_DERSIG, SCRIPT_VERIFY_STRICTENC,
|
|
21
39
|
SCRIPT_VERIFY_LOW_S, SCRIPT_VERIFY_NULLFAIL, SCRIPT_VERIFY_MINIMALDATA,
|
|
@@ -23,7 +41,10 @@ module Bitcoin
|
|
|
23
41
|
SCRIPT_VERIFY_CONST_SCRIPTCODE,
|
|
24
42
|
SCRIPT_VERIFY_WITNESS, SCRIPT_VERIFY_TAPROOT,
|
|
25
43
|
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS,
|
|
26
|
-
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM
|
|
44
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM,
|
|
45
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION,
|
|
46
|
+
SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS,
|
|
47
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE
|
|
27
48
|
].inject(:|)
|
|
28
49
|
|
|
29
50
|
# Sign a message.
|
|
@@ -71,7 +92,9 @@ module Bitcoin
|
|
|
71
92
|
# @param [String] signature The signature in base64 format.
|
|
72
93
|
# @param [String] message The message that was signed.
|
|
73
94
|
# @return [Boolean] Verification result.
|
|
95
|
+
# @raise [ArgumentError] If +signature+ is not a String or cannot be decoded.
|
|
74
96
|
def verify_message(address, signature, message, prefix: Bitcoin.chain_params.message_magic)
|
|
97
|
+
raise ArgumentError, 'signature must be a String.' unless signature.is_a?(String)
|
|
75
98
|
addr_script = Bitcoin::Script.parse_from_addr(address)
|
|
76
99
|
variant, body = case signature
|
|
77
100
|
when ''
|
|
@@ -99,7 +122,8 @@ module Bitcoin
|
|
|
99
122
|
verify_simple(addr_script, payload, message, prefix: prefix)
|
|
100
123
|
when :full
|
|
101
124
|
begin
|
|
102
|
-
tx
|
|
125
|
+
# strict rejects bytes after the tx, as Bitcoin Core's DecodeTx does.
|
|
126
|
+
tx = Bitcoin::Tx.parse_from_payload(payload, strict: true)
|
|
103
127
|
rescue StandardError
|
|
104
128
|
raise ArgumentError, 'error parsing signature as full variant'
|
|
105
129
|
end
|
|
@@ -143,7 +167,7 @@ module Bitcoin
|
|
|
143
167
|
|
|
144
168
|
def validate_format!(format)
|
|
145
169
|
unless [FORMAT_LEGACY, FORMAT_FULL, FORMAT_SIMPLE].include?(format)
|
|
146
|
-
raise ArgumentError "Invalid format specified."
|
|
170
|
+
raise ArgumentError, "Invalid format specified."
|
|
147
171
|
end
|
|
148
172
|
end
|
|
149
173
|
|
|
@@ -182,10 +206,23 @@ module Bitcoin
|
|
|
182
206
|
def verify_simple(addr_script, witness_payload, message, prefix:)
|
|
183
207
|
digest = message_hash(message, prefix: prefix, legacy: false)
|
|
184
208
|
tx = to_sign_tx(digest, addr_script.to_addr)
|
|
185
|
-
tx.in[0].script_witness =
|
|
209
|
+
tx.in[0].script_witness = parse_simple_witness(witness_payload)
|
|
186
210
|
run_interpreter(tx, addr_script)
|
|
187
211
|
end
|
|
188
212
|
|
|
213
|
+
# Parse the witness of a simple signature, which has to be exactly one serialized witness.
|
|
214
|
+
# @raise [ArgumentError] If the witness is truncated or followed by other bytes.
|
|
215
|
+
def parse_simple_witness(payload)
|
|
216
|
+
buf = StringIO.new(payload)
|
|
217
|
+
begin
|
|
218
|
+
witness = Bitcoin::ScriptWitness.parse_from_payload(buf)
|
|
219
|
+
rescue StandardError
|
|
220
|
+
raise ArgumentError, 'error parsing signature as simple variant'
|
|
221
|
+
end
|
|
222
|
+
raise ArgumentError, 'error parsing signature as simple variant' unless buf.eof?
|
|
223
|
+
witness
|
|
224
|
+
end
|
|
225
|
+
|
|
189
226
|
def verify_full(addr_script, tx, message, prefix:)
|
|
190
227
|
digest = message_hash(message, prefix: prefix, legacy: false)
|
|
191
228
|
to_spend = to_spend_tx(digest, addr_script.to_addr)
|
|
@@ -196,7 +233,7 @@ module Bitcoin
|
|
|
196
233
|
|
|
197
234
|
def run_interpreter(tx, script_pubkey)
|
|
198
235
|
tx_out = Bitcoin::TxOut.new(script_pubkey: script_pubkey)
|
|
199
|
-
checker =
|
|
236
|
+
checker = BIP322Checker.new(tx: tx, input_index: 0, prevouts: [tx_out])
|
|
200
237
|
interpreter = Bitcoin::ScriptInterpreter.new(flags: BIP322_VERIFY_FLAGS, checker: checker)
|
|
201
238
|
interpreter.verify_script(tx.in[0].script_sig, script_pubkey, tx.in[0].script_witness)
|
|
202
239
|
end
|
|
@@ -215,6 +252,7 @@ module Bitcoin
|
|
|
215
252
|
private_class_method :validate_to_sign_tx!
|
|
216
253
|
private_class_method :run_interpreter
|
|
217
254
|
private_class_method :verify_simple
|
|
255
|
+
private_class_method :parse_simple_witness
|
|
218
256
|
private_class_method :verify_full
|
|
219
257
|
private_class_method :verify_legacy
|
|
220
258
|
end
|
data/lib/bitcoin/mnemonic.rb
CHANGED
|
@@ -8,6 +8,11 @@ module Bitcoin
|
|
|
8
8
|
|
|
9
9
|
attr_reader :language
|
|
10
10
|
|
|
11
|
+
# The number of words of a valid mnemonic sentence (BIP-39).
|
|
12
|
+
WORD_COUNTS = [12, 15, 18, 21, 24].freeze
|
|
13
|
+
# The number of bytes of a valid entropy (BIP-39).
|
|
14
|
+
ENTROPY_BYTES = [16, 20, 24, 28, 32].freeze
|
|
15
|
+
|
|
11
16
|
def initialize(language)
|
|
12
17
|
raise ArgumentError, 'specified language is not supported.' unless Mnemonic.word_lists.include?(language)
|
|
13
18
|
@language = language
|
|
@@ -22,6 +27,8 @@ module Bitcoin
|
|
|
22
27
|
# @param [Array[String]] words the array of mnemonic word.
|
|
23
28
|
# @return [String] an entropy with hex format.
|
|
24
29
|
def to_entropy(words)
|
|
30
|
+
# BIP-39: the entropy is 128 to 256 bits in steps of 32, which gives 12, 15, 18, 21 or 24 words.
|
|
31
|
+
raise ArgumentError, 'Invalid mnemonic length.' unless words.is_a?(Array) && WORD_COUNTS.include?(words.size)
|
|
25
32
|
word_master = load_words
|
|
26
33
|
mnemonic = words.map do |w|
|
|
27
34
|
index = word_master.index(normalize(w).downcase)
|
|
@@ -39,6 +46,7 @@ module Bitcoin
|
|
|
39
46
|
# @return [Array] the array of mnemonic word.
|
|
40
47
|
def to_mnemonic(entropy)
|
|
41
48
|
raise ArgumentError, 'entropy is empty.' if entropy.nil? || entropy.empty?
|
|
49
|
+
raise ArgumentError, 'Invalid entropy length.' unless ENTROPY_BYTES.include?(entropy.htb.bytesize)
|
|
42
50
|
e = entropy.htb.unpack1('B*')
|
|
43
51
|
seed = e + checksum(e)
|
|
44
52
|
mnemonic_index = seed.chars.each_slice(11).map{|i|i.join.to_i(2)}
|
data/lib/bitcoin/out_point.rb
CHANGED
|
@@ -25,7 +25,7 @@ module Bitcoin
|
|
|
25
25
|
# @return [Bitcoin::OutPoint]
|
|
26
26
|
def self.parse_from_payload(payload)
|
|
27
27
|
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
|
28
|
-
hash, index =
|
|
28
|
+
hash, index = Bitcoin.read_exact(buf, 36).unpack('a32V')
|
|
29
29
|
OutPoint.new(hash.bth, index)
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -33,7 +33,11 @@ module Bitcoin
|
|
|
33
33
|
tx_hash == COINBASE_HASH && index == COINBASE_INDEX
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
# @raise [ArgumentError] If the index does not fit in 4 bytes. -1, the default, is written as 0xffffffff.
|
|
36
37
|
def to_payload
|
|
38
|
+
unless index.is_a?(Integer) && index.between?(-1, COINBASE_INDEX)
|
|
39
|
+
raise ArgumentError, 'index must be an integer from 0 to 0xffffffff.'
|
|
40
|
+
end
|
|
37
41
|
[tx_hash.htb, index].pack('a32V')
|
|
38
42
|
end
|
|
39
43
|
|
|
@@ -42,7 +46,7 @@ module Bitcoin
|
|
|
42
46
|
end
|
|
43
47
|
|
|
44
48
|
def valid?
|
|
45
|
-
index
|
|
49
|
+
index.is_a?(Integer) && index.between?(0, COINBASE_INDEX) && (!coinbase? && tx_hash != COINBASE_HASH)
|
|
46
50
|
end
|
|
47
51
|
|
|
48
52
|
# convert hash to txid
|
data/lib/bitcoin/partial_tree.rb
CHANGED
|
@@ -19,6 +19,9 @@ module Bitcoin
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
# https://bitcoin.org/en/developer-reference#creating-a-merkleblock-message
|
|
22
|
+
# The tree is read depth first the way Bitcoin Core's CPartialMerkleTree::TraverseAndExtract does, and
|
|
23
|
+
# like Core it is rejected when the flags or the hashes run out, when a hash is left over, or when a flag
|
|
24
|
+
# bit outside the padding of the last byte is left over.
|
|
22
25
|
# @param [Integer] tx_count The number of transactions in the block.
|
|
23
26
|
# @param [Array] hashes Array of hash values with hex format.
|
|
24
27
|
# @param [String] flags The sequence of bits, a string of '0' and '1'.
|
|
@@ -26,24 +29,12 @@ module Bitcoin
|
|
|
26
29
|
# @raise [ArgumentError] If the given parameters are out of range or inconsistent.
|
|
27
30
|
def self.build(tx_count, hashes, flags)
|
|
28
31
|
validate!(tx_count, hashes, flags)
|
|
29
|
-
|
|
32
|
+
bits = flags.each_char.map(&:to_i)
|
|
30
33
|
root = build_initial_tree( Array.new(tx_count) { Node.new })
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if f.zero? || current_node.leaf?
|
|
36
|
-
current_node.value = hashes[hash_index]
|
|
37
|
-
hash_index += 1
|
|
38
|
-
end
|
|
39
|
-
current_node = current_node.next_partial
|
|
40
|
-
if hash_index == hashes.size
|
|
41
|
-
if current_node&.leaf?
|
|
42
|
-
current_node.value = hashes.last
|
|
43
|
-
end
|
|
44
|
-
break
|
|
45
|
-
end
|
|
46
|
-
end
|
|
34
|
+
used = { bits: 0, hashes: 0 }
|
|
35
|
+
traverse(root, bits, hashes, used)
|
|
36
|
+
raise ArgumentError, 'Invalid partial tree. Not every hash was used.' unless used[:hashes] == hashes.size
|
|
37
|
+
raise ArgumentError, 'Invalid partial tree. Not every flag bit was used.' unless (used[:bits] + 7) / 8 == (bits.size + 7) / 8
|
|
47
38
|
new(root)
|
|
48
39
|
end
|
|
49
40
|
|
|
@@ -61,13 +52,36 @@ module Bitcoin
|
|
|
61
52
|
raise ArgumentError, 'flags must have at least one bit per hash.' if flags.size < hashes.size
|
|
62
53
|
end
|
|
63
54
|
|
|
55
|
+
# Read the flag bit of +node+ and, for a node which is neither a leaf nor the parent of a match, the
|
|
56
|
+
# hash which stands for it. The right child of the last node of an odd level repeats the left one and
|
|
57
|
+
# has no flag or hash of its own.
|
|
58
|
+
# @raise [ArgumentError] If the flags or the hashes run out.
|
|
59
|
+
def self.traverse(node, bits, hashes, used)
|
|
60
|
+
raise ArgumentError, 'Invalid partial tree. The flags ran out.' if used[:bits] >= bits.size
|
|
61
|
+
node.flag = bits[used[:bits]]
|
|
62
|
+
used[:bits] += 1
|
|
63
|
+
if node.flag.zero? || node.leaf?
|
|
64
|
+
raise ArgumentError, 'Invalid partial tree. The hashes ran out.' if used[:hashes] >= hashes.size
|
|
65
|
+
node.value = hashes[used[:hashes]]
|
|
66
|
+
used[:hashes] += 1
|
|
67
|
+
else
|
|
68
|
+
traverse(node.left, bits, hashes, used)
|
|
69
|
+
if node.right.duplicated
|
|
70
|
+
node.right.value = node.left.value
|
|
71
|
+
else
|
|
72
|
+
traverse(node.right, bits, hashes, used)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
private_class_method :traverse
|
|
77
|
+
|
|
64
78
|
def self.build_initial_tree(nodes)
|
|
65
79
|
raise ArgumentError, 'nodes must not be empty.' if nodes.empty?
|
|
66
80
|
while nodes.size != 1
|
|
67
81
|
nodes = nodes.each_slice(2).map { |m|
|
|
68
82
|
parent = Node.new
|
|
69
83
|
parent.left = m[0]
|
|
70
|
-
parent.right = m[1] ? m[1] : m[0].dup
|
|
84
|
+
parent.right = m[1] ? m[1] : m[0].dup.tap { |n| n.duplicated = true }
|
|
71
85
|
parent
|
|
72
86
|
}
|
|
73
87
|
end
|
|
@@ -86,9 +100,13 @@ module Bitcoin
|
|
|
86
100
|
attr_accessor :parent
|
|
87
101
|
attr_accessor :left
|
|
88
102
|
attr_accessor :right
|
|
103
|
+
# Whether this node stands in for a missing right child, as the last node of a level with an odd
|
|
104
|
+
# count is paired with a copy of itself. Such a copy is equal to its sibling by construction.
|
|
105
|
+
attr_accessor :duplicated
|
|
89
106
|
|
|
90
107
|
def initialize(value = nil)
|
|
91
108
|
@value = value
|
|
109
|
+
@duplicated = false
|
|
92
110
|
end
|
|
93
111
|
|
|
94
112
|
def left=(node)
|
|
@@ -103,8 +121,11 @@ module Bitcoin
|
|
|
103
121
|
|
|
104
122
|
def value
|
|
105
123
|
return @value if @value
|
|
106
|
-
|
|
107
|
-
|
|
124
|
+
left_value, right_value = left.value, right.value
|
|
125
|
+
# Two identical branches mean the hashes under them are not unique, which is how the duplicated
|
|
126
|
+
# tx of CVE-2012-2459 hides in a merkleblock. Bitcoin Core marks such a tree as bad.
|
|
127
|
+
raise ArgumentError, 'Invalid partial tree. The left and right branches are identical.' if !right.duplicated && left_value == right_value
|
|
128
|
+
Bitcoin.double_sha256([left_value + right_value].pack('H*')).bth
|
|
108
129
|
end
|
|
109
130
|
|
|
110
131
|
def root?
|
|
@@ -119,14 +140,6 @@ module Bitcoin
|
|
|
119
140
|
!flag.nil?
|
|
120
141
|
end
|
|
121
142
|
|
|
122
|
-
def next_partial
|
|
123
|
-
return nil if root? && (flag.zero? || (left.nil? && right.nil?) || (left.partial? && right.partial?))
|
|
124
|
-
return parent.next_partial if flag.zero? || leaf?
|
|
125
|
-
return left unless left.partial?
|
|
126
|
-
self.right = left.dup unless right
|
|
127
|
-
right.partial? ? parent.next_partial : right
|
|
128
|
-
end
|
|
129
|
-
|
|
130
143
|
# calculate the depth of this node in the tree.
|
|
131
144
|
def depth
|
|
132
145
|
d = 0
|
data/lib/bitcoin/payment_code.rb
CHANGED
|
@@ -7,10 +7,15 @@ module Bitcoin
|
|
|
7
7
|
|
|
8
8
|
attr_accessor :x_value
|
|
9
9
|
attr_accessor :sign
|
|
10
|
+
attr_accessor :features_bits # the features byte with hex format. Only bit 0 may be set.
|
|
11
|
+
attr_accessor :reserve_field # the 13 reserved bytes with hex format, zero unless a future version says otherwise.
|
|
10
12
|
|
|
11
13
|
VERSION_BYTE = '47'
|
|
12
14
|
SUPPORT_VERSIONS = ['01']
|
|
13
15
|
SUPPORT_SIGNS = ['02', '03']
|
|
16
|
+
PAYLOAD_SIZE = 80 # version, features, sign, x value, chain code and the reserved bytes.
|
|
17
|
+
# The Base58Check form: the 0x47 version byte, the payload and the checksum.
|
|
18
|
+
BASE58_PAYLOAD_SIZE = 1 + PAYLOAD_SIZE + 4
|
|
14
19
|
|
|
15
20
|
def initialize
|
|
16
21
|
@version = '01'
|
|
@@ -26,6 +31,9 @@ module Bitcoin
|
|
|
26
31
|
|
|
27
32
|
payment_code = PaymentCode.new
|
|
28
33
|
payment_code.depth = master_ext_key.depth
|
|
34
|
+
payment_code.number = master_ext_key.number
|
|
35
|
+
payment_code.parent_fingerprint = master_ext_key.parent_fingerprint
|
|
36
|
+
payment_code.ver = master_ext_key.ver
|
|
29
37
|
payment_code.key = master_ext_key.key
|
|
30
38
|
payment_code.sign = compressed_pubkey[0..1]
|
|
31
39
|
payment_code.x_value = compressed_pubkey[2..-1]
|
|
@@ -50,28 +58,38 @@ module Bitcoin
|
|
|
50
58
|
end
|
|
51
59
|
|
|
52
60
|
# decode base58 encoded payment code
|
|
61
|
+
# The serialization is the 85 bytes of BIP-47: the 0x47 version byte, the 80 byte payload and the
|
|
62
|
+
# checksum. The features byte may only have bit 0 set, and the reserved bytes are kept as they are.
|
|
53
63
|
# @params [String] base58_payment_code base58 encoded payment code
|
|
64
|
+
# @return [String] the 80 byte payload with binary format.
|
|
65
|
+
# @raise [ArgumentError] If +base58_payment_code+ is not a payment code.
|
|
54
66
|
def self.from_base58(base58_payment_code)
|
|
67
|
+
raise ArgumentError, 'payment code must be string.' unless base58_payment_code.is_a?(String)
|
|
55
68
|
hex = Bitcoin::Base58.decode(base58_payment_code)
|
|
69
|
+
raise ArgumentError, 'invalid payment code length' unless hex.length == BASE58_PAYLOAD_SIZE * 2
|
|
70
|
+
raise ArgumentError, 'invalid version byte' unless hex[0..1] == VERSION_BYTE
|
|
71
|
+
payment_code = hex[0...-8]
|
|
72
|
+
raise ArgumentError, Errors::Messages::INVALID_CHECKSUM unless Bitcoin.calc_checksum(payment_code) == hex[-8..-1]
|
|
73
|
+
|
|
56
74
|
version = hex[2..3]
|
|
75
|
+
features = hex[4..5]
|
|
57
76
|
sign = hex[6..7]
|
|
58
|
-
|
|
59
|
-
|
|
77
|
+
x_value = hex[8..71]
|
|
78
|
+
chain_code_hex = hex[72..135]
|
|
79
|
+
reserved = hex[136..161]
|
|
60
80
|
|
|
61
|
-
raise ArgumentError, 'invalid version byte' unless hex[0..1] == VERSION_BYTE
|
|
62
81
|
raise ArgumentError, 'invalid version' unless PaymentCode.support_version?(version)
|
|
82
|
+
raise ArgumentError, 'invalid features' unless (features.to_i(16) & 0xfe).zero?
|
|
63
83
|
raise ArgumentError, 'invalid sign' unless PaymentCode.support_sign?(sign)
|
|
64
|
-
raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless Bitcoin::Key.new(pubkey: sign +
|
|
65
|
-
raise ArgumentError, Errors::Messages::INVALID_CHECKSUM unless Bitcoin.calc_checksum(payment_code) == hex[-8..-1]
|
|
66
|
-
|
|
67
|
-
x_value = payment_code[8..71]
|
|
68
|
-
chain_code_hex = payment_code[72..135]
|
|
84
|
+
raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless Bitcoin::Key.new(pubkey: sign + x_value).fully_valid_pubkey?
|
|
69
85
|
|
|
70
86
|
payment_code_pubkey = PaymentCode.new
|
|
71
87
|
payment_code_pubkey.depth = 3
|
|
88
|
+
payment_code_pubkey.features_bits = features
|
|
72
89
|
payment_code_pubkey.sign = sign
|
|
73
90
|
payment_code_pubkey.x_value = x_value
|
|
74
91
|
payment_code_pubkey.chain_code = [chain_code_hex].pack('H*')
|
|
92
|
+
payment_code_pubkey.reserve_field = reserved
|
|
75
93
|
|
|
76
94
|
payment_code_pubkey.to_payload
|
|
77
95
|
end
|
|
@@ -11,8 +11,7 @@ module Bitcoin
|
|
|
11
11
|
def initialize(pubkey, info)
|
|
12
12
|
pubkey = pubkey.encoding == Encoding::ASCII_8BIT ? pubkey : pubkey.htb
|
|
13
13
|
raise ArgumentError, 'Size of key was not the expected size for the type BIP32 keypath.' unless [Bitcoin::Key::PUBLIC_KEY_SIZE, Bitcoin::Key::COMPRESSED_PUBLIC_KEY_SIZE].include?(pubkey.bytesize)
|
|
14
|
-
pubkey =
|
|
15
|
-
raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless pubkey.fully_valid_pubkey?
|
|
14
|
+
pubkey = PSBT.parse_pubkey(pubkey.bth)
|
|
16
15
|
@pubkey = pubkey.pubkey
|
|
17
16
|
@info = info
|
|
18
17
|
end
|