|
8 | 8 | #include <common/system.h> |
9 | 9 | #include <core_io.h> |
10 | 10 | #include <key.h> |
| 11 | +#include <policy/policy.h> |
11 | 12 | #include <rpc/util.h> |
12 | 13 | #include <script/script.h> |
13 | 14 | #include <script/script_error.h> |
|
18 | 19 | #include <streams.h> |
19 | 20 | #include <test/util/json.h> |
20 | 21 | #include <test/util/random.h> |
| 22 | +#include <test/util/script.h> |
21 | 23 | #include <test/util/setup_common.h> |
22 | 24 | #include <test/util/transaction_utils.h> |
23 | 25 | #include <util/fs.h> |
@@ -1747,4 +1749,288 @@ BOOST_AUTO_TEST_CASE(compute_tapleaf) |
1747 | 1749 | BOOST_CHECK_EQUAL(ComputeTapleafHash(0xc2, std::span(script)), tlc2); |
1748 | 1750 | } |
1749 | 1751 |
|
| 1752 | +/** A test vector for OP_TEMPLATEHASH. */ |
| 1753 | +struct TemplateHashTestCase |
| 1754 | +{ |
| 1755 | + //! The transaction being validated. |
| 1756 | + const CTransaction spending_tx; |
| 1757 | + //! The outputs spent by the transaction being validated. |
| 1758 | + const std::vector<CTxOut> spent_outputs; |
| 1759 | + //! The index of the transaction input being validated. |
| 1760 | + const uint32_t input_index; |
| 1761 | + //! Whether script validation is expected to succeed. |
| 1762 | + const bool valid; |
| 1763 | + //! Description of the test vector. |
| 1764 | + const std::string comment; |
| 1765 | + |
| 1766 | + explicit TemplateHashTestCase(std::vector<CTxOut> spent_txos, CTransaction tx, uint32_t idx, bool val, std::string com): |
| 1767 | + spending_tx{std::move(tx)}, spent_outputs{std::move(spent_txos)}, input_index{idx}, valid{val}, comment{std::move(com)} {} |
| 1768 | + |
| 1769 | + UniValue GetJson() const |
| 1770 | + { |
| 1771 | + UniValue json{UniValue::VOBJ}, spent_txos{UniValue::VARR}; |
| 1772 | + for (const auto& txo: spent_outputs) { |
| 1773 | + DataStream ssTxo; |
| 1774 | + ssTxo << txo; |
| 1775 | + spent_txos.push_back(HexStr(ssTxo)); |
| 1776 | + } |
| 1777 | + json.pushKV("spent_outputs", std::move(spent_txos)); |
| 1778 | + json.pushKV("spending_tx", EncodeHexTx(spending_tx)); |
| 1779 | + json.pushKV("input_index", input_index); |
| 1780 | + json.pushKV("valid", valid); |
| 1781 | + json.pushKV("comment", comment); |
| 1782 | + return json; |
| 1783 | + } |
| 1784 | +}; |
| 1785 | + |
| 1786 | +/** Shorthand for making a copy of a vector. **/ |
| 1787 | +template<typename T> |
| 1788 | +static std::vector<T> Clone(std::vector<T>& vec) |
| 1789 | +{ |
| 1790 | + return std::vector<T>{vec}; |
| 1791 | +} |
| 1792 | + |
| 1793 | +/** Run script validation for provided tx and input index. Check it succeeds/fails according to provided validity status. **/ |
| 1794 | +static void CheckTemplateMatch(const CMutableTransaction& tx, std::vector<CTxOut> spent_outputs, unsigned in_index, |
| 1795 | + bool is_valid, std::vector<TemplateHashTestCase>& cases, std::string comment) |
| 1796 | +{ |
| 1797 | + Assert(tx.vin.size() == spent_outputs.size() && in_index < tx.vin.size()); |
| 1798 | + constexpr unsigned FLAGS{MANDATORY_SCRIPT_VERIFY_FLAGS | SCRIPT_VERIFY_TEMPLATEHASH}; |
| 1799 | + constexpr CAmount dummy_am{0}; // We never check signatures. |
| 1800 | + constexpr auto dummy_mdb{MissingDataBehavior::ASSERT_FAIL}; |
| 1801 | + const auto spent_spk{spent_outputs[in_index].scriptPubKey}; |
| 1802 | + |
| 1803 | + // Record for test vector generation. |
| 1804 | + cases.emplace_back(spent_outputs, CTransaction{tx}, in_index, is_valid, comment); |
| 1805 | + |
| 1806 | + // Perform script validation with the provided inputs. |
| 1807 | + PrecomputedTransactionData precomp; |
| 1808 | + precomp.Init(tx, std::move(spent_outputs)); |
| 1809 | + const auto checker{GenericTransactionSignatureChecker(&tx, in_index, dummy_am, precomp, dummy_mdb)}; |
| 1810 | + ScriptError err; |
| 1811 | + bool res{VerifyScript(tx.vin[in_index].scriptSig, spent_spk, &tx.vin[in_index].scriptWitness, FLAGS, checker, &err)}; |
| 1812 | + |
| 1813 | + // Check script validation result, if not valid make sure the failure is the one expected for `<hash> OP_TEMPLATEHASH OP_EQUAL`. |
| 1814 | + BOOST_CHECK_MESSAGE(res == is_valid, std::string{"Script validation unexpectedly "} + (res ? "succeeded" : "failed") + ": " + comment); |
| 1815 | + if (!is_valid) { |
| 1816 | + BOOST_CHECK_MESSAGE(err == ScriptError::SCRIPT_ERR_EVAL_FALSE, std::string{"Unexpected error for '"} + comment + "': " + ScriptErrorString(err)); |
| 1817 | + } |
| 1818 | +} |
| 1819 | + |
| 1820 | +/** Sanity check next-transaction commitments using OP_TEMPLATEHASH. */ |
| 1821 | +BOOST_AUTO_TEST_CASE(templatehash) |
| 1822 | +{ |
| 1823 | + // Record the various cases exercised in this test to optionally generate vectors at the end. |
| 1824 | + std::vector<TemplateHashTestCase> test_cases; |
| 1825 | + |
| 1826 | + // The transaction whose template hash is to be checked. |
| 1827 | + CMutableTransaction tx; |
| 1828 | + tx.vin = { |
| 1829 | + CTxIn{*Txid::FromHex("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"), 21}, |
| 1830 | + CTxIn{*Txid::FromHex("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"), 12} |
| 1831 | + }; |
| 1832 | + tx.vout.emplace_back(424242, ScriptFromHex("001482074bdf6ce32b071dd120a17cf99cbc01ad3080")); |
| 1833 | + |
| 1834 | + // Construct the script to be spent, checking the (valid) hash of this transaction. |
| 1835 | + const uint256 vanilla_hash{GetTemplateHash(tx, 0)}; |
| 1836 | + const auto leaf_script{CScript() << vanilla_hash << OP_TEMPLATEHASH << OP_EQUAL}; |
| 1837 | + TaprootBuilder builder; |
| 1838 | + builder.Add(0, leaf_script, TAPROOT_LEAF_TAPSCRIPT); |
| 1839 | + builder.Finalize(XOnlyPubKey::NUMS_H); |
| 1840 | + const CScript spent_spk{GetScriptForDestination(builder.GetOutput())}; |
| 1841 | + |
| 1842 | + // Outputs spent by the transaction whose template hash is to be checked. |
| 1843 | + CTxOut dummy_txo{1'085'986, ScriptFromHex("76a914079ded3e3befdab0757fe0e8842aeffc0ff2160288ac")}; |
| 1844 | + std::vector<CTxOut> spent_outputs{{CTxOut{424243, spent_spk}, dummy_txo}}; |
| 1845 | + |
| 1846 | + // Construct the witness data for the transaction. |
| 1847 | + const auto spend_data{builder.GetSpendData()}; |
| 1848 | + const auto control_blocks{spend_data.scripts.begin()->second}; |
| 1849 | + const auto& cb{*control_blocks.begin()}; |
| 1850 | + tx.vin[0].scriptWitness.stack.emplace_back(leaf_script.begin(), leaf_script.end()); |
| 1851 | + tx.vin[0].scriptWitness.stack.emplace_back(cb.begin(), cb.end()); |
| 1852 | + |
| 1853 | + // Script validation must pass when we use the right hash for the right input. |
| 1854 | + { |
| 1855 | + CheckTemplateMatch(tx, Clone(spent_outputs), 0, /*is_valid=*/true, test_cases, std::string{"Template hash matches. Input index matches."}); |
| 1856 | + } |
| 1857 | + |
| 1858 | + // Script validation must pass when we use the right hash for the wrong input. |
| 1859 | + { |
| 1860 | + // Swap the two inputs and corresponding spent utxos. |
| 1861 | + CMutableTransaction tx2{tx}; |
| 1862 | + std::swap(tx2.vin[0], tx2.vin[1]); |
| 1863 | + auto spent_outputs2{spent_outputs}; |
| 1864 | + std::swap(spent_outputs2[0], spent_outputs2[1]); |
| 1865 | + |
| 1866 | + CheckTemplateMatch(tx2, std::move(spent_outputs2), 1, /*is_valid=*/false, test_cases, std::string{"Template hash matches. Input index mismatches."}); |
| 1867 | + } |
| 1868 | + |
| 1869 | + // Script validation must fail if any committed field is malleated in the transaction. |
| 1870 | + // Version: |
| 1871 | + { |
| 1872 | + CMutableTransaction tx2{tx}; |
| 1873 | + tx2.version = 42; |
| 1874 | + CheckTemplateMatch(tx2, Clone(spent_outputs), 0, /*is_valid=*/false, test_cases, std::string{"Template hash mismatches: incorrect transaction version."}); |
| 1875 | + } |
| 1876 | + // Locktime: |
| 1877 | + { |
| 1878 | + CMutableTransaction tx2{tx}; |
| 1879 | + tx2.nLockTime = 42; |
| 1880 | + CheckTemplateMatch(tx2, Clone(spent_outputs), 0, /*is_valid=*/false, test_cases, std::string{"Template hash mismatches: incorrect transaction locktime."}); |
| 1881 | + } |
| 1882 | + // Output value: |
| 1883 | + { |
| 1884 | + CMutableTransaction tx2{tx}; |
| 1885 | + tx2.vout[0].nValue++; |
| 1886 | + CheckTemplateMatch(tx2, Clone(spent_outputs), 0, /*is_valid=*/false, test_cases, std::string{"Template hash mismatches: incorrect output value."}); |
| 1887 | + } |
| 1888 | + // Output script: |
| 1889 | + { |
| 1890 | + CMutableTransaction tx2{tx}; |
| 1891 | + tx2.vout[0].scriptPubKey = ScriptFromHex("001482074bdf6ce32b071dd120a17cf99cbc01ad3081"); |
| 1892 | + CheckTemplateMatch(tx2, Clone(spent_outputs), 0, /*is_valid=*/false, test_cases, std::string{"Template hash mismatches: incorrect output script."}); |
| 1893 | + } |
| 1894 | + // This input's sequence: |
| 1895 | + { |
| 1896 | + CMutableTransaction tx2{tx}; |
| 1897 | + tx2.vin[0].nSequence = 42; |
| 1898 | + CheckTemplateMatch(tx2, Clone(spent_outputs), 0, /*is_valid=*/false, test_cases, std::string{"Template hash mismatches: incorrect sequence in spending input."}); |
| 1899 | + } |
| 1900 | + // Another input's sequence: |
| 1901 | + { |
| 1902 | + CMutableTransaction tx2{tx}; |
| 1903 | + tx2.vin[1].nSequence = 42; |
| 1904 | + CheckTemplateMatch(tx2, Clone(spent_outputs), 0, /*is_valid=*/false, test_cases, std::string{"Template hash mismatches: incorrect sequence in another input."}); |
| 1905 | + } |
| 1906 | + // This input's annex: |
| 1907 | + { |
| 1908 | + CMutableTransaction tx2{tx}; |
| 1909 | + tx2.vin[0].scriptWitness.stack.push_back({ANNEX_TAG, 0}); |
| 1910 | + CheckTemplateMatch(tx2, Clone(spent_outputs), 0, /*is_valid=*/false, test_cases, std::string{"Template hash mismatches: spending input contains annex but none was committed."}); |
| 1911 | + } |
| 1912 | + |
| 1913 | + // Script validation must succeed if a non-committed transaction field is malleated. |
| 1914 | + // Another input's annex: |
| 1915 | + { |
| 1916 | + CMutableTransaction tx2{tx}; |
| 1917 | + tx2.vin[1].scriptWitness.stack.push_back({ANNEX_TAG, 'd', 'u', 'm', 'm', 'y'}); |
| 1918 | + CheckTemplateMatch(tx2, Clone(spent_outputs), 0, /*is_valid=*/true, test_cases, std::string{"Template hash matches with malleated annex for another input."}); |
| 1919 | + } |
| 1920 | + // Another input's scriptsig: |
| 1921 | + { |
| 1922 | + CMutableTransaction tx2{tx}; |
| 1923 | + tx2.vin[1].scriptSig.resize(1); |
| 1924 | + CheckTemplateMatch(tx2, Clone(spent_outputs), 0, /*is_valid=*/true, test_cases, std::string{"Template hash matches with malleated scriptSig for another input."}); |
| 1925 | + } |
| 1926 | + // This input's prevout: |
| 1927 | + COutPoint dummy_op{*Txid::FromHex("27c4d937dca276fb2b61e579902e8a876fd5b5abc17590410ced02d5a9f8e483"), 42}; |
| 1928 | + { |
| 1929 | + CMutableTransaction tx2{tx}; |
| 1930 | + tx2.vin[0].prevout = dummy_op; |
| 1931 | + CheckTemplateMatch(tx2, Clone(spent_outputs), 0, /*is_valid=*/true, test_cases, std::string{"Template hash matches with malleated prevout for spending input."}); |
| 1932 | + } |
| 1933 | + // Another input's prevout: |
| 1934 | + { |
| 1935 | + CMutableTransaction tx2{tx}; |
| 1936 | + tx2.vin[1].prevout = dummy_op; |
| 1937 | + CheckTemplateMatch(tx2, Clone(spent_outputs), 0, /*is_valid=*/true, test_cases, std::string{"Template hash matches with malleated prevout for another input."}); |
| 1938 | + } |
| 1939 | + // Spent output's value: |
| 1940 | + { |
| 1941 | + std::vector<CTxOut> spent_outputs2(spent_outputs); |
| 1942 | + ++spent_outputs2[0].nValue; |
| 1943 | + CheckTemplateMatch(tx, std::move(spent_outputs2), 0, /*is_valid=*/true, test_cases, std::string{"Template hash matches with malleated value for corresponding spent output."}); |
| 1944 | + } |
| 1945 | + // Other spent output's value: |
| 1946 | + { |
| 1947 | + std::vector<CTxOut> spent_outputs2(spent_outputs); |
| 1948 | + ++spent_outputs2[1].nValue; |
| 1949 | + CheckTemplateMatch(tx, std::move(spent_outputs2), 0, /*is_valid=*/true, test_cases, std::string{"Template hash matches with malleated value for other spent output."}); |
| 1950 | + } |
| 1951 | + // Other spent output's scriptpubkey: |
| 1952 | + { |
| 1953 | + std::vector<CTxOut> spent_outputs2(spent_outputs); |
| 1954 | + spent_outputs2[1].scriptPubKey = ScriptFromHex("0014266a4832c001885db26e853ef1d1dde840f7dbaf"); |
| 1955 | + CheckTemplateMatch(tx, std::move(spent_outputs2), 0, /*is_valid=*/true, test_cases, std::string{"Template hash matches with malleated scriptpubkey for other spent output."}); |
| 1956 | + } |
| 1957 | + |
| 1958 | + // Same transaction but different hash |
| 1959 | + { |
| 1960 | + CMutableTransaction tx2{tx}; |
| 1961 | + auto spent_outputs2{spent_outputs}; |
| 1962 | + |
| 1963 | + // Re-build the scriptpubkey with a leaf script containing the check against a dummy hash instead. |
| 1964 | + std::vector<uint8_t> dummy_hash(32); |
| 1965 | + const auto leaf_script2{CScript() << dummy_hash << OP_TEMPLATEHASH << OP_EQUAL}; |
| 1966 | + TaprootBuilder builder2; |
| 1967 | + builder2.Add(0, leaf_script2, TAPROOT_LEAF_TAPSCRIPT); |
| 1968 | + builder2.Finalize(XOnlyPubKey::NUMS_H); |
| 1969 | + spent_outputs2[0].scriptPubKey = GetScriptForDestination(builder2.GetOutput()); |
| 1970 | + |
| 1971 | + // Re-build the spending transaction's witness. |
| 1972 | + const auto spend_data{builder2.GetSpendData()}; |
| 1973 | + const auto control_blocks{spend_data.scripts.begin()->second}; |
| 1974 | + const auto& cb{*control_blocks.begin()}; |
| 1975 | + tx2.vin[0].scriptWitness.stack.clear(); |
| 1976 | + tx2.vin[0].scriptWitness.stack.emplace_back(leaf_script2.begin(), leaf_script2.end()); |
| 1977 | + tx2.vin[0].scriptWitness.stack.emplace_back(cb.begin(), cb.end()); |
| 1978 | + |
| 1979 | + // Verification must fail, the hash does not correspond to this transaction. |
| 1980 | + CheckTemplateMatch(tx2, std::move(spent_outputs2), 0, /*is_valid=*/false, test_cases, std::string{"Template hash mismatches: spending a script with a different committed hash."}); |
| 1981 | + } |
| 1982 | + |
| 1983 | + // We can also commit to a transaction which contains an annex. |
| 1984 | + { |
| 1985 | + CMutableTransaction tx2{tx}; |
| 1986 | + auto spent_outputs2{spent_outputs}; |
| 1987 | + |
| 1988 | + // Set an annex to the transaction. |
| 1989 | + std::vector<uint8_t> annex{ANNEX_TAG, 'd', 'a', 't', 'a'}; |
| 1990 | + tx2.vin[0].scriptWitness.stack.push_back(annex); |
| 1991 | + |
| 1992 | + // Sanity check this transaction isn't valid according to our previous commitment (but don't |
| 1993 | + // record it, as this error already was above). |
| 1994 | + std::vector<TemplateHashTestCase> dummy_cases; |
| 1995 | + CheckTemplateMatch(tx2, Clone(spent_outputs2), 0, /*is_valid=*/false, dummy_cases, std::string{}); |
| 1996 | + |
| 1997 | + // Re-build the scriptpubkey with a leaf script containing the template hash committing to the annex. |
| 1998 | + const uint256 hash_with_annex{GetTemplateHash(tx2, 0)}; |
| 1999 | + const auto leaf_script2{CScript() << hash_with_annex << OP_TEMPLATEHASH << OP_EQUAL}; |
| 2000 | + TaprootBuilder builder2; |
| 2001 | + builder2.Add(0, leaf_script2, TAPROOT_LEAF_TAPSCRIPT); |
| 2002 | + builder2.Finalize(XOnlyPubKey::NUMS_H); |
| 2003 | + spent_outputs2[0].scriptPubKey = GetScriptForDestination(builder2.GetOutput()); |
| 2004 | + |
| 2005 | + // Re-build the spending transaction's witness. |
| 2006 | + const auto spend_data{builder2.GetSpendData()}; |
| 2007 | + const auto control_blocks{spend_data.scripts.begin()->second}; |
| 2008 | + const auto& cb{*control_blocks.begin()}; |
| 2009 | + tx2.vin[0].scriptWitness.stack.clear(); |
| 2010 | + tx2.vin[0].scriptWitness.stack.emplace_back(leaf_script2.begin(), leaf_script2.end()); |
| 2011 | + tx2.vin[0].scriptWitness.stack.emplace_back(cb.begin(), cb.end()); |
| 2012 | + tx2.vin[0].scriptWitness.stack.emplace_back(annex.begin(), annex.end()); |
| 2013 | + |
| 2014 | + // Verification must pass if the commitment is for the transaction with this annex. |
| 2015 | + CheckTemplateMatch(tx2, Clone(spent_outputs2), 0, /*is_valid=*/true, test_cases, std::string{"Template hash matches in the presence of an annex."}); |
| 2016 | + |
| 2017 | + // But must fail for a transaction with another annex. |
| 2018 | + std::vector<uint8_t> other_annex{ANNEX_TAG, 'J', 'P', 'E', 'G'}; |
| 2019 | + tx2.vin[0].scriptWitness.stack.back() = other_annex; |
| 2020 | + CheckTemplateMatch(tx2, std::move(spent_outputs2), 0, /*is_valid=*/false, test_cases, std::string{"Template hash mismatches: spending with a different annex than that committed."}); |
| 2021 | + } |
| 2022 | + |
| 2023 | + // Optionally dump test vectors as JSON. Uncomment UPDATE_JSON_TESTS at the top of this file to use. |
| 2024 | +#ifdef UPDATE_JSON_TESTS |
| 2025 | + UniValue json_cases{UniValue::VARR}; |
| 2026 | + for (const auto& test_case: test_cases) { |
| 2027 | + json_cases.push_back(test_case.GetJson()); |
| 2028 | + } |
| 2029 | + const auto json_str{JSONPrettyPrint(json_cases)}; |
| 2030 | + FILE* file = fsbridge::fopen("templatehash_tests.json.gen", "w"); |
| 2031 | + fputs(json_str.c_str(), file); |
| 2032 | + fclose(file); |
| 2033 | +#endif |
| 2034 | +} |
| 2035 | + |
1750 | 2036 | BOOST_AUTO_TEST_SUITE_END() |
0 commit comments