diff --git a/src/core_write.cpp b/src/core_write.cpp --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -117,7 +117,7 @@ // Multisig scripts due to the restrictions on the pubkey // formats (see IsCompressedOrUncompressedPubKey) being // incongruous with the checks in - // CheckTransactionSignatureEncoding. + // CheckTransactionECDSASignatureEncoding. uint32_t flags = SCRIPT_VERIFY_STRICTENC; if (vch.back() & SIGHASH_FORKID) { // If the transaction is using SIGHASH_FORKID, we need @@ -125,7 +125,7 @@ // TODO: Remove after the Hard Fork. flags |= SCRIPT_ENABLE_SIGHASH_FORKID; } - if (CheckTransactionSignatureEncoding(vch, flags, + if (CheckTransactionECDSASignatureEncoding(vch, flags, nullptr)) { const uint8_t chSigHashType = vch.back(); if (mapSigHashTypes.count(chSigHashType)) { diff --git a/src/key.h b/src/key.h --- a/src/key.h +++ b/src/key.h @@ -107,12 +107,19 @@ CPubKey GetPubKey() const; /** - * Create a DER-serialized signature. + * Create a DER-serialized ECDSA signature. * The test_case parameter tweaks the deterministic nonce. */ bool Sign(const uint256 &hash, std::vector &vchSig, uint32_t test_case = 0) const; + /** + * Create a Schnorr signature. + * The test_case parameter tweaks the deterministic nonce. + */ + bool SignSchnorr(const uint256 &hash, std::vector &vchSig, + uint32_t test_case = 0) const; + /** * Create a compact signature (65 bytes), which allows reconstructing the * used public key. diff --git a/src/key.cpp b/src/key.cpp --- a/src/key.cpp +++ b/src/key.cpp @@ -12,6 +12,7 @@ #include #include +#include static secp256k1_context *secp256k1_context_sign = nullptr; @@ -201,6 +202,25 @@ return true; } + +bool CKey::SignSchnorr(const uint256 &hash, std::vector &vchSig, + uint32_t test_case) const { + if (!fValid) return false; + vchSig.resize(64); + uint8_t extra_entropy[32] = {0}; + WriteLE32(extra_entropy, test_case); + + secp256k1_pubkey pubkey; + int retp = secp256k1_ec_pubkey_create(secp256k1_context_sign, &pubkey, begin()); + assert(retp); + + int ret = secp256k1_schnorr_sign(secp256k1_context_sign, &vchSig[0], hash.begin(), + begin(), &pubkey, secp256k1_nonce_function_rfc6979, + test_case ? extra_entropy : nullptr); + assert(ret); + return true; +} + bool CKey::VerifyPubKey(const CPubKey &pubkey) const { if (pubkey.IsCompressed() != fCompressed) { return false; diff --git a/src/pubkey.h b/src/pubkey.h --- a/src/pubkey.h +++ b/src/pubkey.h @@ -136,11 +136,17 @@ bool IsCompressed() const { return size() == 33; } /** - * Verify a DER signature (~72 bytes). + * Verify an ECDSA DER signature (~72 bytes). * If this public key is not fully valid, the return value will be false. */ bool Verify(const uint256 &hash, const std::vector &vchSig) const; + /** + * Verify a Schnorr signature (=64 bytes). + * If this public key is not fully valid, the return value will be false. + */ + bool VerifySchnorr(const uint256 &hash, const std::vector &vchSig) const; + /** * Check whether a signature is normalized (lower-S). */ diff --git a/src/pubkey.cpp b/src/pubkey.cpp --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace { /* Global secp256k1_context object used for verification. */ @@ -194,6 +195,26 @@ &pubkey); } +bool CPubKey::VerifySchnorr(const uint256 &hash, + const std::vector &vchSig) const { + if (!IsValid()) { + return false; + } + + if (vchSig.size() != 64) { + return false; + } + + secp256k1_pubkey pubkey; + if (!secp256k1_ec_pubkey_parse(secp256k1_context_verify, &pubkey, + &(*this)[0], size())) { + return false; + } + + return secp256k1_schnorr_verify(secp256k1_context_verify, &vchSig[0], + hash.begin(), &pubkey); +} + bool CPubKey::RecoverCompact(const uint256 &hash, const std::vector &vchSig) { if (vchSig.size() != 65) { diff --git a/src/script/interpreter.h b/src/script/interpreter.h --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -29,11 +29,13 @@ class BaseSignatureChecker { public: - virtual bool VerifySignature(const std::vector &vchSig, + virtual bool VerifySignature(bool isSchnorr, + const std::vector &vchSig, const CPubKey &vchPubKey, const uint256 &sighash) const; - virtual bool CheckSig(const std::vector &scriptSig, + virtual bool CheckSig(bool isSchnorr, + const std::vector &scriptSig, const std::vector &vchPubKey, const CScript &scriptCode, uint32_t flags) const { return false; @@ -67,7 +69,8 @@ : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {} // The overriden functions are now final. - bool CheckSig(const std::vector &scriptSig, + bool CheckSig(bool isSchnorr, + const std::vector &scriptSig, const std::vector &vchPubKey, const CScript &scriptCode, uint32_t flags) const final override; diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -81,6 +81,16 @@ return true; } +static bool IsSchnorrDataSignature(const valtype &vchSig) { + return vchSig.size() == 64; +// return vchSig.size == 65 && vchSig[0] == 's'; +} + +static bool IsSchnorrTransactionSignature(const valtype &vchSig) { + return vchSig.size() == 65; +// return vchSig.size == 66 && vchSig[0] == 's'; +} + static bool IsOpcodeDisabled(opcodetype opcode, uint32_t flags) { switch (opcode) { case OP_INVERT: @@ -876,13 +886,28 @@ valtype &vchSig = stacktop(-2); valtype &vchPubKey = stacktop(-1); - if (!CheckTransactionSignatureEncoding(vchSig, flags, - serror) || - !CheckPubKeyEncoding(vchPubKey, flags, serror)) { + if (!CheckPubKeyEncoding(vchPubKey, flags, serror)) { // serror is set return false; } + bool isSchnorr = (flags & SCRIPT_ENABLE_SCHNORR) && + IsSchnorrTransactionSignature(vchSig); + + if (isSchnorr) { + if (!CheckTransactionSchnorrSignatureEncoding( + vchSig, flags, serror)) { + // serror is set + return false; + } + } else { + if (!CheckTransactionECDSASignatureEncoding( + vchSig, flags, serror)) { + // serror is set + return false; + } + } + // Subset of script starting at the most recent // codeseparator CScript scriptCode(pbegincodehash, pend); @@ -890,7 +915,8 @@ // Remove signature for pre-fork scripts CleanupScriptCode(scriptCode, vchSig, flags); - bool fSuccess = checker.CheckSig(vchSig, vchPubKey, + bool fSuccess = checker.CheckSig(isSchnorr, vchSig, + vchPubKey, scriptCode, flags); if (!fSuccess && (flags & SCRIPT_VERIFY_NULLFAIL) && @@ -928,9 +954,17 @@ valtype &vchMessage = stacktop(-2); valtype &vchPubKey = stacktop(-1); - if (!CheckDataSignatureEncoding(vchSig, flags, - serror) || - !CheckPubKeyEncoding(vchPubKey, flags, serror)) { + if (!CheckPubKeyEncoding(vchPubKey, flags, serror)) { + // serror is set + return false; + } + + bool isSchnorr = (flags & SCRIPT_ENABLE_SCHNORR) && + IsSchnorrDataSignature(vchSig); + + // Schnorr sigs do not need ECDSA encoding checked. + if (!isSchnorr && !CheckDataECDSASignatureEncoding(vchSig, + flags, serror)) { // serror is set return false; } @@ -941,7 +975,7 @@ CSHA256() .Write(vchMessage.data(), vchMessage.size()) .Finalize(vchHash.data()); - fSuccess = checker.VerifySignature( + fSuccess = checker.VerifySignature(isSchnorr, vchSig, CPubKey(vchPubKey), uint256(vchHash)); } @@ -1028,7 +1062,7 @@ // pubkey/signature evaluation distinguishable by // CHECKMULTISIG NOT if the STRICTENC flag is set. // See the script_(in)valid tests for details. - if (!CheckTransactionSignatureEncoding( + if (!CheckTransactionECDSASignatureEncoding( vchSig, flags, serror) || !CheckPubKeyEncoding(vchPubKey, flags, serror)) { @@ -1037,7 +1071,7 @@ } // Check signature - bool fOk = checker.CheckSig(vchSig, vchPubKey, + bool fOk = checker.CheckSig(false, vchSig, vchPubKey, scriptCode, flags); if (fOk) { @@ -1462,13 +1496,19 @@ return ss.GetHash(); } -bool BaseSignatureChecker::VerifySignature(const std::vector &vchSig, +bool BaseSignatureChecker::VerifySignature(bool isSchnorr, + const std::vector &vchSig, const CPubKey &pubkey, const uint256 &sighash) const { - return pubkey.Verify(sighash, vchSig); + if (isSchnorr) { + return pubkey.VerifySchnorr(sighash, vchSig); + } else { + return pubkey.Verify(sighash, vchSig); + } } bool TransactionSignatureChecker::CheckSig( + bool isSchnorr, const std::vector &vchSigIn, const std::vector &vchPubKey, const CScript &scriptCode, uint32_t flags) const { CPubKey pubkey(vchPubKey); @@ -1487,7 +1527,7 @@ uint256 sighash = SignatureHash(scriptCode, *txTo, nIn, sigHashType, amount, this->txdata, flags); - if (!VerifySignature(vchSig, pubkey, sighash)) { + if (!VerifySignature(isSchnorr, vchSig, pubkey, sighash)) { return false; } diff --git a/src/script/script_flags.h b/src/script/script_flags.h --- a/src/script/script_flags.h +++ b/src/script/script_flags.h @@ -98,6 +98,12 @@ // Is OP_CHECKDATASIG and variant are enabled. // SCRIPT_ENABLE_CHECKDATASIG = (1U << 18), + + // Are OP_CHECK[DATA]SIG[VERIFY] overloaded to allow Schnorr signatures? + // Note this does not include OP_CHECKMULTISIG[VERIFY]. + // + SCRIPT_ENABLE_SCHNORR = (1U << 19), + }; #endif // BITCOIN_SCRIPT_SCRIPTFLAGS_H diff --git a/src/script/sigcache.h b/src/script/sigcache.h --- a/src/script/sigcache.h +++ b/src/script/sigcache.h @@ -51,7 +51,8 @@ : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn), store(storeIn) {} - bool VerifySignature(const std::vector &vchSig, + bool VerifySignature(bool isSchnorr, + const std::vector &vchSig, const CPubKey &vchPubKey, const uint256 &sighash) const override; }; diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp --- a/src/script/sigcache.cpp +++ b/src/script/sigcache.cpp @@ -33,12 +33,14 @@ CSignatureCache() { GetRandBytes(nonce.begin(), 32); } void ComputeEntry(uint256 &entry, const uint256 &hash, - const std::vector &vchSig, + bool isSchnorr, const std::vector &vchSig, const CPubKey &pubkey) { + uint8_t sb = isSchnorr?1:0; CSHA256() .Write(nonce.begin(), 32) .Write(hash.begin(), 32) .Write(&pubkey[0], pubkey.size()) + .Write(&sb, 1) .Write(&vchSig[0], vchSig.size()) .Finalize(entry.begin()); } @@ -81,15 +83,16 @@ } bool CachingTransactionSignatureChecker::VerifySignature( + bool isSchnorr, const std::vector &vchSig, const CPubKey &pubkey, const uint256 &sighash) const { uint256 entry; - signatureCache.ComputeEntry(entry, sighash, vchSig, pubkey); + signatureCache.ComputeEntry(entry, sighash, isSchnorr, vchSig, pubkey); if (signatureCache.Get(entry, !store)) { return true; } - if (!TransactionSignatureChecker::VerifySignature(vchSig, pubkey, - sighash)) { + if (!TransactionSignatureChecker::VerifySignature(isSchnorr, vchSig, + pubkey, sighash)) { return false; } if (store) { diff --git a/src/script/sigencoding.h b/src/script/sigencoding.h --- a/src/script/sigencoding.h +++ b/src/script/sigencoding.h @@ -28,19 +28,27 @@ } // namespace /** - * Check that the signature provided on some data is properly encoded. - * Signatures passed to OP_CHECKDATASIG and its verify variant must be checked + * Check that the ECDSA signature provided on some data is properly encoded. + * ECDSA signatures passed to OP_CHECKDATASIG and its verify variant must be checked * using this function. */ -bool CheckDataSignatureEncoding(const valtype &vchSig, uint32_t flags, - ScriptError *serror); +bool CheckDataECDSASignatureEncoding(const valtype &vchSig, uint32_t flags, + ScriptError *serror); /** - * Check that the signature provided to authentify a transaction is properly - * encoded. Signatures passed to OP_CHECKSIG, OP_CHECKMULTISIG and their verify + * Check that the ECDSA signature provided to authentify a transaction is properly + * encoded. ECDSA signatures passed to OP_CHECKSIG, OP_CHECKMULTISIG and their verify * variants must be checked using this function. */ -bool CheckTransactionSignatureEncoding(const valtype &vchSig, uint32_t flags, +bool CheckTransactionECDSASignatureEncoding(const valtype &vchSig, uint32_t flags, + ScriptError *serror); + +/** + * Check that the ECDSA signature provided to authentify a transaction is properly + * encoded. ECDSA signatures passed to OP_CHECKSIG, OP_CHECKMULTISIG and their verify + * variants must be checked using this function. + */ +bool CheckTransactionSchnorrSignatureEncoding(const valtype &vchSig, uint32_t flags, ScriptError *serror); /** diff --git a/src/script/sigencoding.cpp b/src/script/sigencoding.cpp --- a/src/script/sigencoding.cpp +++ b/src/script/sigencoding.cpp @@ -168,7 +168,29 @@ return true; } -bool CheckDataSignatureEncoding(const valtype &vchSig, uint32_t flags, +static bool CheckTransactionSignatureHashType(const valtype &vchSig, uint32_t flags, + ScriptError *serror) { + if (flags & SCRIPT_VERIFY_STRICTENC) { + SigHashType hashType = GetHashType(vchSig); + if (!hashType.isDefined()) { + return set_error(serror, SCRIPT_ERR_SIG_HASHTYPE); + } + + bool usesForkId = hashType.hasForkId(); + bool forkIdEnabled = flags & SCRIPT_ENABLE_SIGHASH_FORKID; + if (!forkIdEnabled && usesForkId) { + return set_error(serror, SCRIPT_ERR_ILLEGAL_FORKID); + } + + if (forkIdEnabled && !usesForkId) { + return set_error(serror, SCRIPT_ERR_MUST_USE_FORKID); + } + } + + return true; +} + +bool CheckDataECDSASignatureEncoding(const valtype &vchSig, uint32_t flags, ScriptError *serror) { // Empty signature. Not strictly DER encoded, but allowed to provide a // compact way to provide an invalid signature for use with CHECK(MULTI)SIG @@ -180,7 +202,7 @@ vchSig | boost::adaptors::sliced(0, vchSig.size()), flags, serror); } -bool CheckTransactionSignatureEncoding(const valtype &vchSig, uint32_t flags, +bool CheckTransactionECDSASignatureEncoding(const valtype &vchSig, uint32_t flags, ScriptError *serror) { // Empty signature. Not strictly DER encoded, but allowed to provide a // compact way to provide an invalid signature for use with CHECK(MULTI)SIG @@ -195,23 +217,18 @@ return false; } - if (flags & SCRIPT_VERIFY_STRICTENC) { - if (!GetHashType(vchSig).isDefined()) { - return set_error(serror, SCRIPT_ERR_SIG_HASHTYPE); - } - - bool usesForkId = GetHashType(vchSig).hasForkId(); - bool forkIdEnabled = flags & SCRIPT_ENABLE_SIGHASH_FORKID; - if (!forkIdEnabled && usesForkId) { - return set_error(serror, SCRIPT_ERR_ILLEGAL_FORKID); - } + return CheckTransactionSignatureHashType(vchSig, flags, serror); +} - if (forkIdEnabled && !usesForkId) { - return set_error(serror, SCRIPT_ERR_MUST_USE_FORKID); - } +bool CheckTransactionSchnorrSignatureEncoding(const valtype &vchSig, uint32_t flags, + ScriptError *serror) { + // Empty signature. Not strictly valid, but allowed to provide a + // compact way to provide an invalid signature for use with CHECKMULTISIG + if (vchSig.size() == 0) { + return true; } - return true; + return CheckTransactionSignatureHashType(vchSig, flags, serror); } static bool IsCompressedOrUncompressedPubKey(const valtype &vchPubKey) { diff --git a/src/script/sign.cpp b/src/script/sign.cpp --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -239,7 +239,7 @@ continue; } - if (checker.CheckSig(sig, pubkey, scriptPubKey, + if (checker.CheckSig(false, sig, pubkey, scriptPubKey, STANDARD_SCRIPT_VERIFY_FLAGS)) { sigs[pubkey] = sig; break; @@ -358,7 +358,8 @@ public: DummySignatureChecker() {} - bool CheckSig(const std::vector &scriptSig, + bool CheckSig(bool isSchnorr, + const std::vector &scriptSig, const std::vector &vchPubKey, const CScript &scriptCode, uint32_t flags) const override { return true; diff --git a/src/test/sigencoding_tests.cpp b/src/test/sigencoding_tests.cpp --- a/src/test/sigencoding_tests.cpp +++ b/src/test/sigencoding_tests.cpp @@ -19,7 +19,7 @@ static void CheckSignatureEncodingWithSigHashType(const valtype &vchSig, uint32_t flags) { ScriptError err = SCRIPT_ERR_OK; - BOOST_CHECK(CheckDataSignatureEncoding(vchSig, flags, &err)); + BOOST_CHECK(CheckDataECDSASignatureEncoding(vchSig, flags, &err)); const bool hasForkId = (flags & SCRIPT_ENABLE_SIGHASH_FORKID) != 0; const bool hasStrictEnc = (flags & SCRIPT_VERIFY_STRICTENC) != 0; @@ -38,7 +38,7 @@ // Check the signature with the proper forkid flag. SigHashType sigHash = baseSigHash.withForkId(hasForkId); valtype validSig = SignatureWithHashType(vchSig, sigHash); - BOOST_CHECK(CheckTransactionSignatureEncoding(validSig, flags, &err)); + BOOST_CHECK(CheckTransactionECDSASignatureEncoding(validSig, flags, &err)); // If we have strict encoding, we prevent the use of undefined flags. std::array undefSigHashes{ @@ -48,7 +48,7 @@ for (SigHashType undefSigHash : undefSigHashes) { valtype undefSighash = SignatureWithHashType(vchSig, undefSigHash); BOOST_CHECK_EQUAL( - CheckTransactionSignatureEncoding(undefSighash, flags, &err), + CheckTransactionECDSASignatureEncoding(undefSighash, flags, &err), !hasStrictEnc); if (hasStrictEnc) { BOOST_CHECK_EQUAL(err, SCRIPT_ERR_SIG_HASHTYPE); @@ -60,7 +60,7 @@ valtype invalidSig = SignatureWithHashType(vchSig, invalidSigHash); BOOST_CHECK_EQUAL( - CheckTransactionSignatureEncoding(invalidSig, flags, &err), + CheckTransactionECDSASignatureEncoding(invalidSig, flags, &err), !hasStrictEnc); if (hasStrictEnc) { BOOST_CHECK_EQUAL(err, hasForkId ? SCRIPT_ERR_MUST_USE_FORKID @@ -162,15 +162,15 @@ ScriptError err = SCRIPT_ERR_OK; // Empty sig is always valid. - BOOST_CHECK(CheckDataSignatureEncoding({}, flags, &err)); - BOOST_CHECK(CheckTransactionSignatureEncoding({}, flags, &err)); + BOOST_CHECK(CheckDataECDSASignatureEncoding({}, flags, &err)); + BOOST_CHECK(CheckTransactionECDSASignatureEncoding({}, flags, &err)); // Signature are valid as long as the forkid flag is correct. CheckSignatureEncodingWithSigHashType(minimalSig, flags); if (flags & SCRIPT_VERIFY_LOW_S) { // If we do enforce low S, then high S sigs are rejected. - BOOST_CHECK(!CheckDataSignatureEncoding(highSSig, flags, &err)); + BOOST_CHECK(!CheckDataECDSASignatureEncoding(highSSig, flags, &err)); BOOST_CHECK_EQUAL(err, SCRIPT_ERR_SIG_HIGH_S); } else { // If we do not enforce low S, then high S sigs are accepted. @@ -183,11 +183,11 @@ // If we get any of the dersig flags, the non canonical dersig // signature fails. BOOST_CHECK( - !CheckDataSignatureEncoding(nonDERSig, flags, &err)); + !CheckDataECDSASignatureEncoding(nonDERSig, flags, &err)); BOOST_CHECK_EQUAL(err, SCRIPT_ERR_SIG_DER); } else { // If we do not check, then it is accepted. - BOOST_CHECK(CheckDataSignatureEncoding(nonDERSig, flags, &err)); + BOOST_CHECK(CheckDataECDSASignatureEncoding(nonDERSig, flags, &err)); } } @@ -197,11 +197,11 @@ // If we get any of the dersig flags, the high S but non dersig // signature fails. BOOST_CHECK( - !CheckDataSignatureEncoding(nonDERSig, flags, &err)); + !CheckDataECDSASignatureEncoding(nonDERSig, flags, &err)); BOOST_CHECK_EQUAL(err, SCRIPT_ERR_SIG_DER); } else { // If we do not check, then it is accepted. - BOOST_CHECK(CheckDataSignatureEncoding(nonDERSig, flags, &err)); + BOOST_CHECK(CheckDataECDSASignatureEncoding(nonDERSig, flags, &err)); } } }