diff --git a/src/key.h b/src/key.h --- a/src/key.h +++ b/src/key.h @@ -113,6 +113,13 @@ bool SignECDSA(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 ECDSA 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; @@ -185,7 +186,9 @@ bool CKey::SignECDSA(const uint256 &hash, std::vector &vchSig, uint32_t test_case) const { - if (!fValid) return false; + if (!fValid) { + return false; + } vchSig.resize(72); size_t nSigLen = 72; uint8_t extra_entropy[32] = {0}; @@ -201,6 +204,22 @@ 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); + + int ret = secp256k1_schnorr_sign( + secp256k1_context_sign, &vchSig[0], hash.begin(), begin(), + 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; @@ -220,7 +239,9 @@ bool CKey::SignCompact(const uint256 &hash, std::vector &vchSig) const { - if (!fValid) return false; + if (!fValid) { + return false; + } vchSig.resize(65); int rec = -1; secp256k1_ecdsa_recoverable_signature sig; @@ -244,7 +265,9 @@ fCompressed = vchPubKey.IsCompressed(); fValid = true; - if (fSkipCheck) return true; + if (fSkipCheck) { + return true; + } return VerifyPubKey(vchPubKey); } diff --git a/src/pubkey.h b/src/pubkey.h --- a/src/pubkey.h +++ b/src/pubkey.h @@ -142,6 +142,13 @@ bool VerifyECDSA(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 DER-serialized ECDSA 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/test/key_tests.cpp b/src/test/key_tests.cpp --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -32,8 +32,55 @@ static const std::string strAddressBad = "1HV9Lc3sNHZxwj4Zk6fB38tEmBryq2cBiF"; +// get r value produced by ECDSA signing algorithm +// (assumes ECDSA r is encoded in the canonical manner) +std::vector get_r_ECDSA(std::vector sigECDSA) { + std::vector ret(32, 0); + + assert(sigECDSA[2] == 2); + int rlen = sigECDSA[3]; + assert(rlen <= 33); + assert(sigECDSA[4 + rlen] == 2); + if (rlen == 33) { + assert(sigECDSA[4] == 0); + std::copy(sigECDSA.begin() + 5, sigECDSA.begin() + 37, ret.begin()); + } else { + std::copy(sigECDSA.begin() + 4, sigECDSA.begin() + 36, + ret.begin() + (32 - rlen)); + } + return ret; +} + BOOST_FIXTURE_TEST_SUITE(key_tests, BasicTestingSetup) +BOOST_AUTO_TEST_CASE(internal_test) { + // test get_r_ECDSA (defined above) to make sure it's working properly + BOOST_CHECK(get_r_ECDSA(ParseHex( + "3045022100c6ab5f8acfccc114da39dd5ad0b1ef4d39df6a721e8" + "24c22e00b7bc7944a1f7802206ff23df3802e241ee234a8b66c40" + "c82e56a6cc37f9b50463111c9f9229b8f3b3")) == + ParseHex("c6ab5f8acfccc114da39dd5ad0b1ef4d39df6a721e8" + "24c22e00b7bc7944a1f78")); + BOOST_CHECK(get_r_ECDSA(ParseHex( + "3045022046ab5f8acfccc114da39dd5ad0b1ef4d39df6a721e8" + "24c22e00b7bc7944a1f7802206ff23df3802e241ee234a8b66c40" + "c82e56a6cc37f9b50463111c9f9229b8f3b3")) == + ParseHex("46ab5f8acfccc114da39dd5ad0b1ef4d39df6a721e8" + "24c22e00b7bc7944a1f78")); + BOOST_CHECK(get_r_ECDSA(ParseHex( + "3045021f4b5f8acfccc114da39dd5ad0b1ef4d39df6a721e8" + "24c22e00b7bc7944a1f7802206ff23df3802e241ee234a8b66c40" + "c82e56a6cc37f9b50463111c9f9229b8f3b3")) == + ParseHex("004b5f8acfccc114da39dd5ad0b1ef4d39df6a721e8" + "24c22e00b7bc7944a1f78")); + BOOST_CHECK(get_r_ECDSA(ParseHex( + "3045021e5f8acfccc114da39dd5ad0b1ef4d39df6a721e8" + "24c22e00b7bc7944a1f7802206ff23df3802e241ee234a8b66c40" + "c82e56a6cc37f9b50463111c9f9229b8f3b3")) == + ParseHex("00005f8acfccc114da39dd5ad0b1ef4d39df6a721e8" + "24c22e00b7bc7944a1f78")); +} + BOOST_AUTO_TEST_CASE(key_test1) { CBitcoinSecret bsecret1, bsecret2, bsecret1C, bsecret2C, baddress1; BOOST_CHECK(bsecret1.SetString(strSecret1)); @@ -90,7 +137,7 @@ std::string strMsg = strprintf("Very secret message %i: 11", n); uint256 hashMsg = Hash(strMsg.begin(), strMsg.end()); - // normal signatures + // normal ECDSA signatures std::vector sign1, sign2, sign1C, sign2C; @@ -119,7 +166,7 @@ BOOST_CHECK(!pubkey2C.VerifyECDSA(hashMsg, sign1C)); BOOST_CHECK(pubkey2C.VerifyECDSA(hashMsg, sign2C)); - // compact signatures (with key recovery) + // compact ECDSA signatures (with key recovery) std::vector csign1, csign2, csign1C, csign2C; @@ -139,13 +186,65 @@ BOOST_CHECK(rkey2 == pubkey2); BOOST_CHECK(rkey1C == pubkey1C); BOOST_CHECK(rkey2C == pubkey2C); + + // Schnorr signatures + + std::vector ssign1, ssign2, ssign1C, ssign2C; + + BOOST_CHECK(key1.SignSchnorr(hashMsg, ssign1)); + BOOST_CHECK(key2.SignSchnorr(hashMsg, ssign2)); + BOOST_CHECK(key1C.SignSchnorr(hashMsg, ssign1C)); + BOOST_CHECK(key2C.SignSchnorr(hashMsg, ssign2C)); + + BOOST_CHECK(pubkey1.VerifySchnorr(hashMsg, ssign1)); + BOOST_CHECK(!pubkey1.VerifySchnorr(hashMsg, ssign2)); + BOOST_CHECK(pubkey1.VerifySchnorr(hashMsg, ssign1C)); + BOOST_CHECK(!pubkey1.VerifySchnorr(hashMsg, ssign2C)); + + BOOST_CHECK(!pubkey2.VerifySchnorr(hashMsg, ssign1)); + BOOST_CHECK(pubkey2.VerifySchnorr(hashMsg, ssign2)); + BOOST_CHECK(!pubkey2.VerifySchnorr(hashMsg, ssign1C)); + BOOST_CHECK(pubkey2.VerifySchnorr(hashMsg, ssign2C)); + + BOOST_CHECK(pubkey1C.VerifySchnorr(hashMsg, ssign1)); + BOOST_CHECK(!pubkey1C.VerifySchnorr(hashMsg, ssign2)); + BOOST_CHECK(pubkey1C.VerifySchnorr(hashMsg, ssign1C)); + BOOST_CHECK(!pubkey1C.VerifySchnorr(hashMsg, ssign2C)); + + BOOST_CHECK(!pubkey2C.VerifySchnorr(hashMsg, ssign1)); + BOOST_CHECK(pubkey2C.VerifySchnorr(hashMsg, ssign2)); + BOOST_CHECK(!pubkey2C.VerifySchnorr(hashMsg, ssign1C)); + BOOST_CHECK(pubkey2C.VerifySchnorr(hashMsg, ssign2C)); + + // check deterministicity of ECDSA & Schnorr + BOOST_CHECK(sign1 == sign1C); + BOOST_CHECK(sign2 == sign2C); + BOOST_CHECK(ssign1 == ssign1C); + BOOST_CHECK(ssign2 == ssign2C); + + // Extract r value from ECDSA and Schnorr. Make sure they are + // distinct (nonce reuse would be dangerous and can leak private key). + std::vector rE1 = get_r_ECDSA(sign1); + BOOST_CHECK(ssign1.size() == 64); + std::vector rS1(ssign1.begin(), ssign1.begin() + 32); + BOOST_CHECK(rE1.size() == 32); + BOOST_CHECK(rS1.size() == 32); + BOOST_CHECK(rE1 != rS1); + + std::vector rE2 = get_r_ECDSA(sign2); + BOOST_CHECK(ssign2.size() == 64); + std::vector rS2(ssign2.begin(), ssign2.begin() + 32); + BOOST_CHECK(rE2.size() == 32); + BOOST_CHECK(rS2.size() == 32); + BOOST_CHECK(rE2 != rS2); } - // test deterministic signing + // test deterministic signing expected values std::vector detsig, detsigc; std::string strMsg = "Very deterministic message"; uint256 hashMsg = Hash(strMsg.begin(), strMsg.end()); + // ECDSA BOOST_CHECK(key1.SignECDSA(hashMsg, detsig)); BOOST_CHECK(key1C.SignECDSA(hashMsg, detsigc)); BOOST_CHECK(detsig == detsigc); @@ -160,6 +259,7 @@ ParseHex("304502210094dc5a77b8d5db6b42b66c29d7033cd873fac7a1272" "4a90373726f60bb9f852a02204eb4c98b9a2f5c017f9417ba7c43" "279c20c84bb058dc05b3beeb9333016b15bb")); + // Compact BOOST_CHECK(key1.SignCompact(hashMsg, detsig)); BOOST_CHECK(key1C.SignCompact(hashMsg, detsigc)); BOOST_CHECK(detsig == @@ -180,6 +280,21 @@ ParseHex("209ffc56b38fbfc0e3eb2c42dff99d2375982449f35019c1b3d56" "ca62bef187c5103e483a0ad481eaacc224fef4ee2995027300d5f" "2457f7a20c43547aeddbae6e")); + // Schnorr + BOOST_CHECK(key1.SignSchnorr(hashMsg, detsig)); + BOOST_CHECK(key1C.SignSchnorr(hashMsg, detsigc)); + BOOST_CHECK(detsig == detsigc); + BOOST_CHECK(detsig == + ParseHex("2c56731ac2f7a7e7f11518fc7722a166b02438924ca9d8b4d1113" + "47b81d0717571846de67ad3d913a8fdf9d8f3f73161a4c48ae81c" + "b183b214765feb86e255ce")); + BOOST_CHECK(key2.SignSchnorr(hashMsg, detsig)); + BOOST_CHECK(key2C.SignSchnorr(hashMsg, detsigc)); + BOOST_CHECK(detsig == detsigc); + BOOST_CHECK(detsig == + ParseHex("e7167ae0afbba6019b4c7fcfe6de79165d555e8295bd72da1b8aa" + "1a5b54305880517cace1bcb0cb515e2eeaffd49f1e4dd49fd7282" + "6b4b1573c84da49a38405d")); } BOOST_AUTO_TEST_SUITE_END()