From 69f352abe60651efa9e1f79a45e56b209f5e31f6 Mon Sep 17 00:00:00 2001 From: eheilman Date: Fri, 10 Jan 2025 20:11:48 -0500 Subject: [PATCH] tests: improves tapscript unit tests This commit creates new test utilities for future Taproot script tests within script_tests.json. The key features of this commit are the addition of three new tags: `#SCRIPT#`, `#CONTROLBLOCK#`, and `#TAPROOTOUTPUT#`. These tags streamline the test creation process by eliminating the need to manually generate these components outside the test suite. * `#SCRIPT#`: Parses Tapscript and outputs a byte string of opcodes. * `#CONTROLBLOCK#`: Automatically generates the control block for a given Taproot output. * `#TAPROOTOUTPUT#`: Generates the final Taproot scriptPubKey. --- src/test/data/script_tests.json | 57 +++++++++++++++++++++++++++++++++ src/test/script_tests.cpp | 29 +++++++++++++++-- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/test/data/script_tests.json b/src/test/data/script_tests.json index ad05240369b..f535616522d 100644 --- a/src/test/data/script_tests.json +++ b/src/test/data/script_tests.json @@ -2610,6 +2610,63 @@ [["645168", 0.00000001], "0x22 0x0020f913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "HASH160 0x14 0xdbb7d1c0a56b7a9c423300c8cca6e6e065baf1dc EQUAL", "P2SH,WITNESS", "UNBALANCED_CONDITIONAL"], [["645168", 0.00000001], "0x22 0x0020f913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "HASH160 0x14 0xdbb7d1c0a56b7a9c423300c8cca6e6e065baf1dc EQUAL", "P2SH,WITNESS,MINIMALIF", "UNBALANCED_CONDITIONAL"], +["Tapscript tests"], +[ + [ + "1ffe1234567890", + "00", + "#SCRIPT# HASH256 DUP SHA1 DROP DUP DROP TOALTSTACK HASH256 DUP DROP TOALTSTACK FROMALTSTACK", + "#CONTROLBLOCK#", + 0.00000001 + ], + "", + "0x51 0x20 #TAPROOTOUTPUT#", + "P2SH,WITNESS,TAPROOT", + "OK", + "TAPSCRIPT Tests testing tapscript with many different op codes including ALTSTACK interactions" +], +[ + [ + "abcde", + "#SCRIPT# 1 IF SHA256 ENDIF SIZE SWAP DROP 32 EQUAL", + "#CONTROLBLOCK#", + 0.00000001 + ], + "", + "0x51 0x20 #TAPROOTOUTPUT#", + "P2SH,WITNESS,TAPROOT", + "OK", + "TAPSCRIPT Test IF conditional when true" +], +[ + [ + "abcde", + "#SCRIPT# 0 IF SHA256 ENDIF SIZE SWAP DROP 32 EQUAL", + "#CONTROLBLOCK#", + 0.00000001 + ], + "", + "0x51 0x20 #TAPROOTOUTPUT#", + "P2SH,WITNESS,TAPROOT", + "EVAL_FALSE", + "TAPSCRIPT Test IF conditional when false" +], +[ + [ + "a", + "b", + "c", + "#SCRIPT# EQUAL IF DROP DROP ENDIF", + "#CONTROLBLOCK#", + 0.00000001 + ], + "", + "0x51 0x20 #TAPROOTOUTPUT#", + "P2SH,WITNESS,TAPROOT", + "INVALID_STACK_OPERATION", + "TAPSCRIPT Test that push operations still execute inside of a false IF conditional" +], + ["NULLFAIL should cover all signatures and signatures only"], ["0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", "DERSIG", "OK", "BIP66 and NULLFAIL-compliant"], ["0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", "DERSIG,NULLFAIL", "OK", "BIP66 and NULLFAIL-compliant"], diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 8753ddee37f..7fba2de3fb6 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -916,16 +916,34 @@ BOOST_AUTO_TEST_CASE(script_json_test) // amount (nValue) to use in the crediting tx UniValue tests = read_json(json_tests::script_tests); + const KeyData keys; for (unsigned int idx = 0; idx < tests.size(); idx++) { const UniValue& test = tests[idx]; std::string strTest = test.write(); CScriptWitness witness; + TaprootBuilder taprootBuilder; CAmount nValue = 0; unsigned int pos = 0; if (test.size() > 0 && test[pos].isArray()) { unsigned int i=0; for (i = 0; i < test[pos].size()-1; i++) { - witness.stack.push_back(ParseHex(test[pos][i].get_str())); + auto element = test[pos][i].get_str(); + // We use #SCRIPT# to flag a non-hex script that we can read using ParseScript + // Taproot script must be third from the last element in witness stack + std::string scriptFlag = std::string("#SCRIPT#"); + if (element.starts_with(scriptFlag)) { + CScript script = ParseScript(element.substr(scriptFlag.size())); + witness.stack.push_back(ToByteVector(script)); + } else if (strcmp(element.c_str(), "#CONTROLBLOCK#") == 0) { + // Taproot script control block - second from the last element in witness stack + // If #CONTROLBLOCK# we auto-generate the control block + taprootBuilder.Add(/*depth=*/0, witness.stack.back(), TAPROOT_LEAF_TAPSCRIPT, /*track=*/true); + taprootBuilder.Finalize(XOnlyPubKey(keys.key0.GetPubKey())); + auto controlblocks = taprootBuilder.GetSpendData().scripts[{witness.stack.back(), TAPROOT_LEAF_TAPSCRIPT}]; + witness.stack.push_back(*(controlblocks.begin())); + } else { + witness.stack.push_back(ParseHex(element)); + } } nValue = AmountFromValue(test[pos][i]); pos++; @@ -940,7 +958,14 @@ BOOST_AUTO_TEST_CASE(script_json_test) std::string scriptSigString = test[pos++].get_str(); CScript scriptSig = ParseScript(scriptSigString); std::string scriptPubKeyString = test[pos++].get_str(); - CScript scriptPubKey = ParseScript(scriptPubKeyString); + CScript scriptPubKey; + // If requested, auto-generate the taproot output + if (strcmp(scriptPubKeyString.c_str(), "0x51 0x20 #TAPROOTOUTPUT#")== 0) { + BOOST_CHECK_MESSAGE(taprootBuilder.IsComplete(), "Failed to autogenerate Tapscript output key"); + scriptPubKey = CScript() << OP_1 << ToByteVector(taprootBuilder.GetOutput()); + } else { + scriptPubKey = ParseScript(scriptPubKeyString); + } unsigned int scriptflags = ParseScriptFlags(test[pos++].get_str()); int scriptError = ParseScriptError(test[pos++].get_str());