diff --git a/test/functional/feature_tapscript_opcodes.py b/test/functional/feature_tapscript_opcodes.py new file mode 100755 index 0000000000..8d83d6851e --- /dev/null +++ b/test/functional/feature_tapscript_opcodes.py @@ -0,0 +1,147 @@ +#!/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 test_framework.key import compute_xonly_pubkey, generate_privkey, sign_schnorr, tweak_add_privkey, tweak_add_pubkey, verify_schnorr +from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, CTxOutValue, CTxOutWitness, FromHex, sha256, uint256_from_str +from test_framework.test_framework import BitcoinTestFramework +from test_framework.script import CScript, OP_1, OP_EQUAL, OP_SWAP, OP_SHA256FINALIZE, OP_SHA256INITIALIZE, OP_SHA256UPDATE, TaprootSignatureHash, 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 create_taproot_utxo(self, scripts = None): + # 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 = FromHex(CTransaction(), 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 = FromHex(CTransaction(), 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) + _txid = self.nodes[0].sendrawtransaction(signed_raw_tx['hex']) + tx = FromHex(CTransaction(), signed_raw_tx['hex']) + tx.rehash() + self.nodes[0].generate(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 = []): + + # Create a taproot utxo + scripts = [("s0", script)] + prev_tx, prev_vout, spk, sec, pub, tap = self.create_taproot_utxo(scripts) + + # Spend the pegin and taproot tx together + in_total = prev_tx.vout[prev_vout].nValue.getAmount() + fees = 1000 + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(prev_tx.sha256, prev_vout))) + tx.vout.append(CTxOut(nValue = CTxOutValue(in_total - fees), scriptPubKey = spk)) # send back to self + tx.vout.append(CTxOut(CTxOutValue(fees))) + + suffix_annex = [] + control_block = bytes([tap.leaves["s0"].version + tap.negflag]) + tap.inner_pubkey + tap.leaves["s0"].merklebranch + wit = inputs + [bytes(tap.leaves["s0"].script), control_block] + suffix_annex + tx.wit.vtxinwit.append(CTxInWitness()) + tx.wit.vtxinwit[0].scriptWitness.stack = wit + + self.nodes[0].sendrawtransaction(hexstring = tx.serialize().hex()) + self.nodes[0].generate(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.nodes[0].generate(101) + self.wait_until(lambda: self.nodes[0].getblockcount() == 101, timeout=5) + # 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 upto len 80 can be filled in a single standard witness element + # Testing only random even len for speedup + 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]) + + +if __name__ == '__main__': + TapHashPeginTest().main() \ No newline at end of file diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 8e033ded9d..cc9ceb324d 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -255,6 +255,11 @@ OP_NOP10 = CScriptOp(0xb9) # BIP 342 opcodes (Tapscript) OP_CHECKSIGADD = CScriptOp(0xba) +# Elements Tapscript extension +OP_SHA256INITIALIZE = CScriptOp(0xc4) +OP_SHA256UPDATE = CScriptOp(0xc5) +OP_SHA256FINALIZE = CScriptOp(0xc6) + OP_INVALIDOPCODE = CScriptOp(0xff) OPCODE_NAMES.update({ @@ -936,4 +941,4 @@ def taproot_construct(pubkey, scripts=None): return TaprootInfo(CScript([OP_1, tweaked]), pubkey, negated + 0, tweak, leaves) def is_op_success(o): - return o == 80 or o == 98 or (o >= 137 and o <= 138) or (o >= 141 and o <= 142) or (o >= 149 and o <= 151) or (o >= 187 and o <= 191) or (o >= 196 and o <= 254) + return o == 80 or o == 98 or (o >= 137 and o <= 138) or (o >= 141 and o <= 142) or (o >= 149 and o <= 151) or (o >= 187 and o <= 191) or (o >= 223 and o <= 254) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 87f1434184..d41978d5b4 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -135,6 +135,7 @@ BASE_SCRIPTS = [ 'wallet_listtransactions.py --descriptors', 'feature_taproot.py', 'feature_taphash_pegins_issuances.py', + 'feature_tapscript_opcodes.py', # vv Tests less than 60s vv 'p2p_sendheaders.py', 'wallet_importmulti.py --legacy-wallet',