mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-13 12:33:42 +02:00
The first transaction was failing with bad-txns-inputs-missingorspent because the "input" selected with fundrawtransaction was the anyonecanspend initialfreecoins. Fixed by first spending the initialfreecoins to "real" outputs.
603 lines
34 KiB
Python
Executable file
603 lines
34 KiB
Python
Executable file
#!/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()
|
|
|
|
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("Instrospection 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("Instrospection 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("Instrospection 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("Instrospection 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 instrospection
|
|
self.log.info("Instrospection 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()
|