test: 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.

Update src/test/script_tests.cpp

Co-authored-by: Jan B <608446+janb84@users.noreply.github.com>
This commit is contained in:
Ethan Heilman
2025-03-20 19:00:34 -04:00
parent 3e167085ba
commit e3d7533ac9
2 changed files with 86 additions and 5 deletions

View File

@@ -916,21 +916,38 @@ 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++) {
auto element = test[pos][i].get_str();
const auto witness_value{TryParseHex<unsigned char>(element)};
if (!witness_value.has_value()) {
BOOST_ERROR("Bad witness in test: " << strTest << " witness is not hex: " << element);
// 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
static const std::string SCRIPT_FLAG{"#SCRIPT#"};
if (element.starts_with(SCRIPT_FLAG)) {
CScript script = ParseScript(element.substr(SCRIPT_FLAG.size()));
witness.stack.push_back(ToByteVector(script));
} else if (element == "#CONTROLBLOCK#") {
// 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 {
const auto witness_value{TryParseHex<unsigned char>(element)};
if (!witness_value.has_value()) {
BOOST_ERROR("Bad witness in test: " << strTest << " witness is not hex: " << element);
}
witness.stack.push_back(witness_value.value());
}
witness.stack.push_back(witness_value.value());
}
nValue = AmountFromValue(test[pos][i]);
pos++;
@@ -945,7 +962,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 (scriptPubKeyString == "0x51 0x20 #TAPROOTOUTPUT#") {
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());