Skip to content

Commit 5ccab71

Browse files
committed
Merge bitcoin/bitcoin#23394: Taproot wallet test vectors (generation+tests)
f1c33ee tests: implement BIP341 test vectors (Pieter Wuille) ac3037d tests: BIP341 test vector generation (Pieter Wuille) ca83ffc tests: add deterministic signing mode to ECDSA (Pieter Wuille) c98c53f tests: abstract out precomputed BIP341 signature hash elements (Pieter Wuille) a5bde01 tests: give feature_taproot access to sighash preimages (Pieter Wuille) 5140825 tests: add more fields to TaprootInfo (Pieter Wuille) 2478c67 Make signing follow BIP340 exactly w.r.t. aux randomness (Pieter Wuille) Pull request description: This PR adds code to `test/functional/feature_taproot.py` which runs through a (deterministic) scenario covering several aspects of the wallet side of BIP341 (scriptPubKey computation from keys/scripts, control block computation, key path spending), with the ability to output test vectors in mediawiki format based on this scenario. The generated tests are then also included directly in `src/test/script_tests.cpp` and `src/test/script_standard_tests.cpp`. I intend to add these test vectors to BIP341 itself: bitcoin/bips#1225 ACKs for top commit: laanwj: Code review ACK f1c33ee Tree-SHA512: fcf7109539cb214d3190516b205cd32d2b1b452f14aa66f4107acfaa8bfc7d368f626857f1935665a4342eabc0b9ee8aba608a7c0a2494bec0b498e723439c9d
2 parents 7f0f853 + f1c33ee commit 5ccab71

File tree

13 files changed

+943
-55
lines changed

13 files changed

+943
-55
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ FUZZ_BINARY=test/fuzz/fuzz$(EXEEXT)
1616

1717
JSON_TEST_FILES = \
1818
test/data/script_tests.json \
19+
test/data/bip341_wallet_vectors.json \
1920
test/data/base58_encode_decode.json \
2021
test/data/blockfilters.json \
2122
test/data/key_io_valid.json \

src/key.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ bool CKey::SignCompact(const uint256 &hash, std::vector<unsigned char>& vchSig)
275275
return true;
276276
}
277277

278-
bool CKey::SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint256* merkle_root, const uint256* aux) const
278+
bool CKey::SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint256* merkle_root, const uint256& aux) const
279279
{
280280
assert(sig.size() == 64);
281281
secp256k1_keypair keypair;
@@ -288,7 +288,7 @@ bool CKey::SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint2
288288
uint256 tweak = XOnlyPubKey(pubkey_bytes).ComputeTapTweakHash(merkle_root->IsNull() ? nullptr : merkle_root);
289289
if (!secp256k1_keypair_xonly_tweak_add(GetVerifyContext(), &keypair, tweak.data())) return false;
290290
}
291-
bool ret = secp256k1_schnorrsig_sign(secp256k1_context_sign, sig.data(), hash.data(), &keypair, aux ? (unsigned char*)aux->data() : nullptr);
291+
bool ret = secp256k1_schnorrsig_sign(secp256k1_context_sign, sig.data(), hash.data(), &keypair, (unsigned char*)aux.data());
292292
if (ret) {
293293
// Additional verification step to prevent using a potentially corrupted signature
294294
secp256k1_xonly_pubkey pubkey_verify;

src/key.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ class CKey
130130

131131
/**
132132
* Create a BIP-340 Schnorr signature, for the xonly-pubkey corresponding to *this,
133-
* optionally tweaked by *merkle_root. Additional nonce entropy can be provided through
133+
* optionally tweaked by *merkle_root. Additional nonce entropy is provided through
134134
* aux.
135135
*
136136
* merkle_root is used to optionally perform tweaking of the private key, as specified
@@ -143,7 +143,7 @@ class CKey
143143
* (this is used for key path spending, with specific
144144
* Merkle root of the script tree).
145145
*/
146-
bool SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint256* merkle_root = nullptr, const uint256* aux = nullptr) const;
146+
bool SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint256* merkle_root, const uint256& aux) const;
147147

148148
//! Derive BIP32 child key.
149149
bool Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;

src/script/interpreter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1483,7 +1483,7 @@ template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo,
14831483
template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo);
14841484
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
14851485

1486-
static const CHashWriter HASHER_TAPSIGHASH = TaggedHash("TapSighash");
1486+
const CHashWriter HASHER_TAPSIGHASH = TaggedHash("TapSighash");
14871487
const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf");
14881488
const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch");
14891489

src/script/interpreter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ static constexpr size_t TAPROOT_CONTROL_NODE_SIZE = 32;
229229
static constexpr size_t TAPROOT_CONTROL_MAX_NODE_COUNT = 128;
230230
static constexpr size_t TAPROOT_CONTROL_MAX_SIZE = TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT;
231231

232+
extern const CHashWriter HASHER_TAPSIGHASH; //!< Hasher with tag "TapSighash" pre-fed to it.
232233
extern const CHashWriter HASHER_TAPLEAF; //!< Hasher with tag "TapLeaf" pre-fed to it.
233234
extern const CHashWriter HASHER_TAPBRANCH; //!< Hasher with tag "TapBranch" pre-fed to it.
234235

src/script/sign.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider&
8181
uint256 hash;
8282
if (!SignatureHashSchnorr(hash, execdata, *txTo, nIn, nHashType, sigversion, *m_txdata, MissingDataBehavior::FAIL)) return false;
8383
sig.resize(64);
84-
if (!key.SignSchnorr(hash, sig, merkle_root, nullptr)) return false;
84+
// Use uint256{} as aux_rnd for now.
85+
if (!key.SignSchnorr(hash, sig, merkle_root, {})) return false;
8586
if (nHashType) sig.push_back(nHashType);
8687
return true;
8788
}

src/test/data/bip341_wallet_vectors.json

Lines changed: 452 additions & 0 deletions
Large diffs are not rendered by default.

src/test/key_tests.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ BOOST_AUTO_TEST_CASE(bip340_test_vectors)
321321
key.Set(sec.begin(), sec.end(), true);
322322
XOnlyPubKey pubkey(key.GetPubKey());
323323
BOOST_CHECK(std::equal(pubkey.begin(), pubkey.end(), pub.begin(), pub.end()));
324-
bool ok = key.SignSchnorr(msg256, sig64, nullptr, &aux256);
324+
bool ok = key.SignSchnorr(msg256, sig64, nullptr, aux256);
325325
BOOST_CHECK(ok);
326326
BOOST_CHECK(std::vector<unsigned char>(sig64, sig64 + 64) == sig);
327327
// Verify those signatures for good measure.
@@ -337,7 +337,7 @@ BOOST_AUTO_TEST_CASE(bip340_test_vectors)
337337
BOOST_CHECK(tweaked);
338338
XOnlyPubKey tweaked_key = tweaked->first;
339339
aux256 = InsecureRand256();
340-
bool ok = key.SignSchnorr(msg256, sig64, &merkle_root, &aux256);
340+
bool ok = key.SignSchnorr(msg256, sig64, &merkle_root, aux256);
341341
BOOST_CHECK(ok);
342342
BOOST_CHECK(tweaked_key.VerifySchnorr(msg256, sig64));
343343
}

src/test/script_standard_tests.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

5+
#include <test/data/bip341_wallet_vectors.json.h>
6+
57
#include <key.h>
68
#include <key_io.h>
79
#include <script/script.h>
@@ -12,6 +14,8 @@
1214

1315
#include <boost/test/unit_test.hpp>
1416

17+
#include <univalue.h>
18+
1519

1620
BOOST_FIXTURE_TEST_SUITE(script_standard_tests, BasicTestingSetup)
1721

@@ -385,4 +389,46 @@ BOOST_AUTO_TEST_CASE(script_standard_taproot_builder)
385389
BOOST_CHECK_EQUAL(EncodeDestination(builder.GetOutput()), "bc1pj6gaw944fy0xpmzzu45ugqde4rz7mqj5kj0tg8kmr5f0pjq8vnaqgynnge");
386390
}
387391

392+
BOOST_AUTO_TEST_CASE(bip341_spk_test_vectors)
393+
{
394+
using control_set = decltype(TaprootSpendData::scripts)::mapped_type;
395+
396+
UniValue tests;
397+
tests.read((const char*)json_tests::bip341_wallet_vectors, sizeof(json_tests::bip341_wallet_vectors));
398+
399+
const auto& vectors = tests["scriptPubKey"];
400+
401+
for (const auto& vec : vectors.getValues()) {
402+
TaprootBuilder spktest;
403+
std::map<std::pair<CScript, int>, int> scriptposes;
404+
std::function<void (const UniValue&, int)> parse_tree = [&](const UniValue& node, int depth) {
405+
if (node.isNull()) return;
406+
if (node.isObject()) {
407+
auto script_bytes = ParseHex(node["script"].get_str());
408+
CScript script(script_bytes.begin(), script_bytes.end());
409+
int idx = node["id"].get_int();
410+
int leaf_version = node["leafVersion"].get_int();
411+
scriptposes[{script, leaf_version}] = idx;
412+
spktest.Add(depth, script, leaf_version);
413+
} else {
414+
parse_tree(node[0], depth + 1);
415+
parse_tree(node[1], depth + 1);
416+
}
417+
};
418+
parse_tree(vec["given"]["scriptTree"], 0);
419+
spktest.Finalize(XOnlyPubKey(ParseHex(vec["given"]["internalPubkey"].get_str())));
420+
BOOST_CHECK_EQUAL(HexStr(GetScriptForDestination(spktest.GetOutput())), vec["expected"]["scriptPubKey"].get_str());
421+
BOOST_CHECK_EQUAL(EncodeDestination(spktest.GetOutput()), vec["expected"]["bip350Address"].get_str());
422+
auto spend_data = spktest.GetSpendData();
423+
BOOST_CHECK_EQUAL(vec["intermediary"]["merkleRoot"].isNull(), spend_data.merkle_root.IsNull());
424+
if (!spend_data.merkle_root.IsNull()) {
425+
BOOST_CHECK_EQUAL(vec["intermediary"]["merkleRoot"].get_str(), HexStr(spend_data.merkle_root));
426+
}
427+
BOOST_CHECK_EQUAL(spend_data.scripts.size(), scriptposes.size());
428+
for (const auto& scriptpos : scriptposes) {
429+
BOOST_CHECK(spend_data.scripts[scriptpos.first] == control_set{ParseHex(vec["expected"]["scriptPathControlBlocks"][scriptpos.second].get_str())});
430+
}
431+
}
432+
}
433+
388434
BOOST_AUTO_TEST_SUITE_END()

src/test/script_tests.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include <test/data/script_tests.json.h>
6+
#include <test/data/bip341_wallet_vectors.json.h>
67

78
#include <core_io.h>
89
#include <fs.h>
@@ -1743,4 +1744,79 @@ BOOST_AUTO_TEST_CASE(script_assets_test)
17431744
file.close();
17441745
}
17451746

1747+
BOOST_AUTO_TEST_CASE(bip341_keypath_test_vectors)
1748+
{
1749+
UniValue tests;
1750+
tests.read((const char*)json_tests::bip341_wallet_vectors, sizeof(json_tests::bip341_wallet_vectors));
1751+
1752+
const auto& vectors = tests["keyPathSpending"];
1753+
1754+
for (const auto& vec : vectors.getValues()) {
1755+
auto txhex = ParseHex(vec["given"]["rawUnsignedTx"].get_str());
1756+
CMutableTransaction tx;
1757+
VectorReader(SER_NETWORK, PROTOCOL_VERSION, txhex, 0) >> tx;
1758+
std::vector<CTxOut> utxos;
1759+
for (const auto& utxo_spent : vec["given"]["utxosSpent"].getValues()) {
1760+
auto script_bytes = ParseHex(utxo_spent["scriptPubKey"].get_str());
1761+
CScript script{script_bytes.begin(), script_bytes.end()};
1762+
CAmount amount{utxo_spent["amountSats"].get_int()};
1763+
utxos.emplace_back(amount, script);
1764+
}
1765+
1766+
PrecomputedTransactionData txdata;
1767+
txdata.Init(tx, std::vector<CTxOut>{utxos}, true);
1768+
1769+
BOOST_CHECK(txdata.m_bip341_taproot_ready);
1770+
BOOST_CHECK_EQUAL(HexStr(txdata.m_spent_amounts_single_hash), vec["intermediary"]["hashAmounts"].get_str());
1771+
BOOST_CHECK_EQUAL(HexStr(txdata.m_outputs_single_hash), vec["intermediary"]["hashOutputs"].get_str());
1772+
BOOST_CHECK_EQUAL(HexStr(txdata.m_prevouts_single_hash), vec["intermediary"]["hashPrevouts"].get_str());
1773+
BOOST_CHECK_EQUAL(HexStr(txdata.m_spent_scripts_single_hash), vec["intermediary"]["hashScriptPubkeys"].get_str());
1774+
BOOST_CHECK_EQUAL(HexStr(txdata.m_sequences_single_hash), vec["intermediary"]["hashSequences"].get_str());
1775+
1776+
for (const auto& input : vec["inputSpending"].getValues()) {
1777+
int txinpos = input["given"]["txinIndex"].get_int();
1778+
int hashtype = input["given"]["hashType"].get_int();
1779+
1780+
// Load key.
1781+
auto privkey = ParseHex(input["given"]["internalPrivkey"].get_str());
1782+
CKey key;
1783+
key.Set(privkey.begin(), privkey.end(), true);
1784+
1785+
// Load Merkle root.
1786+
uint256 merkle_root;
1787+
if (!input["given"]["merkleRoot"].isNull()) {
1788+
merkle_root = uint256{ParseHex(input["given"]["merkleRoot"].get_str())};
1789+
}
1790+
1791+
// Compute and verify (internal) public key.
1792+
XOnlyPubKey pubkey{key.GetPubKey()};
1793+
BOOST_CHECK_EQUAL(HexStr(pubkey), input["intermediary"]["internalPubkey"].get_str());
1794+
1795+
// Sign and verify signature.
1796+
FlatSigningProvider provider;
1797+
provider.keys[key.GetPubKey().GetID()] = key;
1798+
MutableTransactionSignatureCreator creator(&tx, txinpos, utxos[txinpos].nValue, &txdata, hashtype);
1799+
std::vector<unsigned char> signature;
1800+
BOOST_CHECK(creator.CreateSchnorrSig(provider, signature, pubkey, nullptr, &merkle_root, SigVersion::TAPROOT));
1801+
BOOST_CHECK_EQUAL(HexStr(signature), input["expected"]["witness"][0].get_str());
1802+
1803+
// We can't observe the tweak used inside the signing logic, so verify by recomputing it.
1804+
BOOST_CHECK_EQUAL(HexStr(pubkey.ComputeTapTweakHash(merkle_root.IsNull() ? nullptr : &merkle_root)), input["intermediary"]["tweak"].get_str());
1805+
1806+
// We can't observe the sighash used inside the signing logic, so verify by recomputing it.
1807+
ScriptExecutionData sed;
1808+
sed.m_annex_init = true;
1809+
sed.m_annex_present = false;
1810+
uint256 sighash;
1811+
BOOST_CHECK(SignatureHashSchnorr(sighash, sed, tx, txinpos, hashtype, SigVersion::TAPROOT, txdata, MissingDataBehavior::FAIL));
1812+
BOOST_CHECK_EQUAL(HexStr(sighash), input["intermediary"]["sigHash"].get_str());
1813+
1814+
// To verify the sigmsg, hash the expected sigmsg, and compare it with the (expected) sighash.
1815+
BOOST_CHECK_EQUAL(HexStr((CHashWriter(HASHER_TAPSIGHASH) << MakeSpan(ParseHex(input["intermediary"]["sigMsg"].get_str()))).GetSHA256()), input["intermediary"]["sigHash"].get_str());
1816+
}
1817+
1818+
}
1819+
1820+
}
1821+
17461822
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)