mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
Merge bitcoin/bitcoin#31640: tests: improves tapscript unit tests
e3d7533ac9
test: improves tapscript unit tests (Ethan Heilman)3e167085ba
test: Ensures test fails if witness is not hex (Ethan Heilman) Pull request description: 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. This code was originally part of the OP_CAT PR https://github.com/bitcoin/bitcoin/pull/29247 but was pulled out into a separate PR to reduce the rebase treadmill for the OP_CAT PR. Additionally this PR adds a check to ensure that if the witness data can not be parsed as hex the test fails. Prior to this PR, the test code would fail silently and set the values it couldn't parse as empty stack elements. This fix was suggested by @instagibbs. ## Rationale While writing JSON script tests (script_tests.json) for https://github.com/bitcoin/bitcoin/pull/29247 we ran into the following problem. The JSON script tests are simple and easy to write for pre-Tapscript scripts, but adding or changing a Tapscript test requires substantial work per test. Consider the following pre-tapscript test: ``` ["'aa' 'bb'", "CAT 0x4c 0x02 0xaabb EQUAL", "P2SH,STRICTENC", "DISABLED_OPCODE", "CAT disabled"] ```` whereas a Tapscript test for the same script (annotated with comments for better readability) would look like: ``` [ [ "aa", "bb", "7e4c02aabb87", // output script "c0d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d", // control block 0.00000001 ], "", "0x51 0x20 0x15048ed3a65748549c27b671936987093cf73a4c9cb18522a74fb9553060ca99", // Tapscript output "P2SH,WITNESS,TAPROOT", "OK", "TAPSCRIPT CATs aa and bb together and checks if EQUAL to aabb" ] ``` Computing the Tapscript output, such as `0x51 0x20 0x15048ed3a65748549c27b671936987093cf73a4c9cb18522a74fb9553060ca99`, requires writing custom code and running it for each test. The same is true for the Tapscript control block, such as `c0d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d`. If a test is changed or updated new outputs and control blocks must be computed. The complexity of doing this is likely the reason that no one has added any Tapscript tests to JSON script tests until this PR. In this PR we address this issue by adding the following improvements to JSON script tests: Adding simple macros ("#SCRIPT# and #CONTROLBLOCK#) that allow the script test parser to automatically generate and inject a valid Tapscript output and control block to be computed automatically from the JSON script. Allowing Tapscript scripts to use the human readable strings like pre-script scripts by marking the location of the script in the witness stack using #SCRIPT#. This transforms the unreadable script 7e4c02aabb87 into #SCRIPT# CAT 0x4c 0x02 0xaabb EQUAL. This results in the following JSON script test which is far easier to write and easier to read. ``` [ [ "aa", "bb", "#SCRIPT# CAT", "#CONTROLBLOCK#", 0.00000001 ], "", "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "OK", "TAPSCRIPT Test of OP_CAT flag by calling CAT on two elements. TAPSCRIPT_OP_CAT flag is set so CAT is executed." ], ``` ACKs for top commit: instagibbs: reACKe3d7533ac9
sipa: utACKe3d7533ac9
janb84: Re ACK [e3d7533
](e3d7533ac9
) Tree-SHA512: 948c3ec28a4b2b222c2d77e48918ed19d298b51d64662fc20959073edd9978fc796516a392da9755a7e173f556e3021816dc6ce8eb3ed16bbe0fa6ebc574fd48
This commit is contained in:
commit
728e86e3f3
2 changed files with 88 additions and 2 deletions
|
@ -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"
|
||||
],
|
||||
[
|
||||
[
|
||||
"abcdef",
|
||||
"#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"
|
||||
],
|
||||
[
|
||||
[
|
||||
"abcdef",
|
||||
"#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"
|
||||
],
|
||||
[
|
||||
[
|
||||
"aa",
|
||||
"bb",
|
||||
"cc",
|
||||
"#SCRIPT# EQUAL IF DROP DROP ENDIF",
|
||||
"#CONTROLBLOCK#",
|
||||
0.00000001
|
||||
],
|
||||
"",
|
||||
"0x51 0x20 #TAPROOTOUTPUT#",
|
||||
"P2SH,WITNESS,TAPROOT",
|
||||
"OK",
|
||||
"TAPSCRIPT Test that DROP operations do not 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"],
|
||||
|
|
|
@ -916,16 +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++) {
|
||||
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
|
||||
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());
|
||||
}
|
||||
}
|
||||
nValue = AmountFromValue(test[pos][i]);
|
||||
pos++;
|
||||
|
@ -940,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());
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue