-
Notifications
You must be signed in to change notification settings - Fork 388
/
Copy pathfeature_tapscript_opcodes.py
executable file
·604 lines (509 loc) · 34 KB
/
feature_tapscript_opcodes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
#!/usr/bin/env python3
# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
#
# Test for taproot sighash algorithm with pegins and issuances
from random import randint
from test_framework.util import BITCOIN_ASSET_BYTES, assert_raises_rpc_error, satoshi_round
from test_framework.key import ECKey, compute_xonly_pubkey, generate_privkey, sign_schnorr
from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, CTxOutNonce, CTxOutValue, ser_uint256, sha256, tx_from_hex
from test_framework.test_framework import BitcoinTestFramework
from test_framework.script import CScript, CScriptNum, CScriptOp, OP_0, OP_1, OP_2, OP_ADD64, OP_AND, OP_CHECKSIGFROMSTACK, OP_DIV64, OP_DROP, OP_DUP, OP_ECMULSCALARVERIFY, OP_EQUAL, OP_EQUALVERIFY, OP_FALSE, OP_FROMALTSTACK, OP_GREATERTHAN64, OP_GREATERTHANOREQUAL64, OP_INSPECTINPUTASSET, OP_INSPECTINPUTISSUANCE, OP_INSPECTINPUTOUTPOINT, OP_INSPECTINPUTSCRIPTPUBKEY, OP_INSPECTINPUTSEQUENCE, OP_INSPECTINPUTVALUE, OP_INSPECTLOCKTIME, OP_INSPECTNUMINPUTS, OP_INSPECTNUMOUTPUTS, OP_INSPECTOUTPUTASSET, OP_INSPECTOUTPUTNONCE, OP_INSPECTOUTPUTSCRIPTPUBKEY, OP_INSPECTOUTPUTVALUE, OP_INSPECTVERSION, OP_LE32TOLE64, OP_LE64TOSCRIPTNUM, OP_LESSTHAN64, OP_LESSTHANOREQUAL64, OP_MUL64, OP_NEG64, OP_NOT, OP_INVERT, OP_OR, OP_PUSHCURRENTINPUTINDEX, OP_SCRIPTNUMTOLE64, OP_SHA256FINALIZE, OP_SHA256INITIALIZE, OP_SHA256UPDATE, OP_SIZE, OP_SUB64, OP_SWAP, OP_TWEAKVERIFY, OP_TOALTSTACK, OP_TXWEIGHT, OP_VERIFY, OP_XOR, taproot_construct, SIGHASH_DEFAULT, SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE, SIGHASH_ANYONECANPAY
import os
VALID_SIGHASHES_ECDSA = [
SIGHASH_ALL,
SIGHASH_NONE,
SIGHASH_SINGLE,
SIGHASH_ANYONECANPAY + SIGHASH_ALL,
SIGHASH_ANYONECANPAY + SIGHASH_NONE,
SIGHASH_ANYONECANPAY + SIGHASH_SINGLE
]
VALID_SIGHASHES_TAPROOT = [SIGHASH_DEFAULT] + VALID_SIGHASHES_ECDSA
class TapHashPeginTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [
["-initialfreecoins=2100000000000000",
"-anyonecanspendaremine=1",
"-blindedaddresses=1",
"-validatepegin=0",
"-con_parent_chain_signblockscript=51",
"-parentscriptprefix=75",
"-parent_bech32_hrp=ert",
"-minrelaytxfee=0",
"-maxtxfee=100.0",
]]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
self.skip_if_no_bdb()
def setup_network(self, split=False):
self.setup_nodes()
def get_utxo(self, fund_tx, idx):
spent = None
# Coin selection
for utxo in self.nodes[0].listunspent():
if utxo["txid"] == ser_uint256(fund_tx.vin[idx].prevout.hash)[::-1].hex() and utxo["vout"] == fund_tx.vin[idx].prevout.n:
spent = utxo
assert(spent is not None)
assert(len(fund_tx.vin) == 2)
return spent
def create_taproot_utxo(self, scripts = None, blind = False):
# modify the transaction to add one output that should spend previous taproot
# Create a taproot prevout
addr = self.nodes[0].getnewaddress()
sec = generate_privkey()
pub = compute_xonly_pubkey(sec)[0]
tap = taproot_construct(pub, scripts)
spk = tap.scriptPubKey
# No need to test blinding in this unit test
unconf_addr = self.nodes[0].getaddressinfo(addr)['unconfidential']
raw_tx = self.nodes[0].createrawtransaction([], [{unconf_addr: 1.2}])
# edit spk directly, no way to get new address.
# would need to implement bech32m in python
tx = tx_from_hex(raw_tx)
tx.vout[0].scriptPubKey = spk
tx.vout[0].nValue = CTxOutValue(12*10**7)
raw_hex = tx.serialize().hex()
fund_tx = self.nodes[0].fundrawtransaction(raw_hex, False, )["hex"]
fund_tx = tx_from_hex(fund_tx)
# Createrawtransaction might rearrage txouts
prev_vout = None
for i, out in enumerate(fund_tx.vout):
if spk == out.scriptPubKey:
prev_vout = i
tx = self.nodes[0].blindrawtransaction(fund_tx.serialize().hex())
signed_raw_tx = self.nodes[0].signrawtransactionwithwallet(tx)
self.nodes[0].sendrawtransaction(signed_raw_tx['hex'])
tx = tx_from_hex(signed_raw_tx['hex'])
tx.rehash()
self.generate(self.nodes[0], 1)
last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash())
assert(tx.hash in last_blk['tx'])
return tx, prev_vout, spk, sec, pub, tap
def tapscript_satisfy_test(self, script, inputs = None, add_issuance = False,
add_pegin = False, fail=None, add_prevout=False, add_asset=False,
add_value=False, add_spk = False, seq = 0, add_out_spk = None,
add_out_asset = None, add_out_value = None, add_out_nonce = None,
ver = 2, locktime = 0, add_num_outputs=False, add_weight=False, blind=False):
if inputs is None:
inputs = []
# Create a taproot utxo
scripts = [("s0", script)]
prev_tx, prev_vout, spk, sec, pub, tap = self.create_taproot_utxo(scripts)
if add_pegin:
fund_info = self.nodes[0].getpeginaddress()
peg_id = self.nodes[0].sendtoaddress(fund_info["mainchain_address"], 1)
raw_peg_tx = self.nodes[0].gettransaction(peg_id)["hex"]
peg_txid = self.nodes[0].sendrawtransaction(raw_peg_tx)
self.generate(self.nodes[0], 101)
peg_prf = self.nodes[0].gettxoutproof([peg_txid])
claim_script = fund_info["claim_script"]
raw_claim = self.nodes[0].createrawpegin(raw_peg_tx, peg_prf, claim_script)
tx = tx_from_hex(raw_claim['hex'])
else:
tx = CTransaction()
tx.nVersion = ver
tx.nLockTime = locktime
# Spend the pegin and taproot tx together
in_total = prev_tx.vout[prev_vout].nValue.getAmount()
fees = 1000
tap_in_pos = 0
if blind:
# Add an unrelated output
key = ECKey()
key.generate()
tx.vout.append(CTxOut(nValue = CTxOutValue(10000), scriptPubKey = spk, nNonce=CTxOutNonce(key.get_pubkey().get_bytes())))
tx_hex = self.nodes[0].fundrawtransaction(tx.serialize().hex())
tx = tx_from_hex(tx_hex['hex'])
tx.vin.append(CTxIn(COutPoint(prev_tx.sha256, prev_vout), nSequence=seq))
tx.vout.append(CTxOut(nValue = CTxOutValue(in_total - fees), scriptPubKey = spk)) # send back to self
tx.vout.append(CTxOut(CTxOutValue(fees)))
if add_issuance:
blind_addr = self.nodes[0].getnewaddress()
issue_addr = self.nodes[0].validateaddress(blind_addr)['unconfidential']
# Issuances only require one fee output and that output must the last
# one. However the way, the current code is structured, it is not possible
# to this in a super clean without special casing.
if add_pegin:
tx.vout.pop()
tx.vout.pop()
tx.vout.insert(0, CTxOut(nValue = CTxOutValue(in_total), scriptPubKey = spk)) # send back to self)
issued_tx = self.nodes[0].rawissueasset(tx.serialize().hex(), [{"asset_amount":2, "asset_address":issue_addr, "blind":False}])[0]["hex"]
tx = tx_from_hex(issued_tx)
# Sign inputs
if add_pegin:
signed = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())
tx = tx_from_hex(signed['hex'])
tap_in_pos += 1
else:
# Need to create empty witness when not deserializing from rpc
tx.wit.vtxinwit.append(CTxInWitness())
if blind:
tx.vin[0], tx.vin[1] = tx.vin[1], tx.vin[0]
utxo = self.get_utxo(tx, 1)
zero_str = "0"*64
blinded_raw = self.nodes[0].rawblindrawtransaction(tx.serialize().hex(), [zero_str, utxo["amountblinder"]], [1.2, utxo['amount']], [utxo['asset'], utxo['asset']], [zero_str, utxo['assetblinder']])
tx = tx_from_hex(blinded_raw)
signed_raw_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())
tx = tx_from_hex(signed_raw_tx['hex'])
suffix_annex = []
control_block = bytes([tap.leaves["s0"].version + tap.negflag]) + tap.internal_pubkey + tap.leaves["s0"].merklebranch
# Add the prevout to the top of inputs. The witness script will check for equality.
if add_prevout:
inputs = [prev_vout.to_bytes(4, 'little'), ser_uint256(prev_tx.sha256)]
if add_asset:
assert blind # only used with blinding in testing
utxo = self.nodes[0].gettxout(ser_uint256(tx.vin[1].prevout.hash)[::-1].hex(), tx.vin[1].prevout.n)
if "assetcommitment" in utxo:
asset = bytes.fromhex(utxo["assetcommitment"])
else:
asset = b"\x01" + bytes.fromhex(utxo["asset"])[::-1]
inputs = [asset[0:1], asset[1:33]]
if add_value:
utxo = self.nodes[0].gettxout(ser_uint256(tx.vin[1].prevout.hash)[::-1].hex(), tx.vin[1].prevout.n)
if "valuecommitment" in utxo:
value = bytes.fromhex(utxo["valuecommitment"])
inputs = [value[0:1], value[1:33]]
else:
value = b"\x01" + int(satoshi_round(utxo["value"])*COIN).to_bytes(8, 'little')
inputs = [value[0:1], value[1:9]]
if add_spk:
ver = CScriptOp.decode_op_n(int.from_bytes(spk[0:1], 'little'))
inputs = [CScriptNum.encode(CScriptNum(ver))[1:], spk[2:len(spk)]] # always segwit
# Add witness for outputs
if add_out_asset is not None:
asset = tx.vout[add_out_asset].nAsset.vchCommitment
inputs = [asset[0:1], asset[1:33]]
if add_out_value is not None:
value = tx.vout[add_out_value].nValue.vchCommitment
if len(value) == 9:
inputs = [value[0:1], value[1:9][::-1]]
else:
inputs = [value[0:1], value[1:33]]
if add_out_nonce is not None:
nonce = tx.vout[add_out_nonce].nNonce.vchCommitment
if len(nonce) == 1:
inputs = [b'']
else:
inputs = [nonce]
if add_out_spk is not None:
out_spk = tx.vout[add_out_spk].scriptPubKey
if len(out_spk) == 0:
# Python upstream encoding CScriptNum interesting behaviour where it also encodes the length
# This assumes the implicit wallet behaviour of using segwit outputs.
# This is useful while sending scripts, but not while using CScriptNums in constructing scripts
inputs = [CScriptNum.encode(CScriptNum(-1))[1:], sha256(out_spk)]
else:
ver = CScriptOp.decode_op_n(int.from_bytes(out_spk[0:1], 'little'))
inputs = [CScriptNum.encode(CScriptNum(ver))[1:], out_spk[2:len(out_spk)]] # always segwit
if add_num_outputs:
num_outs = len(tx.vout)
inputs = [CScriptNum.encode(CScriptNum(num_outs))[1:]]
if add_weight:
# Add a dummy input and check the overall weight
inputs = [int(5).to_bytes(8, 'little')]
wit = inputs + [bytes(tap.leaves["s0"].script), control_block] + suffix_annex
tx.wit.vtxinwit[tap_in_pos].scriptWitness.stack = wit
exp_weight = self.nodes[0].decoderawtransaction(tx.serialize().hex())["weight"]
inputs = [exp_weight.to_bytes(8, 'little')]
wit = inputs + [bytes(tap.leaves["s0"].script), control_block] + suffix_annex
tx.wit.vtxinwit[tap_in_pos].scriptWitness.stack = wit
if fail:
assert_raises_rpc_error(-26, fail, self.nodes[0].sendrawtransaction, tx.serialize().hex())
return
self.nodes[0].sendrawtransaction(hexstring = tx.serialize().hex())
self.generate(self.nodes[0], 1)
last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash())
tx.rehash()
assert(tx.hash in last_blk['tx'])
def run_test(self):
self.generate(self.nodes[0], 101)
self.wait_until(lambda: self.nodes[0].getblockcount() == 101, timeout=5)
# ELEMENTS: spend the initialfreecoins to node0, to fix bad-txns-inputs-missingorspent error when creating the first taproot utxo
self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 50)
self.generate(self.nodes[0], 1)
# Test whether the above test framework is working
self.log.info("Test simple op_1")
self.tapscript_satisfy_test(CScript([OP_1]))
# Test streaming sha256
# All preimages up to len 80 can be filled in a single standard witness element
# Testing only random even len for speedup
self.log.info("Testing streaming SHA256")
for k in range(0, 81, 2):
preimage = os.urandom(k)
hash = sha256(preimage)
self.tapscript_satisfy_test(CScript([OP_SHA256INITIALIZE, OP_SWAP, OP_SHA256FINALIZE, hash, OP_EQUAL]), inputs = [bytes(0), preimage])
# All preimages greater than 80 require Cating the elements
# But this only works till 520 bytes, streaming opcodes can
# be when dealing with more than 520 bytes
# Also adds a test when the sha256 buffer is full
for pre_len in [64*i for i in range(2, 10)] + [100, 520, 521, 800, 1000, 10000]:
preimage = os.urandom(pre_len)
hash = sha256(preimage)
# Split into chunks of 80
wit = [preimage[i:min(i + 80, len(preimage))] for i in range(0, len(preimage), 80)]
script = CScript([OP_SHA256INITIALIZE] + [OP_SWAP, OP_SHA256UPDATE]*(len(wit)-2) + [OP_SWAP, OP_SHA256FINALIZE, hash, OP_EQUAL])
self.tapscript_satisfy_test(script, wit[::-1])
# Test introspection opcodes
# 1a. No Pegins/issuances
self.log.info("Introspection tests: outpoint flag")
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTOUTPOINT, b'\x00', OP_EQUALVERIFY, OP_DROP, OP_DROP, OP_1]))
# 1b. Add a pegin (Test pegin input must be 0x40)
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTOUTPOINT, b'\x40', OP_EQUALVERIFY, OP_DROP, OP_DROP, OP_1]), add_pegin=True)
# 1c. Add pegin (The non-pegin input must be push 0x00)
self.tapscript_satisfy_test(CScript([OP_1, OP_INSPECTINPUTOUTPOINT, b'\x00', OP_EQUALVERIFY, OP_DROP, OP_DROP, OP_1]), add_pegin=True)
# 1d. Add an issuance
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTOUTPOINT, b'\x80', OP_EQUALVERIFY, OP_DROP, OP_DROP, OP_1]), add_issuance=True)
# 1e. Both issuance and pegins together
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTOUTPOINT, b'\xc0', OP_EQUALVERIFY, OP_DROP, OP_DROP, OP_1]), add_pegin = True, add_issuance=True)
# 1f. Failure test case
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTOUTPOINT, b'\x00', OP_EQUALVERIFY, OP_DROP, OP_DROP, OP_1]), add_pegin = True, add_issuance=True, fail="Script failed an OP_EQUALVERIFY operation")
# Test opcode for inspecting prev tx
self.log.info("Introspection tests: inputs")
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTOUTPOINT, b'\x00', OP_EQUALVERIFY, OP_TOALTSTACK, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_EQUAL]), add_prevout=True)
# Test taproot asset with blinding.
self.tapscript_satisfy_test(CScript([OP_1]), blind=True)
# 2 Test asset(explicit and conf)
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTASSET, 1, OP_EQUALVERIFY, BITCOIN_ASSET_BYTES, OP_EQUAL]))
for _ in range(5): # run multiple times for wallet to select conf utxos
self.tapscript_satisfy_test(CScript([OP_1, OP_INSPECTINPUTASSET, OP_TOALTSTACK, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_EQUAL]), blind=True, add_asset=True)
# 3 Test asset(explicit and conf)
amt = 12*10**7
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTVALUE, 1, OP_EQUALVERIFY, amt.to_bytes(8, 'little'), OP_EQUAL]))
for _ in range(5): # run multiple times for wallet to select conf utxos
self.tapscript_satisfy_test(CScript([OP_1, OP_INSPECTINPUTVALUE, OP_TOALTSTACK, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_EQUAL]), blind=True, add_value=True)
# 4 Test scriptpubkey
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTSCRIPTPUBKEY, OP_TOALTSTACK, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_EQUAL]), add_spk = True)
# 5 Test nSequence
one, two = 1, 2
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTSEQUENCE, one.to_bytes(4, 'little'), OP_EQUAL]), seq = 1)
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTSEQUENCE, two.to_bytes(4, 'little'), OP_EQUAL]), seq = 1, fail="Script evaluated without error but finished with a false/empty top stack element")
# 6 Test issuances
# No issaunce
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTISSUANCE, OP_FALSE, OP_EQUAL]))
# Issuance with 2 units
asset_issue_amount = 2*COIN
self.tapscript_satisfy_test(CScript([
OP_0, OP_INSPECTINPUTISSUANCE,
# Check blinding nonce is zero
bytes(32), OP_EQUALVERIFY,
# Check the entropy is 32 bytes
OP_SIZE, 32, OP_EQUALVERIFY, OP_DROP,
# Check the explicit issue amount
1, OP_EQUALVERIFY, asset_issue_amount.to_bytes(8, 'little'), OP_EQUALVERIFY,
# Last inflation keys is null, check it as explicit `0` LE 8
1, OP_EQUALVERIFY, int(0).to_bytes(8, 'little'), OP_EQUAL
]), add_issuance=True)
# Input index out of bounds
self.tapscript_satisfy_test(CScript([120, OP_1, OP_INSPECTINPUTASSET, OP_FALSE, OP_EQUAL]), fail="Introspection index out of bounds")
self.tapscript_satisfy_test(CScript([-1, OP_1, OP_INSPECTINPUTVALUE, OP_FALSE, OP_EQUAL]), fail="Introspection index out of bounds")
# Test current input
self.log.info("Introspection tests: current input index")
self.tapscript_satisfy_test(CScript([OP_PUSHCURRENTINPUTINDEX, OP_0, OP_EQUAL]))
self.tapscript_satisfy_test(CScript([OP_PUSHCURRENTINPUTINDEX, OP_1, OP_EQUAL]), fail="Script evaluated without error but finished with a false/empty top stack element")
# Test Outputs
self.log.info("Introspection tests: outputs")
for blind in [True, False]:
for out_pos in [0, 1]:
self.tapscript_satisfy_test(CScript([out_pos, OP_INSPECTOUTPUTASSET, OP_TOALTSTACK, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_EQUAL]), blind=blind, add_out_asset=out_pos)
self.tapscript_satisfy_test(CScript([out_pos, OP_INSPECTOUTPUTVALUE, OP_TOALTSTACK, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_EQUAL]), blind=blind, add_out_value=out_pos)
self.tapscript_satisfy_test(CScript([out_pos, OP_INSPECTOUTPUTNONCE, OP_EQUAL]), blind=blind, add_out_nonce=out_pos)
self.tapscript_satisfy_test(CScript([out_pos, OP_INSPECTOUTPUTSCRIPTPUBKEY, OP_TOALTSTACK, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_EQUALVERIFY, OP_1]), blind=blind, add_out_spk=out_pos)
# Test that output index out of bounds fail
self.tapscript_satisfy_test(CScript([120, OP_INSPECTOUTPUTASSET, OP_FALSE, OP_EQUAL]), fail="Introspection index out of bounds")
self.tapscript_satisfy_test(CScript([-1, OP_INSPECTOUTPUTVALUE, OP_FALSE, OP_EQUAL]), fail="Introspection index out of bounds")
# Finally, check the tx introspection
self.log.info("Introspection tests: tx")
# Test version equality
self.tapscript_satisfy_test(CScript([OP_INSPECTVERSION, int(2).to_bytes(4, 'little'), OP_EQUAL]), ver = 2)
self.tapscript_satisfy_test(CScript([OP_INSPECTVERSION, int(5).to_bytes(4, 'little'), OP_EQUAL]), ver = 2, fail="Script evaluated without error but finished with a false/empty top stack element")
# Test nlocktime
self.tapscript_satisfy_test(CScript([OP_INSPECTLOCKTIME, int(7).to_bytes(4, 'little'), OP_EQUAL]), locktime = 7)
self.tapscript_satisfy_test(CScript([OP_INSPECTLOCKTIME, int(7).to_bytes(4, 'little'), OP_EQUAL]), locktime = 10, fail="Script evaluated without error but finished with a false/empty top stack element")
# Test num_inputs
self.tapscript_satisfy_test(CScript([OP_INSPECTNUMINPUTS, OP_1, OP_EQUAL]))
self.tapscript_satisfy_test(CScript([OP_INSPECTNUMINPUTS, OP_2, OP_EQUAL]), fail="Script evaluated without error but finished with a false/empty top stack element")
# Test num_outputs
self.tapscript_satisfy_test(CScript([OP_INSPECTNUMOUTPUTS, OP_EQUAL]), add_num_outputs= True)
# Test tx weight
self.tapscript_satisfy_test(CScript([OP_TXWEIGHT, OP_EQUAL]), add_weight= True)
# short handle to convert int to 8 byte LE
def le8(x, signed=True):
return int(x).to_bytes(8, 'little', signed=signed)
def check_add(a, b, c, fail=None):
self.tapscript_satisfy_test(CScript([OP_ADD64, OP_VERIFY, le8(c), OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_sub(a, b, c, fail=None):
self.tapscript_satisfy_test(CScript([OP_SUB64, OP_VERIFY, le8(c), OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_mul(a, b, c, fail=None):
self.tapscript_satisfy_test(CScript([OP_MUL64, OP_VERIFY, le8(c), OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_div(a, b, q, r, fail=None):
self.tapscript_satisfy_test(CScript([OP_DIV64, OP_VERIFY, le8(q), OP_EQUALVERIFY, le8(r), OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_le(a, b, res, fail=None):
self.tapscript_satisfy_test(CScript([OP_LESSTHAN64, res, OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_leq(a, b, res, fail=None):
self.tapscript_satisfy_test(CScript([OP_LESSTHANOREQUAL64, res, OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_ge(a, b, res, fail=None):
self.tapscript_satisfy_test(CScript([OP_GREATERTHAN64, res, OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_geq(a, b, res, fail=None):
self.tapscript_satisfy_test(CScript([OP_GREATERTHANOREQUAL64, res, OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_neg(a, res, fail=None):
self.tapscript_satisfy_test(CScript([OP_NEG64, OP_VERIFY, le8(res), OP_EQUAL]), inputs = [le8(a)], fail=fail)
# Arithematic opcodes
self.log.info("Check Arithmetic opcodes")
check_add(5, 5, 10)
check_add(-5, -5, -10)
check_add(-5, 5, 0)
check_add(14231, -123213, 14231 - 123213)
check_add(2**63 - 1, 5, 4, fail="Script failed an OP_VERIFY operation")#overflow
check_add(5, 2**63 - 1, 4, fail="Script failed an OP_VERIFY operation")#overflow
check_add(-5, -2**63 + 1, -4, fail="Script failed an OP_VERIFY operation")#overflow
# Subtraction
check_sub(5, 6, -1)
check_sub(-5, 6, -11)
check_sub(-5, -5, 0)
check_sub(14231, -123213, 14231 + 123213)
check_sub(2**63 - 1, 4, 2**63 - 5)
check_sub(-5, 2**63 - 1, -4, fail="Script failed an OP_VERIFY operation")#overflow
check_sub(2**63 - 1, -4, 5, fail="Script failed an OP_VERIFY operation")#overflow
# Multiplication
check_mul(5, 6, 30)
check_mul(-5, 6, -30)
check_mul(-5, 0, 0)
check_mul(-5, -6, 30)
check_mul(14231, -123213, -14231 * 123213)
check_mul(2**32, 2**31 - 1, 2**63 - 2**32)
check_mul(2**32, 2**31, 0, fail="Script failed an OP_VERIFY operation")#overflow
check_mul(-2**32, 2**31, -2**63)# no overflow
check_mul(-2**32, -2**32, 0, fail="Script failed an OP_VERIFY operation")#overflow
# Division
check_div(5, 6, 0, 5)
check_div(4, 2, 2, 0)
check_div(-5, 6, -1, 1) # r must be in 0<=r<a
check_div(5, -6, 0, 5) # r must be in 0<=r<a
check_div(-5, 0, 0, 0, fail="Script failed an OP_VERIFY operation") # only fails if b = 0
check_div(-2**63, -1, 0, 0, fail="Script failed an OP_VERIFY operation") # failure on -2**63/-1
check_div(6213123213513, 621, 6213123213513//621, 6213123213513 % 621)
check_div(2**62, 2**31, 2**31, 0)
# Less than test
# Comparison tests
# Less than
check_le(5, 6, 1)
check_le(5, 5, 0)
check_le(6, 5, 0)
# Less than equal
check_leq(5, 6, 1)
check_leq(5, 5, 1)
check_leq(6, 5, 0)
# Greater than
check_ge(5, 6, 0)
check_ge(5, 5, 0)
check_ge(6, 5, 1)
# Greater than equal
check_geq(5, 6, 0)
check_geq(5, 5, 1)
check_geq(6, 5, 1)
# equal
check_neg(5, -5)
check_neg(-5, 5)
check_neg(5, 4, fail="Script evaluated without error but finished with a false/empty top stack element")
check_neg(-2**63, 0, fail="Script failed an OP_VERIFY operation")
check_neg(2**63-1, -2**63 + 1)
# Test boolean operations
for _ in range(5):
a = randint(-2**63, 2**63 -1)
b = randint(-2**63, 2**63 -1)
self.tapscript_satisfy_test(CScript([OP_AND, le8(a & b), OP_EQUAL]), inputs = [le8(a), le8(b)])
self.tapscript_satisfy_test(CScript([OP_OR, le8(a | b), OP_EQUAL]), inputs = [le8(a), le8(b)])
self.tapscript_satisfy_test(CScript([OP_INVERT, le8(~a), OP_EQUAL]), inputs = [le8(a)])
self.tapscript_satisfy_test(CScript([OP_XOR, le8(a ^ b), OP_EQUAL]), inputs = [le8(a), le8(b)])
# Finally, test conversion opcodes
self.log.info("Check conversion opcodes")
for n in [-2**31 + 1, -1, 0, 1, 2**31 - 1]:
self.tapscript_satisfy_test(CScript([le8(n), OP_LE64TOSCRIPTNUM, n, OP_EQUAL]))
self.tapscript_satisfy_test(CScript([n, OP_SCRIPTNUMTOLE64, le8(n), OP_EQUAL]))
self.tapscript_satisfy_test(CScript([n, OP_DUP, OP_SCRIPTNUMTOLE64, OP_LE64TOSCRIPTNUM, OP_EQUAL]))
self.tapscript_satisfy_test(CScript([le8(n), OP_DUP, OP_LE64TOSCRIPTNUM, OP_SCRIPTNUMTOLE64, OP_EQUAL]))
self.tapscript_satisfy_test(CScript([n.to_bytes(4, 'little', signed=True), OP_LE32TOLE64, le8(n), OP_EQUAL]), fail= "Script evaluated without error but finished with a false/empty top stack element" if (n < 0) else None)
# Non 8 byte inputs
self.tapscript_satisfy_test(CScript([OP_ADD64, OP_VERIFY, le8(9), OP_EQUAL]), inputs = [le8(0), int(9).to_bytes(7, 'little')], fail="Arithmetic opcodes expect 8 bytes operands")
# Overflow inputs
self.tapscript_satisfy_test(CScript([le8(2**31), OP_LE64TOSCRIPTNUM, 2**31, OP_EQUAL]), fail = "Arithmetic opcode error")
self.log.info("Check Crypto opcodes")
res = bytes.fromhex("03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7")
scalar = bytes.fromhex("000000000000000000000000000000000000000000000000000000000000000a")
g = bytes.fromhex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [res, g, scalar])
res = bytes.fromhex("032c0158d0f6df4881e99e65fbea21f27321d817f79ad39e08eaf4f16f1419bb0c")
scalar = bytes.fromhex("e0f47c124f228b97bbdc0e4398aac9788869b9fbbc193d5323fdad9570609de6")
g = bytes.fromhex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [res, g, scalar])
# test that other random values fail correctly
res = bytes.fromhex("032c0158d0f6df4881e99e65fbea21f27321d817f79ad39e08eaf4f16f1419bb0c")
scalar = bytes.fromhex("e0f47c124f228b97bbdc0e4398aac9788869b9fbbc193d5323fdad9570609de6")
g = bytes.fromhex("0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")# y coordinate mutated
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [res, g, scalar], fail="Public key is neither compressed or uncompressed")
invalid_full_pks_33 = [
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81700" # invalid point on curve
]
invalid_full_pks_non33 = [
"0979be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", # 33 byte without 0x02,0x03
"0462b31419b87d5e095e9d7532f92aea39c39735253a48fbdaff48441d5cf706f9a7a946ce481411e4034143e0acad4e79aecb2a8212ee5cca26a7cc5fe5b45881", # compressed
"62b31419b87d5e095e9d7532f92aea39c39735253a48fbdaff48441d5cf706f9", # x-only key
"0262b31419b87d5e095e9d7532f92aea39c39735253a48fbdaff48441d5cf706f9f9f9" # 34 byte key
]
invalid_tweaks = [
"e07c124f228b97bbdc0e4398aac9788869b9fbbc193d5323fdad9570609de6", # non 32 byte hash
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364143" # order + 2
]
invalid_xonly_keys = [
"0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", # 33 byte valid key
"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81700", # invalid point on curve
"0462b31419b87d5e095e9d7532f92aea39c39735253a48fbdaff48441d5cf706f9a7a946ce481411e4034143e0acad4e79aecb2a8212ee5cca26a7cc5fe5b45881", # compressed
"62b31419b87d5e095e9d7532f92aea39c39735253a48fbdaff48441d5cf706" # 31 byte x-only key
]
valid_res = bytes.fromhex("032c0158d0f6df4881e99e65fbea21f27321d817f79ad39e08eaf4f16f1419bb0c")
valid_scalar = bytes.fromhex("e0f47c124f228b97bbdc0e4398aac9788869b9fbbc193d5323fdad9570609de6")
valid_g = bytes.fromhex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
valid_xonly_key = bytes.fromhex("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
for invalid_tweak in invalid_tweaks:
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [valid_res, valid_g, bytes.fromhex(invalid_tweak)], fail="EC scalar mult verify fail")
self.tapscript_satisfy_test(CScript([OP_TWEAKVERIFY, OP_1]), inputs = [valid_res, bytes.fromhex(invalid_tweaks[1]), valid_xonly_key], fail="EC scalar mult verify fail")
self.tapscript_satisfy_test(CScript([OP_TWEAKVERIFY, OP_1]), inputs = [valid_res, bytes.fromhex(invalid_tweaks[0]), valid_xonly_key], fail="Public key is neither compressed or uncompressed")
for invalid_full_pk in invalid_full_pks_33:
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [bytes.fromhex(invalid_full_pk), valid_g, valid_scalar], fail="EC scalar mult verify fail")
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [valid_res, bytes.fromhex(invalid_full_pk), valid_scalar], fail="EC scalar mult verify fail")
for invalid_full_pk in invalid_full_pks_non33:
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [bytes.fromhex(invalid_full_pk), valid_g, valid_scalar], fail="Public key is neither compressed or uncompressed")
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [valid_res, bytes.fromhex(invalid_full_pk), valid_scalar], fail="Public key is neither compressed or uncompressed")
self.tapscript_satisfy_test(CScript([OP_TWEAKVERIFY, OP_1]), inputs = [valid_res, valid_scalar, bytes.fromhex(invalid_xonly_keys[0])], fail="Public key is neither compressed or uncompressed")
self.tapscript_satisfy_test(CScript([OP_TWEAKVERIFY, OP_1]), inputs = [valid_res, valid_scalar, bytes.fromhex(invalid_xonly_keys[1])], fail="EC scalar mult verify fail")
self.tapscript_satisfy_test(CScript([OP_TWEAKVERIFY, OP_1]), inputs = [valid_res, valid_scalar, bytes.fromhex(invalid_xonly_keys[2])], fail="Public key is neither compressed or uncompressed")
self.tapscript_satisfy_test(CScript([OP_TWEAKVERIFY, OP_1]), inputs = [valid_res, valid_scalar, bytes.fromhex(invalid_xonly_keys[3])], fail="Public key is neither compressed or uncompressed")
# Checksigfromstack tests
# 32 byte msg
def csfs_test(msg, mutute_sig = False, fail=None):
sec = generate_privkey()
msg = bytes.fromhex(msg)
pub = compute_xonly_pubkey(sec)[0]
sig = sign_schnorr(sec, msg)
if mutute_sig:
new_sig = sig[:63]
new_sig += (sig[63] ^ 0xff).to_bytes(1, 'little') # flip the last bit
sig = new_sig
self.tapscript_satisfy_test(CScript([msg, pub, OP_CHECKSIGFROMSTACK]), inputs = [sig], fail=fail)
csfs_test("e168c349d0d2499caf3a6d71734c743d517f94f8571fa52c04285b68deec1936")
csfs_test("Hello World!".encode('utf-8').hex(), fail="Invalid Schnorr signature", mutute_sig=True)
msg = bytes.fromhex("e168c349d0d2499caf3a6d71734c743d517f94f8571fa52c04285b68deec1936")
pub = bytes.fromhex("3f67e97da0df6931189cfb0072447da22707897bd5de04936a277ed7e00b35b3")
self.tapscript_satisfy_test(CScript([OP_0, msg, pub, OP_CHECKSIGFROMSTACK, OP_NOT]), inputs = [])
short_sig = bytes.fromhex("0102")
self.tapscript_satisfy_test(CScript([short_sig, msg, pub, OP_CHECKSIGFROMSTACK, OP_NOT]), inputs = [], fail="Invalid Schnorr signature size")
short_pub = bytes.fromhex("67e97da0df6931189cfb0072447da22707897bd5de04936a277ed7e00b35b3")
long_pub = bytes.fromhex("67e97da0df6931189cfb0072447da22707897bd5de04936a277ed7e00b35b30908")
sig = bytes.fromhex("3f67e97da0df6931189cfb0072447da22707897bd5de04936a277ed7e00b35b33f67e97da0df6931189cfb0072447da22707897bd5de04936a277ed7e00b35b3")
self.tapscript_satisfy_test(CScript([msg, short_pub, OP_CHECKSIGFROMSTACK]), inputs = [sig], fail="Public key version reserved for soft-fork upgrades")
self.tapscript_satisfy_test(CScript([msg, long_pub, OP_CHECKSIGFROMSTACK]), inputs = [sig], fail="Public key version reserved for soft-fork upgrades")
if __name__ == '__main__':
TapHashPeginTest().main()