diff --git a/src/crypto/poly1305.h b/src/crypto/poly1305.h index 39b69e1cd4f..080543ffeee 100644 --- a/src/crypto/poly1305.h +++ b/src/crypto/poly1305.h @@ -5,6 +5,9 @@ #ifndef BITCOIN_CRYPTO_POLY1305_H #define BITCOIN_CRYPTO_POLY1305_H +#include + +#include #include #include @@ -32,6 +35,40 @@ void poly1305_finish(poly1305_context *st, unsigned char mac[16]) noexcept; } // namespace poly1305_donna +/** C++ wrapper with std::byte Span interface around poly1305_donna code. */ +class Poly1305 +{ + poly1305_donna::poly1305_context m_ctx; + +public: + /** Length of the output produced by Finalize(). */ + static constexpr unsigned TAGLEN = POLY1305_TAGLEN; + + /** Length of the keys expected by the constructor. */ + static constexpr unsigned KEYLEN = POLY1305_KEYLEN; + + /** Construct a Poly1305 object with a given 32-byte key. */ + Poly1305(Span key) noexcept + { + assert(key.size() == KEYLEN); + poly1305_donna::poly1305_init(&m_ctx, UCharCast(key.data())); + } + + /** Process message bytes. */ + Poly1305& Update(Span msg) noexcept + { + poly1305_donna::poly1305_update(&m_ctx, UCharCast(msg.data()), msg.size()); + return *this; + } + + /** Write authentication tag to 16-byte out. */ + void Finalize(Span out) noexcept + { + assert(out.size() == TAGLEN); + poly1305_donna::poly1305_finish(&m_ctx, UCharCast(out.data())); + } +}; + void poly1305_auth(unsigned char out[POLY1305_TAGLEN], const unsigned char *m, size_t inlen, const unsigned char key[POLY1305_KEYLEN]); diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index b29df832b4f..85494e1b208 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -191,6 +191,22 @@ static void TestPoly1305(const std::string &hexmessage, const std::string &hexke tagres.resize(POLY1305_TAGLEN); poly1305_auth(tagres.data(), m.data(), m.size(), key.data()); BOOST_CHECK(tag == tagres); + + // Test incremental interface + for (int splits = 0; splits < 10; ++splits) { + for (int iter = 0; iter < 10; ++iter) { + auto data = MakeByteSpan(m); + Poly1305 poly1305{MakeByteSpan(key)}; + for (int chunk = 0; chunk < splits; ++chunk) { + size_t now = InsecureRandRange(data.size() + 1); + poly1305.Update(data.first(now)); + data = data.subspan(now); + } + tagres.assign(POLY1305_TAGLEN, 0); + poly1305.Update(data).Finalize(MakeWritableByteSpan(tagres)); + BOOST_CHECK(tag == tagres); + } + } } static void TestHKDF_SHA256_32(const std::string &ikm_hex, const std::string &salt_hex, const std::string &info_hex, const std::string &okm_check_hex) { diff --git a/src/test/fuzz/crypto_poly1305.cpp b/src/test/fuzz/crypto_poly1305.cpp index ac555ed68ce..94a7ed06e91 100644 --- a/src/test/fuzz/crypto_poly1305.cpp +++ b/src/test/fuzz/crypto_poly1305.cpp @@ -20,3 +20,34 @@ FUZZ_TARGET(crypto_poly1305) std::vector tag_out(POLY1305_TAGLEN); poly1305_auth(tag_out.data(), in.data(), in.size(), key.data()); } + + +FUZZ_TARGET(crypto_poly1305_split) +{ + FuzzedDataProvider provider{buffer.data(), buffer.size()}; + + // Read key and instantiate two Poly1305 objects with it. + auto key = provider.ConsumeBytes(Poly1305::KEYLEN); + key.resize(Poly1305::KEYLEN); + Poly1305 poly_full{key}, poly_split{key}; + + // Vector that holds all bytes processed so far. + std::vector total_input; + + // Process input in pieces. + LIMITED_WHILE(provider.remaining_bytes(), 100) { + auto in = provider.ConsumeRandomLengthString(); + poly_split.Update(MakeByteSpan(in)); + // Update total_input to match what was processed. + total_input.insert(total_input.end(), MakeByteSpan(in).begin(), MakeByteSpan(in).end()); + } + + // Process entire input at once. + poly_full.Update(total_input); + + // Verify both agree. + std::array tag_split, tag_full; + poly_split.Finalize(tag_split); + poly_full.Finalize(tag_full); + assert(tag_full == tag_split); +}