From 0ca4c41a9780814de19b997f1635668781dd5fdb Mon Sep 17 00:00:00 2001 From: Jeffrey Walton Date: Sun, 10 Feb 2019 23:08:14 -0500 Subject: [PATCH] Add ed25519 SignStream and VerifyStream functions (GH #796, PR #797) --- donna.h | 35 ++++++++++++++- donna_32.cpp | 122 +++++++++++++++++++++++++++++++++++++++++++++++++-- donna_64.cpp | 122 +++++++++++++++++++++++++++++++++++++++++++++++++-- poly1305.cpp | 2 +- validat7.cpp | 53 ++++++++++++++++++++++ xed25519.cpp | 21 +++++++++ xed25519.h | 21 +++++++++ 7 files changed, 367 insertions(+), 9 deletions(-) diff --git a/donna.h b/donna.h index 68a0c04b..321da901 100644 --- a/donna.h +++ b/donna.h @@ -81,6 +81,23 @@ int ed25519_publickey(byte publicKey[32], const byte secretKey[32]); /// SHA512. int ed25519_sign(const byte* message, size_t messageLength, const byte secretKey[32], const byte publicKey[32], byte signature[64]); +/// \brief Creates a signature on a message +/// \param stream std::istream derived class +/// \param publicKey byte array with the public key +/// \param secretKey byte array with the private key +/// \param signature byte array for the signature +/// \returns 0 on success, non-0 otherwise +/// \details ed25519_sign() generates a signature on a message using +/// the public and private keys. The various buffers can be exact +/// sizes, and do not require extra space like when using the +/// NaCl library functions. +/// \details This ed25519_sign() overload handles large streams. It +/// was added for signing and verifying files that are too large +/// for a memory allocation. +/// \details At the moment the hash function for signing is fixed at +/// SHA512. +int ed25519_sign(std::istream& stream, const byte secretKey[32], const byte publicKey[32], byte signature[64]); + /// \brief Verifies a signature on a message /// \param message byte array with the message /// \param messageLength size of the message, in bytes @@ -88,13 +105,29 @@ int ed25519_sign(const byte* message, size_t messageLength, const byte secretKey /// \param signature byte array with the signature /// \returns 0 on success, non-0 otherwise /// \details ed25519_sign_open() verifies a signature on a message using -/// the public. The various buffers can be exact sizes, and do not +/// the public key. The various buffers can be exact sizes, and do not /// require extra space like when using the NaCl library functions. /// \details At the moment the hash function for signing is fixed at /// SHA512. int ed25519_sign_open(const byte *message, size_t messageLength, const byte publicKey[32], const byte signature[64]); +/// \brief Verifies a signature on a message +/// \param stream std::istream derived class +/// \param publicKey byte array with the public key +/// \param signature byte array with the signature +/// \returns 0 on success, non-0 otherwise +/// \details ed25519_sign_open() verifies a signature on a message using +/// the public key. The various buffers can be exact sizes, and do not +/// require extra space like when using the NaCl library functions. +/// \details This ed25519_sign_open() overload handles large streams. It +/// was added for signing and verifying files that are too large +/// for a memory allocation. +/// \details At the moment the hash function for signing is fixed at +/// SHA512. +int +ed25519_sign_open(std::istream& stream, const byte publicKey[32], const byte signature[64]); + //****************************** Internal ******************************// #ifndef CRYPTOPP_DOXYGEN_PROCESSING diff --git a/donna_32.cpp b/donna_32.cpp index c20f7b3e..25227ff7 100644 --- a/donna_32.cpp +++ b/donna_32.cpp @@ -27,6 +27,9 @@ #include "misc.h" #include "cpu.h" +#include +#include + #if CRYPTOPP_GCC_DIAGNOSTIC_AVAILABLE # pragma GCC diagnostic ignored "-Wunused-function" #endif @@ -1033,7 +1036,21 @@ ed25519_extsk(hash_512bits extsk, const byte sk[32]) { } void -ed25519_hram(hash_512bits hram, const byte RS[64], const byte pk[32], const unsigned char *m, size_t mlen) { +UpdateFromStream(HashTransformation& hash, std::istream& stream) +{ + SecByteBlock block(4096); + while (stream.read((char*)block.begin(), block.size())) + hash.Update(block, block.size()); + + std::streamsize rem = stream.gcount(); + if (rem) + hash.Update(block, (size_t)rem); + + block.SetMark(0); +} + +void +ed25519_hram(hash_512bits hram, const byte RS[64], const byte pk[32], const byte *m, size_t mlen) { SHA512 hash; hash.Update(RS, 32); hash.Update(pk, 32); @@ -1041,6 +1058,15 @@ ed25519_hram(hash_512bits hram, const byte RS[64], const byte pk[32], const unsi hash.Final(hram); } +void +ed25519_hram(hash_512bits hram, const byte RS[64], const byte pk[32], std::istream& stream) { + SHA512 hash; + hash.Update(RS, 32); + hash.Update(pk, 32); + UpdateFromStream(hash, stream); + hash.Final(hram); +} + inline bignum256modm_element_t lt_modm(bignum256modm_element_t a, bignum256modm_element_t b) { return (a - b) >> 31; @@ -1556,7 +1582,7 @@ ge25519_pack(byte r[32], const ge25519 *p) { } int -ed25519_verify(const unsigned char *x, const unsigned char *y, size_t len) { +ed25519_verify(const byte *x, const byte *y, size_t len) { size_t differentbits = 0; while (len--) differentbits |= (*x++ ^ *y++); @@ -1857,6 +1883,55 @@ ed25519_publickey(byte publicKey[32], const byte secretKey[32]) return ed25519_publickey_CXX(publicKey, secretKey); } +int +ed25519_sign_CXX(std::istream& stream, const byte sk[32], const byte pk[32], byte RS[64]) +{ + using namespace CryptoPP::Donna::Ed25519; + + bignum256modm r, S, a; + ALIGN(16) ge25519 R; + hash_512bits extsk, hashr, hram; + + // Unfortunately we need to read the stream twice. The fisrt time calculates + // 'r = H(aExt[32..64], m)'. The second time calculates 'S = H(R,A,m)'. There + // is a data dependency due to hashing 'RS' with 'R = [r]B' that does not + // allow us to read the stream once. + std::streampos where = stream.tellg(); + + ed25519_extsk(extsk, sk); + + /* r = H(aExt[32..64], m) */ + SHA512 hash; + hash.Update(extsk + 32, 32); + UpdateFromStream(hash, stream); + hash.Final(hashr); + expand256_modm(r, hashr, 64); + + /* R = rB */ + ge25519_scalarmult_base_niels(&R, ge25519_niels_base_multiples, r); + ge25519_pack(RS, &R); + + // Reset stream for the second digest + stream.clear(); + stream.seekg(where); + + /* S = H(R,A,m).. */ + ed25519_hram(hram, RS, pk, stream); + expand256_modm(S, hram, 64); + + /* S = H(R,A,m)a */ + expand256_modm(a, extsk, 32); + mul256_modm(S, S, a); + + /* S = (r + H(R,A,m)a) */ + add256_modm(S, S, r); + + /* S = (r + H(R,A,m)a) mod L */ + contract256_modm(RS + 32, S); + + return 0; +} + int ed25519_sign_CXX(const byte *m, size_t mlen, const byte sk[32], const byte pk[32], byte RS[64]) { @@ -1896,6 +1971,13 @@ ed25519_sign_CXX(const byte *m, size_t mlen, const byte sk[32], const byte pk[32 return 0; } +int +ed25519_sign(std::istream& stream, const byte secretKey[32], const byte publicKey[32], + byte signature[64]) +{ + return ed25519_sign_CXX(stream, secretKey, publicKey, signature); +} + int ed25519_sign(const byte* message, size_t messageLength, const byte secretKey[32], const byte publicKey[32], byte signature[64]) @@ -1903,6 +1985,34 @@ ed25519_sign(const byte* message, size_t messageLength, const byte secretKey[32] return ed25519_sign_CXX(message, messageLength, secretKey, publicKey, signature); } +int +ed25519_sign_open_CXX(std::istream& stream, const byte pk[32], const byte RS[64]) { + + using namespace CryptoPP::Donna::Ed25519; + + ALIGN(16) ge25519 R, A; + hash_512bits hash; + bignum256modm hram, S; + byte checkR[32]; + + if ((RS[63] & 224) || !ge25519_unpack_negative_vartime(&A, pk)) + return -1; + + /* hram = H(R,A,m) */ + ed25519_hram(hash, RS, pk, stream); + expand256_modm(hram, hash, 64); + + /* S */ + expand256_modm(S, RS + 32, 32); + + /* SB - H(R,A,m)A */ + ge25519_double_scalarmult_vartime(&R, &A, hram, S); + ge25519_pack(checkR, &R); + + /* check that R = SB - H(R,A,m)A */ + return ed25519_verify(RS, checkR, 32) ? 0 : -1; +} + int ed25519_sign_open_CXX(const byte *m, size_t mlen, const byte pk[32], const byte RS[64]) { @@ -1911,7 +2021,7 @@ ed25519_sign_open_CXX(const byte *m, size_t mlen, const byte pk[32], const byte ALIGN(16) ge25519 R, A; hash_512bits hash; bignum256modm hram, S; - unsigned char checkR[32]; + byte checkR[32]; if ((RS[63] & 224) || !ge25519_unpack_negative_vartime(&A, pk)) return -1; @@ -1937,6 +2047,12 @@ ed25519_sign_open(const byte *message, size_t messageLength, const byte publicKe return ed25519_sign_open_CXX(message, messageLength, publicKey, signature); } +int +ed25519_sign_open(std::istream& stream, const byte publicKey[32], const byte signature[64]) +{ + return ed25519_sign_open_CXX(stream, publicKey, signature); +} + NAMESPACE_END // Donna NAMESPACE_END // CryptoPP diff --git a/donna_64.cpp b/donna_64.cpp index b08d1700..5319c636 100644 --- a/donna_64.cpp +++ b/donna_64.cpp @@ -27,6 +27,9 @@ #include "misc.h" #include "cpu.h" +#include +#include + #if CRYPTOPP_GCC_DIAGNOSTIC_AVAILABLE # pragma GCC diagnostic ignored "-Wunused-function" #endif @@ -781,7 +784,21 @@ ed25519_extsk(hash_512bits extsk, const byte sk[32]) { } void -ed25519_hram(hash_512bits hram, const byte RS[64], const byte pk[32], const unsigned char *m, size_t mlen) { +UpdateFromStream(HashTransformation& hash, std::istream& stream) +{ + SecByteBlock block(4096); + while (stream.read((char*)block.begin(), block.size())) + hash.Update(block, block.size()); + + std::streamsize rem = stream.gcount(); + if (rem) + hash.Update(block, rem); + + block.SetMark(0); +} + +void +ed25519_hram(hash_512bits hram, const byte RS[64], const byte pk[32], const byte *m, size_t mlen) { SHA512 hash; hash.Update(RS, 32); hash.Update(pk, 32); @@ -789,6 +806,15 @@ ed25519_hram(hash_512bits hram, const byte RS[64], const byte pk[32], const unsi hash.Final(hram); } +void +ed25519_hram(hash_512bits hram, const byte RS[64], const byte pk[32], std::istream& stream) { + SHA512 hash; + hash.Update(RS, 32); + hash.Update(pk, 32); + UpdateFromStream(hash, stream); + hash.Final(hram); +} + bignum256modm_element_t lt_modm(bignum256modm_element_t a, bignum256modm_element_t b) { return (a - b) >> 63; @@ -861,7 +887,6 @@ barrett_reduce256_modm(bignum256modm r, const bignum256modm q1, const bignum256m reduce256_modm(r); } - void add256_modm(bignum256modm r, const bignum256modm x, const bignum256modm y) { bignum256modm_element_t c; @@ -1272,7 +1297,7 @@ ge25519_pack(byte r[32], const ge25519 *p) { } int -ed25519_verify(const unsigned char *x, const unsigned char *y, size_t len) { +ed25519_verify(const byte *x, const byte *y, size_t len) { size_t differentbits = 0; while (len--) differentbits |= (*x++ ^ *y++); @@ -1573,6 +1598,54 @@ ed25519_publickey(byte publicKey[32], const byte secretKey[32]) return ed25519_publickey_CXX(publicKey, secretKey); } +int +ed25519_sign_CXX(std::istream& stream, const byte sk[32], const byte pk[32], byte RS[64]) +{ + using namespace CryptoPP::Donna::Ed25519; + + bignum256modm r, S, a; + ALIGN(16) ge25519 R; + hash_512bits extsk, hashr, hram; + + // Unfortunately we need to read the stream twice. The fisrt time calculates + // 'r = H(aExt[32..64], m)'. The second time calculates 'S = H(R,A,m)'. There + // is a data dependency due to hashing 'RS' with 'R = [r]B' that does not + // allow us to read the stream once. + std::streampos where = stream.tellg(); + + ed25519_extsk(extsk, sk); + + /* r = H(aExt[32..64], m) */ + SHA512 hash; + hash.Update(extsk + 32, 32); + UpdateFromStream(hash, stream); + hash.Final(hashr); + expand256_modm(r, hashr, 64); + + /* R = rB */ + ge25519_scalarmult_base_niels(&R, ge25519_niels_base_multiples, r); + ge25519_pack(RS, &R); + + // Reset stream for the second digest + stream.clear(); + stream.seekg(where); + + /* S = H(R,A,m).. */ + ed25519_hram(hram, RS, pk, stream); + expand256_modm(S, hram, 64); + + /* S = H(R,A,m)a */ + expand256_modm(a, extsk, 32); + mul256_modm(S, S, a); + + /* S = (r + H(R,A,m)a) */ + add256_modm(S, S, r); + + /* S = (r + H(R,A,m)a) mod L */ + contract256_modm(RS + 32, S); + return 0; +} + int ed25519_sign_CXX(const byte *m, size_t mlen, const byte sk[32], const byte pk[32], byte RS[64]) { @@ -1611,6 +1684,13 @@ ed25519_sign_CXX(const byte *m, size_t mlen, const byte sk[32], const byte pk[32 return 0; } +int +ed25519_sign(std::istream& stream, const byte secretKey[32], const byte publicKey[32], + byte signature[64]) +{ + return ed25519_sign_CXX(stream, secretKey, publicKey, signature); +} + int ed25519_sign(const byte* message, size_t messageLength, const byte secretKey[32], const byte publicKey[32], byte signature[64]) @@ -1626,7 +1706,7 @@ ed25519_sign_open_CXX(const byte *m, size_t mlen, const byte pk[32], const byte ALIGN(16) ge25519 R, A; hash_512bits hash; bignum256modm hram, S; - unsigned char checkR[32]; + byte checkR[32]; if ((RS[63] & 224) || !ge25519_unpack_negative_vartime(&A, pk)) return -1; @@ -1646,6 +1726,40 @@ ed25519_sign_open_CXX(const byte *m, size_t mlen, const byte pk[32], const byte return ed25519_verify(RS, checkR, 32) ? 0 : -1; } +int +ed25519_sign_open_CXX(std::istream& stream, const byte pk[32], const byte RS[64]) { + + using namespace CryptoPP::Donna::Ed25519; + + ALIGN(16) ge25519 R, A; + hash_512bits hash; + bignum256modm hram, S; + byte checkR[32]; + + if ((RS[63] & 224) || !ge25519_unpack_negative_vartime(&A, pk)) + return -1; + + /* hram = H(R,A,m) */ + ed25519_hram(hash, RS, pk, stream); + expand256_modm(hram, hash, 64); + + /* S */ + expand256_modm(S, RS + 32, 32); + + /* SB - H(R,A,m)A */ + ge25519_double_scalarmult_vartime(&R, &A, hram, S); + ge25519_pack(checkR, &R); + + /* check that R = SB - H(R,A,m)A */ + return ed25519_verify(RS, checkR, 32) ? 0 : -1; +} + +int +ed25519_sign_open(std::istream& stream, const byte publicKey[32], const byte signature[64]) +{ + return ed25519_sign_open_CXX(stream, publicKey, signature); +} + int ed25519_sign_open(const byte *message, size_t messageLength, const byte publicKey[32], const byte signature[64]) { diff --git a/poly1305.cpp b/poly1305.cpp index b25c8ad5..e22cb7f5 100644 --- a/poly1305.cpp +++ b/poly1305.cpp @@ -209,7 +209,7 @@ void Poly1305_Base::UncheckedSetKey(const byte *key, unsigned int length, con if (params.GetValue(Name::IV(), t) && t.begin() && t.size()) { CRYPTOPP_ASSERT(t.size() == m_nk.size()); - Resynchronize(t.begin(), t.size()); + Resynchronize(t.begin(), (int)t.size()); } Restart(); diff --git a/validat7.cpp b/validat7.cpp index db9c31ea..ad8bf755 100644 --- a/validat7.cpp +++ b/validat7.cpp @@ -537,6 +537,59 @@ bool TestEd25519() else std::cout << "FAILED:"; std::cout << " " << SIGN_COUNT << " verifications" << std::endl; + + // Test signature verification using streams + for (unsigned int i = 0; i(GetPrivateKey()); + int ret = Donna::ed25519_sign(stream, pk.GetPrivateKeyBytePtr(), pk.GetPublicKeyBytePtr(), signature); + CRYPTOPP_ASSERT(ret == 0); + + return ret == 0 ? SIGNATURE_LENGTH : 0; +} + // ******************** ed25519 Verifier ************************* // bool ed25519PublicKey::GetVoidValue(const char *name, const std::type_info &valueType, void *pValue) const @@ -856,4 +867,14 @@ bool ed25519Verifier::VerifyAndRestart(PK_MessageAccumulator &messageAccumulator return ret == 0; } +bool ed25519Verifier::VerifyStream(std::istream& stream, const byte *signature, size_t signatureLen) const +{ + CRYPTOPP_ASSERT(signatureLen == SIGNATURE_LENGTH); + + const ed25519PublicKey& pk = static_cast(GetPublicKey()); + int ret = Donna::ed25519_sign_open(stream, pk.GetPublicKeyBytePtr(), signature); + + return ret == 0; +} + NAMESPACE_END // CryptoPP diff --git a/xed25519.h b/xed25519.h index 363d7d7c..f581a586 100644 --- a/xed25519.h +++ b/xed25519.h @@ -574,6 +574,18 @@ struct ed25519Signer : public PK_Signer size_t SignAndRestart(RandomNumberGenerator &rng, PK_MessageAccumulator &messageAccumulator, byte *signature, bool restart) const; + /// \brief Sign a stream + /// \param rng a RandomNumberGenerator derived class + /// \param stream an std::istream derived class + /// \param signature a block of bytes for the signature + /// \return actual signature length + /// \details SignStream() handles large streams. It was added for signing and verifying + /// files that are too large for a memory allocation. + /// \details ed25519 is a determinsitic signature scheme. IsProbabilistic() + /// returns false and the random number generator can be NullRNG(). + /// \pre COUNTOF(signature) == MaxSignatureLength() + size_t SignStream (RandomNumberGenerator &rng, std::istream& stream, byte *signature) const; + protected: ed25519PrivateKey m_key; }; @@ -742,6 +754,15 @@ struct ed25519Verifier : public PK_Verifier bool VerifyAndRestart(PK_MessageAccumulator &messageAccumulator) const; + /// \brief Check whether input signature is a valid signature for input message + /// \param stream an std::istream derived class + /// \param signature a pointer to the signature over the message + /// \param signatureLen the size of the signature + /// \return true if the signature is valid, false otherwise + /// \details VerifyStream() handles large streams. It was added for signing and verifying + /// files that are too large for a memory allocation. + bool VerifyStream(std::istream& stream, const byte *signature, size_t signatureLen) const; + DecodingResult RecoverAndRestart(byte *recoveredMessage, PK_MessageAccumulator &messageAccumulator) const { CRYPTOPP_UNUSED(recoveredMessage); CRYPTOPP_UNUSED(messageAccumulator); throw NotImplemented("ed25519Verifier: this object does not support recoverable messages");