diff --git a/src/Makefile.test.include b/src/Makefile.test.include --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -92,6 +92,7 @@ test/scriptflags.h \ test/scriptnum_tests.cpp \ test/serialize_tests.cpp \ + test/sigcache_tests.cpp \ test/sigencoding_tests.cpp \ test/sighash_tests.cpp \ test/sighashtype_tests.cpp \ diff --git a/src/script/interpreter.h b/src/script/interpreter.h --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -32,6 +32,9 @@ virtual bool VerifySignatureECDSA(const std::vector &vchSig, const CPubKey &vchPubKey, const uint256 &sighash) const; + virtual bool VerifySignatureSchnorr(const std::vector &vchSig, + const CPubKey &vchPubKey, + const uint256 &sighash) const; virtual bool CheckSig(const std::vector &scriptSig, const std::vector &vchPubKey, diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1468,6 +1468,12 @@ return pubkey.VerifyECDSA(sighash, vchSig); } +bool BaseSignatureChecker::VerifySignatureSchnorr( + const std::vector &vchSig, const CPubKey &pubkey, + const uint256 &sighash) const { + return pubkey.VerifySchnorr(sighash, vchSig); +} + bool TransactionSignatureChecker::CheckSig( const std::vector &vchSigIn, const std::vector &vchPubKey, const CScript &scriptCode, uint32_t flags) const { diff --git a/src/script/sigcache.h b/src/script/sigcache.h --- a/src/script/sigcache.h +++ b/src/script/sigcache.h @@ -54,6 +54,9 @@ bool VerifySignatureECDSA(const std::vector &vchSig, const CPubKey &vchPubKey, const uint256 &sighash) const override; + bool VerifySignatureSchnorr(const std::vector &vchSig, + const CPubKey &vchPubKey, + const uint256 &sighash) const override; }; void InitSignatureCache(); diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp --- a/src/script/sigcache.cpp +++ b/src/script/sigcache.cpp @@ -17,26 +17,46 @@ namespace { /** - * Valid signature cache, to avoid doing expensive ECDSA signature checking + * Valid signature cache, to avoid doing expensive signature checking * twice for every transaction (once when accepted into memory pool, and - * again when accepted into the block chain) + * again when accepted into the block chain). + * + * We use a different nonce for ECDSA and Schnorr so that a valid 64-byte ECDSA + * signature cannot be maliciously reused as a Schnorr signature, and a valid + * Schnorr signature cannot be maliciously reused as a 64-byte ECDSA signature. */ class CSignatureCache { private: //! Entries are SHA256(nonce || signature hash || public key || signature): - uint256 nonce; + uint256 nonceECDSA; + uint256 nonceSchnorr; typedef CuckooCache::cache map_type; map_type setValid; boost::shared_mutex cs_sigcache; public: - CSignatureCache() { GetRandBytes(nonce.begin(), 32); } + CSignatureCache() { + GetRandBytes(nonceECDSA.begin(), 32); + GetRandBytes(nonceSchnorr.begin(), 32); + assert(nonceSchnorr != nonceECDSA); + } - void ComputeEntry(uint256 &entry, const uint256 &hash, - const std::vector &vchSig, - const CPubKey &pubkey) { + void ComputeEntryECDSA(uint256 &entry, const uint256 &hash, + const std::vector &vchSig, + const CPubKey &pubkey) { CSHA256() - .Write(nonce.begin(), 32) + .Write(nonceECDSA.begin(), 32) + .Write(hash.begin(), 32) + .Write(&pubkey[0], pubkey.size()) + .Write(&vchSig[0], vchSig.size()) + .Finalize(entry.begin()); + } + + void ComputeEntrySchnorr(uint256 &entry, const uint256 &hash, + const std::vector &vchSig, + const CPubKey &pubkey) { + CSHA256() + .Write(nonceSchnorr.begin(), 32) .Write(hash.begin(), 32) .Write(&pubkey[0], pubkey.size()) .Write(&vchSig[0], vchSig.size()) @@ -84,7 +104,7 @@ const std::vector &vchSig, const CPubKey &pubkey, const uint256 &sighash) const { uint256 entry; - signatureCache.ComputeEntry(entry, sighash, vchSig, pubkey); + signatureCache.ComputeEntryECDSA(entry, sighash, vchSig, pubkey); if (signatureCache.Get(entry, !store)) { return true; } @@ -97,3 +117,21 @@ } return true; } + +bool CachingTransactionSignatureChecker::VerifySignatureSchnorr( + const std::vector &vchSig, const CPubKey &pubkey, + const uint256 &sighash) const { + uint256 entry; + signatureCache.ComputeEntrySchnorr(entry, sighash, vchSig, pubkey); + if (signatureCache.Get(entry, !store)) { + return true; + } + if (!TransactionSignatureChecker::VerifySignatureSchnorr(vchSig, pubkey, + sighash)) { + return false; + } + if (store) { + signatureCache.Set(entry); + } + return true; +} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -108,6 +108,7 @@ scriptflags.cpp scriptnum_tests.cpp serialize_tests.cpp + sigcache_tests.cpp sigencoding_tests.cpp sighash_tests.cpp sighashtype_tests.cpp diff --git a/src/test/sigcache_tests.cpp b/src/test/sigcache_tests.cpp new file mode 100644 --- /dev/null +++ b/src/test/sigcache_tests.cpp @@ -0,0 +1,75 @@ +// Copyright (c) 2012-2015 The Bitcoin Core developers +// Copyright (c) 2019- The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +// (based on key_tests.cpp) + +#include "key.h" + +#include "base58.h" +#include "dstencode.h" +#include "script/interpreter.h" +#include "script/script.h" +#include "script/sigcache.h" +#include "test/test_bitcoin.h" +#include "uint256.h" +#include "util.h" +#include "utilstrencodings.h" + +#include +#include + +#include + +static const std::string strSecret1 = + "5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"; + +BOOST_FIXTURE_TEST_SUITE(sigcache_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(sigcache_test1) { + // Create a dummy transaction since the sigcache is only accessible + // via CachingTransactionSignatureChecker, which requires a tx. + CDataStream stream( + ParseHex( + "010000000122739e70fbee987a8be1788395a2f2e6ad18ccb7ff611cd798071539" + "dde3c38e000000000151ffffffff010000000000000000016a00000000"), + SER_NETWORK, PROTOCOL_VERSION); + CTransaction dummyTx(deserialize, stream); + PrecomputedTransactionData txdata(dummyTx); + + // We access sigcache through CachingTransactionSignatureChecker + CachingTransactionSignatureChecker checker(&dummyTx, 0, 0 * SATOSHI, true, + txdata); + + CBitcoinSecret bsecret1; + BOOST_CHECK(bsecret1.SetString(strSecret1)); + + CKey key1 = bsecret1.GetKey(); + BOOST_CHECK(key1.IsCompressed() == false); + + CPubKey pubkey1 = key1.GetPubKey(); + + BOOST_CHECK(key1.VerifyPubKey(pubkey1)); + + for (int n = 0; n < 16; n++) { + std::string strMsg = strprintf("Sigcache test %i: 11", n); + uint256 hashMsg = Hash(strMsg.begin(), strMsg.end()); + + // Make Schnorr signature (64 bytes) + std::vector sigSchnorr; + BOOST_CHECK(key1.SignSchnorr(hashMsg, sigSchnorr)); + + // direct testing + BOOST_CHECK(pubkey1.VerifySchnorr(hashMsg, sigSchnorr)); + BOOST_CHECK(!pubkey1.VerifyECDSA(hashMsg, sigSchnorr)); + + // put Schnorr signature into cache + BOOST_CHECK( + checker.VerifySignatureSchnorr(sigSchnorr, pubkey1, hashMsg)); + // make sure ECDSA signature doesn't verify + BOOST_CHECK( + !checker.VerifySignatureECDSA(sigSchnorr, pubkey1, hashMsg)); + } +} + +BOOST_AUTO_TEST_SUITE_END()