aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build_msvc/libsecp256k1/libsecp256k1.vcxproj2
-rw-r--r--configure.ac2
-rw-r--r--src/pubkey.cpp15
-rw-r--r--src/pubkey.h20
-rw-r--r--src/script/interpreter.cpp30
-rw-r--r--src/script/interpreter.h9
-rw-r--r--src/script/script_error.cpp6
-rw-r--r--src/script/script_error.h5
-rw-r--r--src/script/sigcache.cpp35
-rw-r--r--src/script/sigcache.h2
-rw-r--r--src/script/sign.h1
-rw-r--r--src/test/fuzz/script_sigcache.cpp18
-rw-r--r--src/test/fuzz/signature_checker.cpp5
-rw-r--r--src/test/key_tests.cpp28
14 files changed, 165 insertions, 13 deletions
diff --git a/build_msvc/libsecp256k1/libsecp256k1.vcxproj b/build_msvc/libsecp256k1/libsecp256k1.vcxproj
index 99fb63fb02..c42918d6e1 100644
--- a/build_msvc/libsecp256k1/libsecp256k1.vcxproj
+++ b/build_msvc/libsecp256k1/libsecp256k1.vcxproj
@@ -12,7 +12,7 @@
</ItemGroup>
<ItemDefinitionGroup>
<ClCompile>
- <PreprocessorDefinitions>ENABLE_MODULE_ECDH;ENABLE_MODULE_RECOVERY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>ENABLE_MODULE_ECDH;ENABLE_MODULE_RECOVERY;ENABLE_MODULE_EXTRAKEYS;ENABLE_MODULE_SCHNORRSIG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\src\secp256k1;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
diff --git a/configure.ac b/configure.ac
index fbf56443f1..3df59e0b7b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1645,7 +1645,7 @@ if test x$need_bundled_univalue = xyes; then
AC_CONFIG_SUBDIRS([src/univalue])
fi
-ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --with-bignum=no --enable-module-recovery"
+ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --with-bignum=no --enable-module-recovery --enable-module-schnorrsig --enable-experimental"
AC_CONFIG_SUBDIRS([src/secp256k1])
AC_OUTPUT
diff --git a/src/pubkey.cpp b/src/pubkey.cpp
index fc14f41a0c..69e3d91392 100644
--- a/src/pubkey.cpp
+++ b/src/pubkey.cpp
@@ -7,6 +7,7 @@
#include <secp256k1.h>
#include <secp256k1_recovery.h>
+#include <secp256k1_schnorrsig.h>
namespace
{
@@ -166,6 +167,20 @@ int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1_ecdsa_
return 1;
}
+XOnlyPubKey::XOnlyPubKey(Span<const unsigned char> bytes)
+{
+ assert(bytes.size() == 32);
+ std::copy(bytes.begin(), bytes.end(), m_keydata.begin());
+}
+
+bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const
+{
+ assert(sigbytes.size() == 64);
+ secp256k1_xonly_pubkey pubkey;
+ if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &pubkey, m_keydata.data())) return false;
+ return secp256k1_schnorrsig_verify(secp256k1_context_verify, sigbytes.data(), msg.begin(), &pubkey);
+}
+
bool CPubKey::Verify(const uint256 &hash, const std::vector<unsigned char>& vchSig) const {
if (!IsValid())
return false;
diff --git a/src/pubkey.h b/src/pubkey.h
index cd1049f66f..1a818037d1 100644
--- a/src/pubkey.h
+++ b/src/pubkey.h
@@ -9,6 +9,7 @@
#include <hash.h>
#include <serialize.h>
+#include <span.h>
#include <uint256.h>
#include <stdexcept>
@@ -206,6 +207,25 @@ public:
bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;
};
+class XOnlyPubKey
+{
+private:
+ uint256 m_keydata;
+
+public:
+ /** Construct an x-only pubkey from exactly 32 bytes. */
+ XOnlyPubKey(Span<const unsigned char> bytes);
+
+ /** Verify a Schnorr signature against this public key.
+ *
+ * sigbytes must be exactly 64 bytes.
+ */
+ bool VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const;
+
+ const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); }
+ size_t size() const { return m_keydata.size(); }
+};
+
struct CExtPubKey {
unsigned char nDepth;
unsigned char vchFingerprint[4];
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
index af8fd4912e..aef6e28118 100644
--- a/src/script/interpreter.cpp
+++ b/src/script/interpreter.cpp
@@ -1522,6 +1522,12 @@ bool GenericTransactionSignatureChecker<T>::VerifyECDSASignature(const std::vect
}
template <class T>
+bool GenericTransactionSignatureChecker<T>::VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const
+{
+ return pubkey.VerifySchnorr(sighash, sig);
+}
+
+template <class T>
bool GenericTransactionSignatureChecker<T>::CheckECDSASignature(const std::vector<unsigned char>& vchSigIn, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
{
CPubKey pubkey(vchPubKey);
@@ -1544,6 +1550,30 @@ bool GenericTransactionSignatureChecker<T>::CheckECDSASignature(const std::vecto
}
template <class T>
+bool GenericTransactionSignatureChecker<T>::CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey_in, SigVersion sigversion, ScriptError* serror) const
+{
+ assert(sigversion == SigVersion::TAPROOT);
+ // Schnorr signatures have 32-byte public keys. The caller is responsible for enforcing this.
+ assert(pubkey_in.size() == 32);
+ if (sig.size() != 64 && sig.size() != 65) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_SIZE);
+
+ XOnlyPubKey pubkey{pubkey_in};
+
+ uint8_t hashtype = SIGHASH_DEFAULT;
+ if (sig.size() == 65) {
+ hashtype = SpanPopBack(sig);
+ if (hashtype == SIGHASH_DEFAULT) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE);
+ }
+ uint256 sighash;
+ assert(this->txdata);
+ if (!SignatureHashSchnorr(sighash, *txTo, nIn, hashtype, sigversion, *this->txdata)) {
+ return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE);
+ }
+ if (!VerifySchnorrSignature(sig, pubkey, sighash)) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG);
+ return true;
+}
+
+template <class T>
bool GenericTransactionSignatureChecker<T>::CheckLockTime(const CScriptNum& nLockTime) const
{
// There are two kinds of nLockTime: lock-by-blockheight
diff --git a/src/script/interpreter.h b/src/script/interpreter.h
index b739528f0f..e54243c8f0 100644
--- a/src/script/interpreter.h
+++ b/src/script/interpreter.h
@@ -7,12 +7,14 @@
#define BITCOIN_SCRIPT_INTERPRETER_H
#include <script/script_error.h>
+#include <span.h>
#include <primitives/transaction.h>
#include <vector>
#include <stdint.h>
class CPubKey;
+class XOnlyPubKey;
class CScript;
class CTransaction;
class CTxOut;
@@ -176,6 +178,11 @@ public:
return false;
}
+ virtual bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptError* serror = nullptr) const
+ {
+ return false;
+ }
+
virtual bool CheckLockTime(const CScriptNum& nLockTime) const
{
return false;
@@ -200,11 +207,13 @@ private:
protected:
virtual bool VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
+ virtual bool VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const;
public:
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {}
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {}
bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override;
+ bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptError* serror = nullptr) const override;
bool CheckLockTime(const CScriptNum& nLockTime) const override;
bool CheckSequence(const CScriptNum& nSequence) const override;
};
diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp
index 3b383393f9..9c49ced3ed 100644
--- a/src/script/script_error.cpp
+++ b/src/script/script_error.cpp
@@ -91,6 +91,12 @@ std::string ScriptErrorString(const ScriptError serror)
return "Witness provided for non-witness script";
case SCRIPT_ERR_WITNESS_PUBKEYTYPE:
return "Using non-compressed keys in segwit";
+ case SCRIPT_ERR_SCHNORR_SIG_SIZE:
+ return "Invalid Schnorr signature size";
+ case SCRIPT_ERR_SCHNORR_SIG_HASHTYPE:
+ return "Invalid Schnorr signature hash type";
+ case SCRIPT_ERR_SCHNORR_SIG:
+ return "Invalid Schnorr signature";
case SCRIPT_ERR_OP_CODESEPARATOR:
return "Using OP_CODESEPARATOR in non-witness script";
case SCRIPT_ERR_SIG_FINDANDDELETE:
diff --git a/src/script/script_error.h b/src/script/script_error.h
index 2978c147e1..c79a11a94b 100644
--- a/src/script/script_error.h
+++ b/src/script/script_error.h
@@ -66,6 +66,11 @@ typedef enum ScriptError_t
SCRIPT_ERR_WITNESS_UNEXPECTED,
SCRIPT_ERR_WITNESS_PUBKEYTYPE,
+ /* Taproot */
+ SCRIPT_ERR_SCHNORR_SIG_SIZE,
+ SCRIPT_ERR_SCHNORR_SIG_HASHTYPE,
+ SCRIPT_ERR_SCHNORR_SIG,
+
/* Constant scriptCode */
SCRIPT_ERR_OP_CODESEPARATOR,
SCRIPT_ERR_SIG_FINDANDDELETE,
diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp
index b9ba006e47..4a6e04f2eb 100644
--- a/src/script/sigcache.cpp
+++ b/src/script/sigcache.cpp
@@ -22,8 +22,9 @@ namespace {
class CSignatureCache
{
private:
- //! Entries are SHA256(nonce || signature hash || public key || signature):
- CSHA256 m_salted_hasher;
+ //! Entries are SHA256(nonce || 'E' or 'S' || 31 zero bytes || signature hash || public key || signature):
+ CSHA256 m_salted_hasher_ecdsa;
+ CSHA256 m_salted_hasher_schnorr;
typedef CuckooCache::cache<uint256, SignatureCacheHasher> map_type;
map_type setValid;
boost::shared_mutex cs_sigcache;
@@ -34,18 +35,30 @@ public:
uint256 nonce = GetRandHash();
// We want the nonce to be 64 bytes long to force the hasher to process
// this chunk, which makes later hash computations more efficient. We
- // just write our 32-byte entropy twice to fill the 64 bytes.
- m_salted_hasher.Write(nonce.begin(), 32);
- m_salted_hasher.Write(nonce.begin(), 32);
+ // just write our 32-byte entropy, and then pad with 'E' for ECDSA and
+ // 'S' for Schnorr (followed by 0 bytes).
+ static constexpr unsigned char PADDING_ECDSA[32] = {'E'};
+ static constexpr unsigned char PADDING_SCHNORR[32] = {'S'};
+ m_salted_hasher_ecdsa.Write(nonce.begin(), 32);
+ m_salted_hasher_ecdsa.Write(PADDING_ECDSA, 32);
+ m_salted_hasher_schnorr.Write(nonce.begin(), 32);
+ m_salted_hasher_schnorr.Write(PADDING_SCHNORR, 32);
}
void
ComputeEntryECDSA(uint256& entry, const uint256 &hash, const std::vector<unsigned char>& vchSig, const CPubKey& pubkey)
{
- CSHA256 hasher = m_salted_hasher;
+ CSHA256 hasher = m_salted_hasher_ecdsa;
hasher.Write(hash.begin(), 32).Write(&pubkey[0], pubkey.size()).Write(&vchSig[0], vchSig.size()).Finalize(entry.begin());
}
+ void
+ ComputeEntrySchnorr(uint256& entry, const uint256 &hash, Span<const unsigned char> sig, const XOnlyPubKey& pubkey)
+ {
+ CSHA256 hasher = m_salted_hasher_schnorr;
+ hasher.Write(hash.begin(), 32).Write(&pubkey[0], pubkey.size()).Write(sig.data(), sig.size()).Finalize(entry.begin());
+ }
+
bool
Get(const uint256& entry, const bool erase)
{
@@ -97,3 +110,13 @@ bool CachingTransactionSignatureChecker::VerifyECDSASignature(const std::vector<
signatureCache.Set(entry);
return true;
}
+
+bool CachingTransactionSignatureChecker::VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const
+{
+ uint256 entry;
+ signatureCache.ComputeEntrySchnorr(entry, sighash, sig, pubkey);
+ if (signatureCache.Get(entry, !store)) return true;
+ if (!TransactionSignatureChecker::VerifySchnorrSignature(sig, pubkey, sighash)) return false;
+ if (store) signatureCache.Set(entry);
+ return true;
+}
diff --git a/src/script/sigcache.h b/src/script/sigcache.h
index 49c6b192e9..00534f9758 100644
--- a/src/script/sigcache.h
+++ b/src/script/sigcache.h
@@ -7,6 +7,7 @@
#define BITCOIN_SCRIPT_SIGCACHE_H
#include <script/interpreter.h>
+#include <span.h>
#include <vector>
@@ -49,6 +50,7 @@ public:
CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn), store(storeIn) {}
bool VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const override;
+ bool VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const override;
};
void InitSignatureCache();
diff --git a/src/script/sign.h b/src/script/sign.h
index b77d26c0d7..a1cfe1574d 100644
--- a/src/script/sign.h
+++ b/src/script/sign.h
@@ -11,6 +11,7 @@
#include <pubkey.h>
#include <script/interpreter.h>
#include <script/keyorigin.h>
+#include <span.h>
#include <streams.h>
class CKey;
diff --git a/src/test/fuzz/script_sigcache.cpp b/src/test/fuzz/script_sigcache.cpp
index 0de1617d57..87af71897b 100644
--- a/src/test/fuzz/script_sigcache.cpp
+++ b/src/test/fuzz/script_sigcache.cpp
@@ -35,11 +35,19 @@ void test_one_input(const std::vector<uint8_t>& buffer)
const bool store = fuzzed_data_provider.ConsumeBool();
PrecomputedTransactionData tx_data;
CachingTransactionSignatureChecker caching_transaction_signature_checker{mutable_transaction ? &tx : nullptr, n_in, amount, store, tx_data};
- const std::optional<CPubKey> pub_key = ConsumeDeserializable<CPubKey>(fuzzed_data_provider);
- if (pub_key) {
- const std::vector<uint8_t> random_bytes = ConsumeRandomLengthByteVector(fuzzed_data_provider);
- if (!random_bytes.empty()) {
- (void)caching_transaction_signature_checker.VerifyECDSASignature(random_bytes, *pub_key, ConsumeUInt256(fuzzed_data_provider));
+ if (fuzzed_data_provider.ConsumeBool()) {
+ const auto random_bytes = fuzzed_data_provider.ConsumeBytes<unsigned char>(64);
+ const XOnlyPubKey pub_key(ConsumeUInt256(fuzzed_data_provider));
+ if (random_bytes.size() == 64) {
+ (void)caching_transaction_signature_checker.VerifySchnorrSignature(random_bytes, pub_key, ConsumeUInt256(fuzzed_data_provider));
+ }
+ } else {
+ const auto random_bytes = ConsumeRandomLengthByteVector(fuzzed_data_provider);
+ const auto pub_key = ConsumeDeserializable<CPubKey>(fuzzed_data_provider);
+ if (pub_key) {
+ if (!random_bytes.empty()) {
+ (void)caching_transaction_signature_checker.VerifyECDSASignature(random_bytes, *pub_key, ConsumeUInt256(fuzzed_data_provider));
+ }
}
}
}
diff --git a/src/test/fuzz/signature_checker.cpp b/src/test/fuzz/signature_checker.cpp
index fb54476095..f538c9272e 100644
--- a/src/test/fuzz/signature_checker.cpp
+++ b/src/test/fuzz/signature_checker.cpp
@@ -33,6 +33,11 @@ public:
return m_fuzzed_data_provider.ConsumeBool();
}
+ bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptError* serror = nullptr) const override
+ {
+ return m_fuzzed_data_provider.ConsumeBool();
+ }
+
bool CheckLockTime(const CScriptNum& nLockTime) const override
{
return m_fuzzed_data_provider.ConsumeBool();
diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp
index 4e4c44266a..3362b8d17c 100644
--- a/src/test/key_tests.cpp
+++ b/src/test/key_tests.cpp
@@ -264,4 +264,32 @@ BOOST_AUTO_TEST_CASE(pubkey_unserialize)
}
}
+BOOST_AUTO_TEST_CASE(bip340_test_vectors)
+{
+ static const std::vector<std::pair<std::array<std::string, 3>, bool>> VECTORS = {
+ {{"F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", "0000000000000000000000000000000000000000000000000000000000000000", "E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0"}, true},
+ {{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A"}, true},
+ {{"DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", "7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C", "5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7"}, true},
+ {{"25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3"}, true},
+ {{"D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9", "4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703", "00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4"}, true},
+ {{"EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B"}, false},
+ {{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2"}, false},
+ {{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD"}, false},
+ {{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6"}, false},
+ {{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051"}, false},
+ {{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197"}, false},
+ {{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B"}, false},
+ {{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B"}, false},
+ {{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"}, false},
+ {{"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B"}, false}
+ };
+
+ for (const auto& test : VECTORS) {
+ auto pubkey = ParseHex(test.first[0]);
+ auto msg = ParseHex(test.first[1]);
+ auto sig = ParseHex(test.first[2]);
+ BOOST_CHECK_EQUAL(XOnlyPubKey(pubkey).VerifySchnorr(uint256(msg), sig), test.second);
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()