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
|
@@ -92,15 +92,26 @@ module Bitcoin
|
|
|
92
92
|
new << WITNESS_VERSION_V0 << redeem_script.to_sha256
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
+
# The range of a decimal number in a script string, as ParseScript in Bitcoin Core: a number
|
|
96
|
+
# outside of it is not valid in a script.
|
|
97
|
+
MAX_SCRIPT_STRING_NUMBER = 0xffffffff
|
|
98
|
+
|
|
95
99
|
# generate script from string.
|
|
100
|
+
# A number is pushed as a script number, as ParseScript in Bitcoin Core does: 0, -1 and 1 to 16
|
|
101
|
+
# become OP_0, OP_1NEGATE and OP_1 to OP_16, and any other number a push of its minimal encoding.
|
|
102
|
+
# @param [String] string script string such as the result of #to_s.
|
|
103
|
+
# @return [Bitcoin::Script]
|
|
104
|
+
# @raise [ArgumentError] If a number is out of -0xffffffff..0xffffffff or a token is not valid.
|
|
96
105
|
def self.from_string(string)
|
|
97
106
|
script = new
|
|
98
107
|
string.split(' ').each do |v|
|
|
99
108
|
opcode = Opcodes.name_to_opcode(v)
|
|
100
109
|
if opcode
|
|
101
110
|
script << (v =~ /^\d/ && Opcodes.small_int_to_opcode(v.ord) ? v.ord : opcode)
|
|
111
|
+
elsif number_token?(v)
|
|
112
|
+
script.send(:push_script_number, v.to_i)
|
|
102
113
|
else
|
|
103
|
-
script <<
|
|
114
|
+
script << v
|
|
104
115
|
end
|
|
105
116
|
end
|
|
106
117
|
script
|
|
@@ -112,7 +123,8 @@ module Bitcoin
|
|
|
112
123
|
# @param [String] token a token of the script string.
|
|
113
124
|
# @return [Boolean] whether +token+ should be interpreted as a script number.
|
|
114
125
|
def self.number_token?(token)
|
|
115
|
-
return
|
|
126
|
+
return true if token =~ /\A-[0-9]+\z/ # A negative number cannot be pushed data.
|
|
127
|
+
return false unless token =~ /\A[0-9]+\z/
|
|
116
128
|
token.length < 12 || token.length.odd?
|
|
117
129
|
end
|
|
118
130
|
|
|
@@ -153,31 +165,31 @@ module Bitcoin
|
|
|
153
165
|
s.chunks << opcode
|
|
154
166
|
return s
|
|
155
167
|
end
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
size_length, format = case pushcode
|
|
169
|
+
when OP_PUSHDATA1 then [1, 'C']
|
|
170
|
+
when OP_PUSHDATA2 then [2, 'v']
|
|
171
|
+
when OP_PUSHDATA4 then [4, 'V']
|
|
172
|
+
else [0, nil]
|
|
173
|
+
end
|
|
174
|
+
if size_length > 0
|
|
175
|
+
packed_size = buf.read(size_length)
|
|
176
|
+
# A length field cut short. Keep the bytes as they are so that the payload round trips,
|
|
177
|
+
# and leave it to the interpreter to reject the push.
|
|
178
|
+
if packed_size.bytesize < size_length
|
|
179
|
+
s.chunks << (opcode + packed_size)
|
|
180
|
+
return s
|
|
181
|
+
end
|
|
182
|
+
len = packed_size.unpack1(format)
|
|
171
183
|
else
|
|
172
|
-
|
|
173
|
-
s.chunks << chunk
|
|
184
|
+
len = pushcode
|
|
174
185
|
end
|
|
186
|
+
# StringIO#read returns nil at EOF for a positive length, which is a push whose data is missing entirely.
|
|
187
|
+
s.chunks << (opcode + (packed_size || '') + buf.read(len).to_s)
|
|
175
188
|
else
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
end
|
|
189
|
+
# Every non-push byte is an opcode of its own, defined or not, like GetOp in Bitcoin Core.
|
|
190
|
+
# An undefined opcode only fails the script when it is executed, so the bytes after it
|
|
191
|
+
# still have to be parsed for OP_ELSE and OP_ENDIF in an unexecuted branch to be found.
|
|
192
|
+
s.chunks << opcode
|
|
181
193
|
end
|
|
182
194
|
end
|
|
183
195
|
s
|
|
@@ -215,8 +227,23 @@ module Bitcoin
|
|
|
215
227
|
end
|
|
216
228
|
|
|
217
229
|
# check whether standard script.
|
|
230
|
+
# Whether this script is a standard scriptPubKey, as IsStandard in Bitcoin Core judges it.
|
|
231
|
+
# An OP_RETURN script is standard up to MAX_OP_RETURN_RELAY bytes, which a tx shares among all of its
|
|
232
|
+
# OP_RETURN outputs. A bare multisig has at most 3 public keys.
|
|
233
|
+
# @return [Boolean]
|
|
218
234
|
def standard?
|
|
219
|
-
p2pkh?
|
|
235
|
+
p2pkh? || p2sh? || p2wpkh? || p2wsh? || p2tr? || p2a? || witness_unknown? ||
|
|
236
|
+
standard_p2pk? || standard_multisig? || standard_op_return?
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Whether this script is a witness program of a version which is not defined yet, which Bitcoin Core relays
|
|
240
|
+
# so that a future soft fork can use it. A version 0 program is defined only for 20 and 32 bytes.
|
|
241
|
+
# @return [Boolean]
|
|
242
|
+
def witness_unknown?
|
|
243
|
+
return false unless witness_program?
|
|
244
|
+
version, program = witness_data
|
|
245
|
+
return false if version == 0
|
|
246
|
+
!(version == 1 && (program.bytesize == 32 || program == P2A_PROGRAM))
|
|
220
247
|
end
|
|
221
248
|
|
|
222
249
|
# Check whether this script is a P2PKH format script.
|
|
@@ -287,13 +314,46 @@ module Bitcoin
|
|
|
287
314
|
sig_count <= pubkey_count
|
|
288
315
|
end
|
|
289
316
|
|
|
317
|
+
# Whether a public key is of a valid size for its header byte, as CPubKey::ValidSize in Bitcoin Core checks it.
|
|
318
|
+
def self.valid_pubkey_size?(data)
|
|
319
|
+
return false if data.nil? || data.empty?
|
|
320
|
+
case data.getbyte(0)
|
|
321
|
+
when 0x02, 0x03 then data.bytesize == 33
|
|
322
|
+
when 0x04, 0x06, 0x07 then data.bytesize == 65
|
|
323
|
+
else false
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
private_class_method :valid_pubkey_size?
|
|
327
|
+
|
|
328
|
+
# Whether this script is a P2PK script which is standard.
|
|
329
|
+
# @return [Boolean]
|
|
330
|
+
def standard_p2pk?
|
|
331
|
+
chunks.size == 2 && chunks[0].pushdata? && chunks[1].ord == OP_CHECKSIG &&
|
|
332
|
+
Bitcoin::Script.send(:valid_pubkey_size?, chunks[0].pushed_data)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Whether this script is a bare multisig script which is standard, m-of-n with 1 <= m <= n <= 3.
|
|
336
|
+
# @return [Boolean]
|
|
337
|
+
def standard_multisig?
|
|
338
|
+
return false if chunks.size < 4 || chunks.last.ord != OP_CHECKMULTISIG
|
|
339
|
+
m = chunks[0].opcode
|
|
340
|
+
n = chunks[-2].opcode
|
|
341
|
+
return false unless m && n && (OP_1..OP_16).cover?(m) && (OP_1..OP_16).cover?(n)
|
|
342
|
+
m = Opcodes.opcode_to_small_int(m)
|
|
343
|
+
n = Opcodes.opcode_to_small_int(n)
|
|
344
|
+
keys = chunks[1...-2]
|
|
345
|
+
return false unless keys.size == n && keys.all? { |k| k.pushdata? && Bitcoin::Script.send(:valid_pubkey_size?, k.pushed_data) }
|
|
346
|
+
n <= 3 && m <= n
|
|
347
|
+
end
|
|
348
|
+
|
|
290
349
|
def op_return?
|
|
291
350
|
chunks.size >= 1 && chunks[0].ord == OP_RETURN
|
|
292
351
|
end
|
|
293
352
|
|
|
353
|
+
# Whether this script is an OP_RETURN script which is standard, OP_RETURN followed by pushes only.
|
|
354
|
+
# @return [Boolean]
|
|
294
355
|
def standard_op_return?
|
|
295
|
-
op_return? && size <= MAX_OP_RETURN_RELAY &&
|
|
296
|
-
(chunks.size == 1 || chunks[1].opcode <= OP_16)
|
|
356
|
+
op_return? && size <= MAX_OP_RETURN_RELAY && chunks[1..-1].all? { |c| c.opcode.nil? || c.opcode <= OP_16 }
|
|
297
357
|
end
|
|
298
358
|
|
|
299
359
|
def op_return_data
|
|
@@ -334,11 +394,14 @@ module Bitcoin
|
|
|
334
394
|
end
|
|
335
395
|
|
|
336
396
|
# get witness commitment
|
|
397
|
+
# The witness commitment this script carries, if it is a commitment output as Bitcoin Core's
|
|
398
|
+
# GetWitnessCommitmentIndex recognizes it: at least 38 bytes which begin with OP_RETURN, a push of
|
|
399
|
+
# 0x24 bytes and the header aa21a9ed. Anything after the 32 byte commitment is ignored.
|
|
400
|
+
# @return [String, nil] The commitment with hex format, or nil if this is not a commitment output.
|
|
337
401
|
def witness_commitment
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
buf.read(32).bth
|
|
402
|
+
payload = to_payload
|
|
403
|
+
return nil if payload.bytesize < 38 || payload[0, 6] != "\x6a\x24".b + WITNESS_COMMITMENT_HEADER.htb
|
|
404
|
+
payload[6, 32].bth
|
|
342
405
|
end
|
|
343
406
|
|
|
344
407
|
# If this script is witness program, return its script code,
|
|
@@ -372,6 +435,24 @@ module Bitcoin
|
|
|
372
435
|
end
|
|
373
436
|
end
|
|
374
437
|
|
|
438
|
+
# Push +n+ as a script number. Unlike #push_int, a number which is also an opcode, e.g. 172
|
|
439
|
+
# (OP_CHECKSIG), is not appended as that opcode.
|
|
440
|
+
# @raise [ArgumentError] If +n+ is out of -0xffffffff..0xffffffff.
|
|
441
|
+
def push_script_number(n)
|
|
442
|
+
unless n.between?(-MAX_SCRIPT_STRING_NUMBER, MAX_SCRIPT_STRING_NUMBER)
|
|
443
|
+
raise ArgumentError, 'decimal numeric value only allowed in the range -0xFFFFFFFF...0xFFFFFFFF.'
|
|
444
|
+
end
|
|
445
|
+
if n == 0
|
|
446
|
+
chunks << Opcodes::OP_0.chr
|
|
447
|
+
elsif n == -1 || n.between?(1, 16)
|
|
448
|
+
chunks << Opcodes.small_int_to_opcode(n).chr
|
|
449
|
+
else
|
|
450
|
+
append_data(Script.encode_number(n))
|
|
451
|
+
end
|
|
452
|
+
self
|
|
453
|
+
end
|
|
454
|
+
private :push_script_number
|
|
455
|
+
|
|
375
456
|
# push integer to stack.
|
|
376
457
|
def push_int(n)
|
|
377
458
|
begin
|
|
@@ -425,8 +506,8 @@ module Bitcoin
|
|
|
425
506
|
if v
|
|
426
507
|
v
|
|
427
508
|
else
|
|
428
|
-
|
|
429
|
-
|
|
509
|
+
if c.valid_pushdata_length?
|
|
510
|
+
data = c.pushed_data
|
|
430
511
|
if data.bytesize <= 4
|
|
431
512
|
Script.decode_number(data.bth) # for scriptnum
|
|
432
513
|
else
|
|
@@ -67,7 +67,7 @@ module Bitcoin
|
|
|
67
67
|
raise 'stack cannot be empty.' if stack.empty?
|
|
68
68
|
redeem_script = Bitcoin::Script.parse_from_payload(stack.pop.htb)
|
|
69
69
|
return false unless eval_script(redeem_script, :base)
|
|
70
|
-
return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last)
|
|
70
|
+
return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last.htb)
|
|
71
71
|
|
|
72
72
|
# P2SH witness program
|
|
73
73
|
if flag?(SCRIPT_VERIFY_WITNESS) && redeem_script.witness_program?
|
|
@@ -77,6 +77,8 @@ module Bitcoin
|
|
|
77
77
|
|
|
78
78
|
version, program = redeem_script.witness_data
|
|
79
79
|
return false unless verify_witness_program(witness, version, program, true)
|
|
80
|
+
# Bypass the cleanstack check at the end. The actual stack is obviously not clean for witness programs.
|
|
81
|
+
stack.resize!(1, Script.encode_number(0))
|
|
80
82
|
end
|
|
81
83
|
end
|
|
82
84
|
|
|
@@ -182,7 +184,7 @@ module Bitcoin
|
|
|
182
184
|
return false unless eval_script(script_pubkey, sig_version, opts: opts)
|
|
183
185
|
|
|
184
186
|
return set_error(SCRIPT_ERR_CLEANSTACK) unless stack.size == 1
|
|
185
|
-
return set_error(SCRIPT_ERR_EVAL_FALSE) unless cast_to_bool(stack.last)
|
|
187
|
+
return set_error(SCRIPT_ERR_EVAL_FALSE) unless cast_to_bool(stack.last.htb)
|
|
186
188
|
true
|
|
187
189
|
end
|
|
188
190
|
|
|
@@ -324,7 +326,7 @@ module Bitcoin
|
|
|
324
326
|
3.times { stack << stack[-3] }
|
|
325
327
|
when OP_IFDUP
|
|
326
328
|
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
|
|
327
|
-
stack << stack.last if cast_to_bool(stack.last)
|
|
329
|
+
stack << stack.last if cast_to_bool(stack.last.htb)
|
|
328
330
|
when OP_RIPEMD160
|
|
329
331
|
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
|
|
330
332
|
stack << Digest::RMD160.hexdigest(pop_string.htb)
|
|
@@ -483,6 +485,7 @@ module Bitcoin
|
|
|
483
485
|
|
|
484
486
|
push_int(success ? num + 1 : num)
|
|
485
487
|
when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
|
|
488
|
+
return set_error(SCRIPT_ERR_TAPSCRIPT_CHECKMULTISIG) if sig_version == :tapscript # BIP-342
|
|
486
489
|
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
|
|
487
490
|
pubkey_count = pop_int
|
|
488
491
|
unless (0..MAX_PUBKEYS_PER_MULTISIG).include?(pubkey_count)
|
|
@@ -505,7 +508,8 @@ module Bitcoin
|
|
|
505
508
|
|
|
506
509
|
sigs = pop_string(sig_count)
|
|
507
510
|
sigs = [sigs] if sigs.is_a?(String)
|
|
508
|
-
|
|
511
|
+
all_sigs = sigs.dup # the loop below drops a signature once it verifies
|
|
512
|
+
|
|
509
513
|
subscript = script.subscript(opts[:begincodehash]..-1)
|
|
510
514
|
|
|
511
515
|
if sig_version == :base
|
|
@@ -532,8 +536,9 @@ module Bitcoin
|
|
|
532
536
|
end
|
|
533
537
|
|
|
534
538
|
if !success && flag?(SCRIPT_VERIFY_NULLFAIL)
|
|
535
|
-
|
|
536
|
-
|
|
539
|
+
# If the operation failed, we require that all signatures must be empty vector, including
|
|
540
|
+
# the ones which did verify, as Bitcoin Core checks every signature element on the stack.
|
|
541
|
+
all_sigs.each do |sig|
|
|
537
542
|
return set_error(SCRIPT_ERR_SIG_NULLFAIL) if sig.bytesize > 0
|
|
538
543
|
end
|
|
539
544
|
end
|
|
@@ -63,11 +63,16 @@ module Bitcoin
|
|
|
63
63
|
|
|
64
64
|
begin
|
|
65
65
|
sighash = tx.sighash_for_input(input_index, opts: opts, hash_type: hash_type, sig_version: sig_version, prevouts: prevouts)
|
|
66
|
-
key = Key.from_xonly_pubkey(pubkey)
|
|
67
|
-
key.verify(sig, sighash, algo: :schnorr)
|
|
68
66
|
rescue ArgumentError
|
|
69
67
|
return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE)
|
|
70
68
|
end
|
|
69
|
+
begin
|
|
70
|
+
key = Key.from_xonly_pubkey(pubkey)
|
|
71
|
+
rescue ArgumentError
|
|
72
|
+
# A public key which is not on the curve makes the signature fail, as XOnlyPubKey::VerifySchnorr in Bitcoin Core does.
|
|
73
|
+
return set_error(SCRIPT_ERR_SCHNORR_SIG)
|
|
74
|
+
end
|
|
75
|
+
key.verify(sig, sighash, algo: :schnorr)
|
|
71
76
|
end
|
|
72
77
|
|
|
73
78
|
def check_locktime(locktime)
|
|
@@ -14,11 +14,15 @@ module Bitcoin
|
|
|
14
14
|
return self.new if buf.eof?
|
|
15
15
|
size = Bitcoin.unpack_var_int_from_io(buf)
|
|
16
16
|
stack = size.times.map do
|
|
17
|
-
|
|
17
|
+
Bitcoin.read_exact(buf, Bitcoin.unpack_var_int_from_io(buf))
|
|
18
18
|
end
|
|
19
19
|
self.new(stack)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
def ==(other)
|
|
23
|
+
other.is_a?(ScriptWitness) && stack == other.stack
|
|
24
|
+
end
|
|
25
|
+
|
|
22
26
|
def empty?
|
|
23
27
|
stack.empty?
|
|
24
28
|
end
|
|
@@ -102,14 +102,20 @@ module Bitcoin
|
|
|
102
102
|
digest = ECDSA.normalize_digest(data, group.bit_length)
|
|
103
103
|
field = ECDSA::PrimeField.new(group.order)
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
# Bit 1 of the recovery id says the x coordinate of R is r + n rather than r. It only fits in the
|
|
106
|
+
# field when r < p - n, and libsecp256k1 rejects the signature otherwise. r + n must not be
|
|
107
|
+
# reduced modulo n here, which would turn it back into r.
|
|
108
|
+
x = r
|
|
109
|
+
if rec & 2 != 0
|
|
110
|
+
return nil if r >= group.field.prime - group.order
|
|
111
|
+
x = r + group.order
|
|
107
112
|
end
|
|
108
113
|
|
|
109
114
|
is_odd = (rec & 1 == 1)
|
|
110
|
-
y_coordinate = group.solve_for_y(
|
|
115
|
+
y_coordinate = group.solve_for_y(x).find{|y| is_odd ? y.odd? : y.even?}
|
|
116
|
+
return nil if y_coordinate.nil? # x is not on the curve
|
|
111
117
|
|
|
112
|
-
p = group.new_point([
|
|
118
|
+
p = group.new_point([x, y_coordinate])
|
|
113
119
|
|
|
114
120
|
inv_r = field.inverse(r)
|
|
115
121
|
u1 = field.mod(inv_r * digest)
|
|
@@ -134,16 +134,19 @@ module Bitcoin
|
|
|
134
134
|
end
|
|
135
135
|
end
|
|
136
136
|
elsif prevout.p2sh?
|
|
137
|
-
|
|
137
|
+
redeem_payload = input.script_sig.chunks.last&.pushed_data
|
|
138
|
+
return nil if redeem_payload.nil? # an input without a scriptSig, or whose last element is not a push
|
|
139
|
+
redeem_script = Bitcoin::Script.parse_from_payload(redeem_payload)
|
|
138
140
|
if redeem_script.p2wpkh?
|
|
139
141
|
pk = input.script_witness.stack.last
|
|
140
|
-
return Bitcoin::Key.new(pubkey: pk.bth) if pk.bytesize == Bitcoin::Key::COMPRESSED_PUBLIC_KEY_SIZE
|
|
142
|
+
return Bitcoin::Key.new(pubkey: pk.bth) if pk && pk.bytesize == Bitcoin::Key::COMPRESSED_PUBLIC_KEY_SIZE
|
|
141
143
|
end
|
|
142
144
|
elsif prevout.p2wpkh?
|
|
143
145
|
pk = input.script_witness.stack.last
|
|
144
|
-
return Bitcoin::Key.new(pubkey: pk.bth) if pk.bytesize == Bitcoin::Key::COMPRESSED_PUBLIC_KEY_SIZE
|
|
146
|
+
return Bitcoin::Key.new(pubkey: pk.bth) if pk && pk.bytesize == Bitcoin::Key::COMPRESSED_PUBLIC_KEY_SIZE
|
|
145
147
|
elsif prevout.p2tr?
|
|
146
148
|
witness_stack = input.script_witness.stack.dup
|
|
149
|
+
return nil if witness_stack.empty?
|
|
147
150
|
witness_stack.pop if witness_stack.last.bth.start_with?("50")
|
|
148
151
|
if witness_stack.length > 1
|
|
149
152
|
# script-path
|
data/lib/bitcoin/slip39/share.rb
CHANGED
|
@@ -5,7 +5,8 @@ module Bitcoin
|
|
|
5
5
|
class Share
|
|
6
6
|
|
|
7
7
|
attr_accessor :id # 15 bits, Integer
|
|
8
|
-
attr_accessor :
|
|
8
|
+
attr_accessor :extendable # 1 bit, Boolean. The extendable backup flag.
|
|
9
|
+
attr_accessor :iteration_exp # 4 bits, Integer
|
|
9
10
|
attr_accessor :group_index # 4 bits, Integer
|
|
10
11
|
attr_accessor :group_threshold # 4 bits, Integer
|
|
11
12
|
attr_accessor :group_count # 4 bits, Integer
|
|
@@ -14,6 +15,24 @@ module Bitcoin
|
|
|
14
15
|
attr_accessor :value # 8n bits, hex string.
|
|
15
16
|
attr_accessor :checksum # 30 bits, Integer
|
|
16
17
|
|
|
18
|
+
def initialize
|
|
19
|
+
@extendable = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Whether the extendable backup flag is set.
|
|
23
|
+
# When it is set, the identifier is not used as the salt of the master secret encryption, so sets of shares
|
|
24
|
+
# with different identifiers can be created for the same master secret and passphrase.
|
|
25
|
+
# @return [Boolean]
|
|
26
|
+
def extendable?
|
|
27
|
+
!!extendable
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The customization string of the RS1024 checksum for this share.
|
|
31
|
+
# @return [Array[Integer]]
|
|
32
|
+
def customization_string
|
|
33
|
+
extendable? ? CUSTOMIZATION_STRING_EXTENDABLE : CUSTOMIZATION_STRING_ORIG
|
|
34
|
+
end
|
|
35
|
+
|
|
17
36
|
# Recover Share from the mnemonic words
|
|
18
37
|
# @param [Array{String}] words the mnemonic words
|
|
19
38
|
# @return [Bitcoin::SLIP39::Share] a share
|
|
@@ -26,15 +45,19 @@ module Bitcoin
|
|
|
26
45
|
end
|
|
27
46
|
|
|
28
47
|
raise ArgumentError, 'Invalid mnemonic length.' if indices.size < MIN_MNEMONIC_LENGTH_WORDS
|
|
29
|
-
raise ArgumentError, 'Invalid mnemonic checksum.' unless verify_rs1024_checksum(indices)
|
|
30
|
-
|
|
31
|
-
padding_length = (RADIX_BITS * (indices.size - METADATA_LENGTH_WORDS)) % 16
|
|
32
|
-
raise ArgumentError, 'Invalid mnemonic length.' if padding_length > 8
|
|
33
48
|
data = indices.map{|i|i.to_s(2).rjust(10, '0')}.join
|
|
34
49
|
|
|
35
50
|
s = self.new
|
|
36
51
|
s.id = data[0...ID_LENGTH_BITS].to_i(2)
|
|
37
|
-
|
|
52
|
+
# The flag selects the customization string, so it is read before the checksum is verified.
|
|
53
|
+
s.extendable = data[ID_LENGTH_BITS] == '1'
|
|
54
|
+
raise ArgumentError, 'Invalid mnemonic checksum.' unless verify_rs1024_checksum(indices, s.customization_string)
|
|
55
|
+
|
|
56
|
+
padding_length = (RADIX_BITS * (indices.size - METADATA_LENGTH_WORDS)) % 16
|
|
57
|
+
raise ArgumentError, 'Invalid mnemonic length.' if padding_length > 8
|
|
58
|
+
|
|
59
|
+
exp_start = ID_LENGTH_BITS + EXTENDABLE_FLAG_LENGTH_BITS
|
|
60
|
+
s.iteration_exp = data[exp_start...(exp_start + ITERATION_EXP_LENGTH_BITS)].to_i(2)
|
|
38
61
|
s.group_index = data[20...24].to_i(2)
|
|
39
62
|
s.group_threshold = data[24...28].to_i(2) + 1
|
|
40
63
|
s.group_count = data[28...32].to_i(2) + 1
|
|
@@ -84,7 +107,9 @@ module Bitcoin
|
|
|
84
107
|
# @param [Boolean] include_checksum whether include checksum when creating indices.
|
|
85
108
|
# @param [Array[Integer]] the array of index
|
|
86
109
|
def build_word_indices(include_checksum = true)
|
|
110
|
+
raise ArgumentError, "Iteration exponent(#{iteration_exp}) must be between 0 and #{MAX_ITERATION_EXP}." unless (0..MAX_ITERATION_EXP).cover?(iteration_exp)
|
|
87
111
|
s = id.to_bits(ID_LENGTH_BITS)
|
|
112
|
+
s << (extendable? ? '1' : '0')
|
|
88
113
|
s << iteration_exp.to_bits(ITERATION_EXP_LENGTH_BITS)
|
|
89
114
|
s << group_index.to_bits(4)
|
|
90
115
|
s << (group_threshold - 1).to_bits(4)
|
|
@@ -105,16 +130,17 @@ module Bitcoin
|
|
|
105
130
|
|
|
106
131
|
# Verify RS1024 checksum
|
|
107
132
|
# @param [Array[Integer] data the array of mnemonic word index
|
|
133
|
+
# @param [Array[Integer]] cs the customization string
|
|
108
134
|
# @return [Boolean] verify result
|
|
109
|
-
def self.verify_rs1024_checksum(data)
|
|
110
|
-
rs1024_polymod(
|
|
135
|
+
def self.verify_rs1024_checksum(data, cs)
|
|
136
|
+
rs1024_polymod(cs + data) == 1
|
|
111
137
|
end
|
|
112
138
|
|
|
113
139
|
# Create RS1024 checksum
|
|
114
140
|
# @param [Array[Integer] data the array of mnemonic word index without checksum
|
|
115
141
|
# @return [Array[Integer]] the array of checksum integer
|
|
116
142
|
def create_rs1024_checksum(data)
|
|
117
|
-
values =
|
|
143
|
+
values = customization_string + data + Array.new(CHECKSUM_LENGTH_WORDS, 0)
|
|
118
144
|
polymod = Bitcoin::SLIP39::Share.rs1024_polymod(values) ^ 1
|
|
119
145
|
CHECKSUM_LENGTH_WORDS.times.to_a.reverse.map {|i|(polymod >> (10 * i)) & 1023 }
|
|
120
146
|
end
|
data/lib/bitcoin/slip39/sss.rb
CHANGED
|
@@ -32,9 +32,15 @@ module Bitcoin
|
|
|
32
32
|
# @param [Integer] exp Iteration exponent. default is 0.
|
|
33
33
|
# @param [String] secret master secret with hex format.
|
|
34
34
|
# @param [String] passphrase the passphrase used for encryption/decryption.
|
|
35
|
+
# @param [Boolean] extendable the extendable backup flag. default is true, as SLIP-39 recommends.
|
|
36
|
+
# When it is set, the identifier is not used as the salt of the encryption, so another set of shares with
|
|
37
|
+
# a different identifier can be created later for the same master secret and passphrase.
|
|
38
|
+
# Shares with the flag set cannot be recovered by bitcoinrb 1.15.0 and earlier, nor by other implementations
|
|
39
|
+
# which predate the flag.
|
|
35
40
|
# @return [Array[Array[Bitcoin::SLIP39::Share]]] array of group shares.
|
|
36
|
-
def self.setup_shares(groups: [], group_threshold: nil, exp: 0, secret: nil, passphrase: '')
|
|
41
|
+
def self.setup_shares(groups: [], group_threshold: nil, exp: 0, secret: nil, passphrase: '', extendable: true)
|
|
37
42
|
raise ArgumentError, 'Groups is empty.' if groups.empty?
|
|
43
|
+
raise ArgumentError, "Iteration exponent(#{exp}) must be between 0 and #{MAX_ITERATION_EXP}." unless (0..MAX_ITERATION_EXP).cover?(exp)
|
|
38
44
|
raise ArgumentError, 'Group threshold must be greater than 0.' if group_threshold.nil? || group_threshold < 1
|
|
39
45
|
raise ArgumentError, 'Master secret does not specified.' unless secret
|
|
40
46
|
raise ArgumentError, "The length of the master secret (#{secret.htb.bytesize} bytes) must be at least #{MIN_STRENGTH_BITS / 8} bytes." if (secret.htb.bytesize * 8) < MIN_STRENGTH_BITS
|
|
@@ -48,7 +54,7 @@ module Bitcoin
|
|
|
48
54
|
end
|
|
49
55
|
|
|
50
56
|
id = SecureRandom.random_number(32767) # 32767 is max number for 15 bits.
|
|
51
|
-
ems = encrypt(secret, passphrase, exp, id)
|
|
57
|
+
ems = encrypt(secret, passphrase, exp, id, extendable)
|
|
52
58
|
|
|
53
59
|
group_shares = split_secret(group_threshold, groups.length, ems)
|
|
54
60
|
|
|
@@ -59,6 +65,7 @@ module Bitcoin
|
|
|
59
65
|
shares.map do |member_index, member_share|
|
|
60
66
|
share = Bitcoin::SLIP39::Share.new
|
|
61
67
|
share.id = id
|
|
68
|
+
share.extendable = extendable
|
|
62
69
|
share.iteration_exp = exp
|
|
63
70
|
share.group_index = group_index
|
|
64
71
|
share.group_threshold = group_threshold
|
|
@@ -83,17 +90,20 @@ module Bitcoin
|
|
|
83
90
|
# @param [String] passphrase the passphrase using decrypt master secret.
|
|
84
91
|
# @param [Boolean] legacy_salt if true, build the PBKDF2 salt with the minimum number of bytes for the
|
|
85
92
|
# identifier. Shares generated by bitcoinrb 1.14.0 and earlier need this to be recovered.
|
|
93
|
+
# It has no effect on shares whose extendable backup flag is set, since their salt does not hold the identifier.
|
|
86
94
|
# @return [String] a master secret.
|
|
87
95
|
def self.recover_secret(shares, passphrase: '', legacy_salt: false)
|
|
88
96
|
raise ArgumentError, 'share is empty.' if shares.nil? || shares.empty?
|
|
89
97
|
groups = {}
|
|
90
98
|
id = shares[0].id
|
|
99
|
+
extendable = shares[0].extendable?
|
|
91
100
|
exp = shares[0].iteration_exp
|
|
92
101
|
group_threshold = shares.first.group_threshold
|
|
93
102
|
group_count = shares.first.group_count
|
|
94
103
|
|
|
95
104
|
shares.each do |share|
|
|
96
105
|
raise ArgumentError, 'Invalid set of shares. All shares must have the same id.' unless id == share.id
|
|
106
|
+
raise ArgumentError, 'Invalid set of shares. All shares must have the same extendable backup flag.' unless extendable == share.extendable?
|
|
97
107
|
raise ArgumentError, 'Invalid set of shares. All shares must have the same group threshold.' unless group_threshold == share.group_threshold
|
|
98
108
|
raise ArgumentError, 'Invalid set of shares. All shares must have the same group count.' unless group_count == share.group_count
|
|
99
109
|
raise ArgumentError, 'Invalid set of shares. All Shares must have the same iteration exponent.' unless exp == share.iteration_exp
|
|
@@ -129,7 +139,7 @@ module Bitcoin
|
|
|
129
139
|
end
|
|
130
140
|
end
|
|
131
141
|
|
|
132
|
-
return decrypt(group_shares.values.first, passphrase, exp, id, legacy_salt) if group_threshold == 1
|
|
142
|
+
return decrypt(group_shares.values.first, passphrase, exp, id, extendable, legacy_salt) if group_threshold == 1
|
|
133
143
|
|
|
134
144
|
raise ArgumentError, "Wrong number of mnemonics. Group threshold is #{group_threshold}, but share count is #{group_shares.length}" if group_shares.length < group_threshold
|
|
135
145
|
|
|
@@ -140,7 +150,7 @@ module Bitcoin
|
|
|
140
150
|
recover_digest = create_digest(secret, random_value)
|
|
141
151
|
raise ArgumentError, 'Invalid digest of the shared secret.' unless digest == recover_digest
|
|
142
152
|
|
|
143
|
-
decrypt(secret, passphrase, exp, id, legacy_salt)
|
|
153
|
+
decrypt(secret, passphrase, exp, id, extendable, legacy_salt)
|
|
144
154
|
end
|
|
145
155
|
|
|
146
156
|
private
|
|
@@ -170,10 +180,11 @@ module Bitcoin
|
|
|
170
180
|
# @param [String] passphrase the passphrase when using encrypt master secret with binary format.
|
|
171
181
|
# @param [Integer] exp iteration exponent
|
|
172
182
|
# @param [Integer] id identifier
|
|
183
|
+
# @param [Boolean] extendable the extendable backup flag
|
|
173
184
|
# @param [Boolean] legacy_salt if true, build the salt with the minimum number of bytes for the identifier.
|
|
174
|
-
def self.decrypt(ems, passphrase, exp, id, legacy_salt = false)
|
|
185
|
+
def self.decrypt(ems, passphrase, exp, id, extendable, legacy_salt = false)
|
|
175
186
|
l, r = ems[0...(ems.length / 2)].htb, ems[(ems.length / 2)..-1].htb
|
|
176
|
-
salt = get_salt(id, legacy_salt)
|
|
187
|
+
salt = get_salt(id, extendable: extendable, legacy: legacy_salt)
|
|
177
188
|
e = (Bitcoin::SLIP39::BASE_ITERATION_COUNT << exp) / Bitcoin::SLIP39::ROUND_COUNT
|
|
178
189
|
Bitcoin::SLIP39::ROUND_COUNT.times.to_a.reverse.each do |i|
|
|
179
190
|
f = OpenSSL::PKCS5.pbkdf2_hmac((i.itb + passphrase), salt + r, e, r.bytesize, 'sha256')
|
|
@@ -187,11 +198,12 @@ module Bitcoin
|
|
|
187
198
|
# @param [String] passphrase the passphrase when using encrypt master secret with binary format.
|
|
188
199
|
# @param [Integer] exp iteration exponent
|
|
189
200
|
# @param [Integer] id identifier
|
|
201
|
+
# @param [Boolean] extendable the extendable backup flag
|
|
190
202
|
# @return [String] encrypted master secret with hex format.
|
|
191
|
-
def self.encrypt(secret, passphrase, exp, id)
|
|
203
|
+
def self.encrypt(secret, passphrase, exp, id, extendable)
|
|
192
204
|
s = secret.htb
|
|
193
205
|
l, r = s[0...(s.bytesize / 2)], s[(s.bytesize / 2)..-1]
|
|
194
|
-
salt = get_salt(id)
|
|
206
|
+
salt = get_salt(id, extendable: extendable)
|
|
195
207
|
e = (Bitcoin::SLIP39::BASE_ITERATION_COUNT << exp) / Bitcoin::SLIP39::ROUND_COUNT
|
|
196
208
|
Bitcoin::SLIP39::ROUND_COUNT.times.to_a.each do |i|
|
|
197
209
|
f = OpenSSL::PKCS5.pbkdf2_hmac((i.itb + passphrase), salt + r, e, r.bytesize, 'sha256')
|
|
@@ -209,14 +221,17 @@ module Bitcoin
|
|
|
209
221
|
h[0...4].bth
|
|
210
222
|
end
|
|
211
223
|
|
|
212
|
-
# get salt
|
|
224
|
+
# get salt prefix used in encryption/decryption.
|
|
213
225
|
# @param [Integer] id id
|
|
226
|
+
# @param [Boolean] extendable the extendable backup flag. If true, the salt is empty since the identifier
|
|
227
|
+
# is not part of the encryption.
|
|
214
228
|
# @param [Boolean] legacy if true, encode the identifier with the minimum number of bytes. SLIP-39 requires
|
|
215
229
|
# two bytes, so a salt built this way only matches shares generated by bitcoinrb 1.14.0 and earlier.
|
|
216
230
|
# @return [String] salt with binary format.
|
|
217
|
-
def self.get_salt(id, legacy
|
|
231
|
+
def self.get_salt(id, extendable: false, legacy: false)
|
|
232
|
+
return '' if extendable
|
|
218
233
|
identifier = legacy ? id.itb : [id].pack('n')
|
|
219
|
-
(Bitcoin::SLIP39::
|
|
234
|
+
(Bitcoin::SLIP39::CUSTOMIZATION_STRING_ORIG.pack('c*') + identifier)
|
|
220
235
|
end
|
|
221
236
|
|
|
222
237
|
# Split the share into +count+ with threshold +threshold+.
|
data/lib/bitcoin/slip39.rb
CHANGED
|
@@ -19,18 +19,26 @@ module Bitcoin
|
|
|
19
19
|
RADIX = 2 ** RADIX_BITS
|
|
20
20
|
# The length of the random identifier in bits.
|
|
21
21
|
ID_LENGTH_BITS = 15
|
|
22
|
+
# The length of the extendable backup flag in bits.
|
|
23
|
+
EXTENDABLE_FLAG_LENGTH_BITS = 1
|
|
22
24
|
# The length of the iteration exponent in bits.
|
|
23
|
-
ITERATION_EXP_LENGTH_BITS =
|
|
24
|
-
# The
|
|
25
|
-
|
|
25
|
+
ITERATION_EXP_LENGTH_BITS = 4
|
|
26
|
+
# The maximum iteration exponent.
|
|
27
|
+
MAX_ITERATION_EXP = (1 << ITERATION_EXP_LENGTH_BITS) - 1
|
|
28
|
+
# The length of the random identifier, extendable backup flag and iteration exponent in words.
|
|
29
|
+
ID_EXP_LENGTH_WORDS = bits_to_words(ID_LENGTH_BITS + EXTENDABLE_FLAG_LENGTH_BITS + ITERATION_EXP_LENGTH_BITS)
|
|
26
30
|
# The maximum number of shares that can be created.
|
|
27
31
|
MAX_SHARE_COUNT = 16
|
|
28
32
|
# The length of the RS1024 checksum in words.
|
|
29
33
|
CHECKSUM_LENGTH_WORDS = 3
|
|
30
34
|
# The length of the digest of the shared secret in bytes.
|
|
31
35
|
DIGEST_LENGTH_BYTES = 4
|
|
32
|
-
# The customization string used in the RS1024 checksum and in the PBKDF2 salt.
|
|
33
|
-
|
|
36
|
+
# The customization string used in the RS1024 checksum and in the PBKDF2 salt when the extendable backup flag is not set.
|
|
37
|
+
CUSTOMIZATION_STRING_ORIG = 'shamir'.bytes
|
|
38
|
+
# The customization string used in the RS1024 checksum when the extendable backup flag is set.
|
|
39
|
+
CUSTOMIZATION_STRING_EXTENDABLE = 'shamir_extendable'.bytes
|
|
40
|
+
# Deprecated alias of CUSTOMIZATION_STRING_ORIG.
|
|
41
|
+
CUSTOMIZATION_STRING = CUSTOMIZATION_STRING_ORIG
|
|
34
42
|
# The length of the mnemonic in words without the share value.
|
|
35
43
|
METADATA_LENGTH_WORDS = ID_EXP_LENGTH_WORDS + 2 + CHECKSUM_LENGTH_WORDS
|
|
36
44
|
# The minimum allowed entropy of the master secret.
|
|
@@ -31,6 +31,18 @@ module Bitcoin
|
|
|
31
31
|
raise NotImplementedError
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
# Generating a control block from a nested tree is not supported yet.
|
|
35
|
+
# @raise [NotImplementedError]
|
|
36
|
+
def control_block(leaf)
|
|
37
|
+
raise NotImplementedError, 'CustomDepthBuilder does not support control_block yet.'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Generating an inclusion proof from a nested tree is not supported yet.
|
|
41
|
+
# @raise [NotImplementedError]
|
|
42
|
+
def inclusion_proof(leaf)
|
|
43
|
+
raise NotImplementedError, 'CustomDepthBuilder does not support inclusion_proof yet.'
|
|
44
|
+
end
|
|
45
|
+
|
|
34
46
|
private
|
|
35
47
|
|
|
36
48
|
def merkle_root
|