diff --git a/.gitmodules b/.gitmodules index f724081..7026083 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "vendor/monocypher"] path = vendor/monocypher url = https://github.com/snej/Monocypher.git +[submodule "vendor/BLAKE3"] + path = vendor/BLAKE3 + url = https://github.com/BLAKE3-team/BLAKE3.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 44a1ed3..af68445 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,16 @@ cmake_minimum_required(VERSION 3.16) -project(MonocypherCpp) +project(MonocypherCpp + VERSION 1.2 + DESCRIPTION "C++ bindings for Monocypher crypto library, and more" + LANGUAGES C CXX +) + +option(MONOCYPHER_ENABLE_BLAKE3 "Adds the Blake3 digest algorithm" ON) +if (MONOCYPHER_ENABLE_BLAKE3) + add_subdirectory(vendor/BLAKE3/c) +endif() set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -19,6 +28,9 @@ include_directories( ) +#### MAIN LIBRARY + + add_library( MonocypherCpp STATIC src/Monocypher.cc src/Monocypher-ed25519.cc @@ -32,13 +44,33 @@ if (NOT MSVC) ) endif() +if (MONOCYPHER_ENABLE_BLAKE3) + target_sources( MonocypherCpp PRIVATE + src/Monocypher+blake3.cc + ) + target_include_directories( MonocypherCpp PRIVATE + vendor/BLAKE3/c/ + ) + target_link_libraries( MonocypherCpp INTERFACE + blake3 + ) +endif() + + #### TESTS + add_executable( MonocypherCppTests tests/MonocypherCppTests.cc tests/tests_main.cc ) +if (MONOCYPHER_ENABLE_BLAKE3) + target_sources( MonocypherCppTests PRIVATE + tests/Test_Blake3.cc + ) +endif() + target_include_directories( MonocypherCppTests PRIVATE "vendor/catch2/" ) diff --git a/include/monocypher/ext/blake3.hh b/include/monocypher/ext/blake3.hh new file mode 100644 index 0000000..6d89fa5 --- /dev/null +++ b/include/monocypher/ext/blake3.hh @@ -0,0 +1,88 @@ +// +// monocypher/ext/blake3.hh +// +// Monocypher-Cpp: Unofficial idiomatic C++17 wrapper for Monocypher +// +// +// Copyright (c) 2024 Jens Alfke. All rights reserved. +// +// --- Standard 2-clause BSD licence follows --- +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once +#include "../hash.hh" +#include + +namespace monocypher::ext { + + struct Blake3Base { + static constexpr const char* name = "BLAKE3"; + using context = byte_array<1912>; + static void init_fn (context *ctx); + static void update_fn(context *ctx, const uint8_t *message, size_t message_size); + protected: + static void final_fn(context *ctx, uint8_t *hash, size_t hash_size); + static void create_fn(uint8_t *hash, size_t hash_size, const uint8_t *msg, size_t msg_size); + static void init_mac_fn(context *ctx, const uint8_t *key, size_t key_size); + static void create_mac_fn(uint8_t *hash, size_t hash_size, + const uint8_t *key, size_t key_size, + const uint8_t *message, size_t message_size); + }; + + /// The BLAKE3 digest algorithm, for use as the template parameter to `hash`. + /// + /// @note This functionality is NOT part of Monocypher itself. + /// It uses the C reference implementation, + template + struct Blake3 : public Blake3Base { + static constexpr size_t hash_size = Size; + + static void final_fn (context *ctx, uint8_t hash[Size]) { + Blake3Base::final_fn(ctx, hash, hash_size); + } + static void create_fn(uint8_t hash[Size], const uint8_t *message, size_t message_size) { + Blake3Base::create_fn(hash, hash_size, message, message_size); + } + + /// Note: BLAKE3's HMAC algorithm requires the key to be exactly 32 bytes. + struct mac { + using context = Blake3::context; + + static void create_fn(uint8_t *hash, const uint8_t *key, size_t key_size, + const uint8_t *message, size_t message_size) + { + Blake3Base::create_mac_fn(hash, hash_size, key, key_size, message, message_size); + } + static void init_fn(context *ctx, const uint8_t *key, size_t key_size) { + Blake3Base::init_mac_fn(ctx, key, key_size); + } + static constexpr auto update_fn = Blake3::update_fn; + static constexpr auto final_fn = Blake3::final_fn; + }; + }; + + /// Blake3 hash class with default 256-bit (32-byte) hash size.. + using blake3 = hash>; +} diff --git a/include/monocypher/ext/ed25519.hh b/include/monocypher/ext/ed25519.hh index 9baa0e8..5007c90 100644 --- a/include/monocypher/ext/ed25519.hh +++ b/include/monocypher/ext/ed25519.hh @@ -64,26 +64,4 @@ namespace monocypher { using key_pair = monocypher::key_pair; }; - - /// SHA-512 algorithm, for use as the template parameter to `hash`. - struct SHA512 { - static constexpr const char* name = "SHA-512"; - static constexpr size_t hash_size = 512 / 8; - - using context = c::crypto_sha512_ctx; - static constexpr auto create_fn = c::crypto_sha512; - static constexpr auto init_fn = c::crypto_sha512_init; - static constexpr auto update_fn = c::crypto_sha512_update; - static constexpr auto final_fn = c::crypto_sha512_final; - - struct mac { - using context = c::crypto_sha512_hmac_ctx; - static constexpr auto create_fn = c::crypto_sha512_hmac; - static constexpr auto init_fn = c::crypto_sha512_hmac_init; - static constexpr auto update_fn = c::crypto_sha512_hmac_update; - static constexpr auto final_fn = c::crypto_sha512_hmac_final; - }; - }; - - using sha512 = hash; } diff --git a/include/monocypher/ext/sha512.hh b/include/monocypher/ext/sha512.hh new file mode 100644 index 0000000..eddbc6d --- /dev/null +++ b/include/monocypher/ext/sha512.hh @@ -0,0 +1,64 @@ +// +// monocypher/ext/sha512.hh +// +// Monocypher-Cpp: Unofficial idiomatic C++17 wrapper for Monocypher +// +// +// Copyright (c) 2022 Jens Alfke. All rights reserved. +// +// --- Standard 2-clause BSD licence follows --- +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once +#include "../hash.hh" +#include "../../../vendor/monocypher/src/optional/monocypher-ed25519.h" + +namespace monocypher { + + // This functionality is an extension that comes with Monocypher. + // It's not considered part of the core API, but is provided for compatibility. + + /// SHA-512 algorithm, for use as the template parameter to `hash`. + struct SHA512 { + static constexpr const char* name = "SHA-512"; + static constexpr size_t hash_size = 512 / 8; + + using context = c::crypto_sha512_ctx; + static constexpr auto create_fn = c::crypto_sha512; + static constexpr auto init_fn = c::crypto_sha512_init; + static constexpr auto update_fn = c::crypto_sha512_update; + static constexpr auto final_fn = c::crypto_sha512_final; + + struct mac { + using context = c::crypto_sha512_hmac_ctx; + static constexpr auto create_fn = c::crypto_sha512_hmac; + static constexpr auto init_fn = c::crypto_sha512_hmac_init; + static constexpr auto update_fn = c::crypto_sha512_hmac_update; + static constexpr auto final_fn = c::crypto_sha512_hmac_final; + }; + }; + + using sha512 = hash; +} diff --git a/include/monocypher/hash.hh b/include/monocypher/hash.hh index 9d722ed..883c791 100644 --- a/include/monocypher/hash.hh +++ b/include/monocypher/hash.hh @@ -38,8 +38,11 @@ namespace monocypher { using namespace MONOCYPHER_CPP_NAMESPACE; /// Cryptographic hash class, templated by algorithm and size. - /// The only `Algorithm` currently available is `Blake2b`. - /// The `Size` is in bytes and must be between 1 and 64. Sizes below 32 are not recommended. + /// The `Size` is in bytes and must be between 1 and 64. + /// + /// Monocypher provides algorithms `Blake2b` (below) and `SHA512` (in ext/sha512.hh). + /// Extra algorithms SHA256 (ext/sha256.hh) and Blake3 (ext/blake3.hh) are implemented by + /// other codebases, not Monocypher. template class hash : public byte_array { public: @@ -49,18 +52,20 @@ namespace monocypher { explicit hash(const std::array &a) :byte_array(a) { } hash(const void *data, size_t size) :byte_array(data, size) { } - /// Returns the Blake2b hash of a message. + /// Returns the hash of a message. static hash create(const void *message, size_t message_size) noexcept { hash result; HashAlgorithm::create_fn(result.data(), u8(message), message_size); return result; } + /// Returns the hash of a message. static hash create(input_bytes message) noexcept { return create(message.data, message.size); } /// Returns the hash of a message and a secret key, for use as a MAC. + /// @warning Some algorithms only work with specific key sizes. template static hash createMAC(const void *message, size_t message_size, const byte_array &key) noexcept { @@ -87,11 +92,12 @@ namespace monocypher { return *this; } + /// Hashes more data. _builder& update(input_bytes message) { return update(message.data, message.size); } - /// Returns the final hash of all the data passed to `update`. + /// Returns the final hash of all the data. [[nodiscard]] hash final() { hash result; @@ -106,7 +112,7 @@ namespace monocypher { /// Incrementally constructs a hash. class builder : public _builder { public: - /// Constructs a Blake2b builder. + /// Constructs a hash builder. /// Call `update` one or more times to hash data, then `final` to get the hash. builder() { HashAlgorithm::init_fn(&this->_ctx); @@ -116,9 +122,10 @@ namespace monocypher { /// Incrementally constructs a MAC. class mac_builder : public _builder { public: - /// Constructs a Blake2b builder with a secret key, for creating MACs. + /// Constructs a hash builder with a secret key, for creating MACs. /// Call `update` one or more times to hash data, then `final` to get the hash. - template + /// @warning Some algorithms only work with specific key sizes. + template mac_builder(const byte_array &key) { HashAlgorithm::mac::init_fn(&this->_ctx, key.data(), key.size()); } @@ -166,7 +173,4 @@ namespace monocypher { /// Blake2b-32 hash class. using blake2b32 = hash>; - // Note: See Monocypher-ed25519.hh for SHA-512, and Monocypher+sha256.hh for SHA-256. - - } diff --git a/src/Monocypher+blake3.cc b/src/Monocypher+blake3.cc new file mode 100644 index 0000000..83e837c --- /dev/null +++ b/src/Monocypher+blake3.cc @@ -0,0 +1,84 @@ +// +// monocypher/Monocypher+blake3.cc +// +// Monocypher-Cpp: Unofficial idiomatic C++17 wrapper for Monocypher +// +// +// Copyright (c) 2024 Jens Alfke. All rights reserved. +// +// --- Standard 2-clause BSD licence follows --- +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "monocypher/ext/blake3.hh" +#include "blake3.h" +#include + +namespace monocypher::ext { + using namespace std; + + // Verify that the size of `context` matches the actual hasher's size: + static_assert(sizeof(Blake3Base::context) == sizeof(blake3_hasher)); + + static blake3_hasher* hasher(Blake3Base::context *ctx) { + return reinterpret_cast(ctx); + } + + void Blake3Base::init_fn(context *ctx) { + blake3_hasher_init(hasher(ctx)); + } + + void Blake3Base::update_fn(context *ctx, const uint8_t *message, size_t message_size) { + blake3_hasher_update(hasher(ctx), message, message_size); + } + + void Blake3Base::final_fn(context *ctx, uint8_t* hash, size_t hash_size) { + blake3_hasher_finalize(hasher(ctx), hash, hash_size); + } + + void Blake3Base::create_fn(uint8_t* hash, size_t hash_size, const uint8_t *message, size_t message_size) { + blake3_hasher ctx; + blake3_hasher_init(&ctx); + blake3_hasher_update(&ctx, message, message_size); + blake3_hasher_finalize(&ctx, hash, hash_size); + } + + void Blake3Base::init_mac_fn(context *ctx, const uint8_t *key, size_t key_size) { + if (key_size != 32) + throw std::invalid_argument("Blake3 MAC requires a 32-byte key"); + blake3_hasher_init_keyed(hasher(ctx), key); + } + + void Blake3Base::create_mac_fn(uint8_t *hash, size_t hash_size, + const uint8_t *key, size_t key_size, + const uint8_t *message, size_t message_size) { + if (key_size != 32) + throw std::invalid_argument("Blake3 MAC requires a 32-byte key"); + blake3_hasher ctx; + blake3_hasher_init_keyed(&ctx, key); + blake3_hasher_update(&ctx, message, message_size); + blake3_hasher_finalize(&ctx, hash, hash_size); + } + +} diff --git a/tests/MonocypherCppTests.cc b/tests/MonocypherCppTests.cc index 51bf273..758f52a 100644 --- a/tests/MonocypherCppTests.cc +++ b/tests/MonocypherCppTests.cc @@ -9,6 +9,7 @@ #include "Monocypher.hh" #include "monocypher/ext/ed25519.hh" #include "monocypher/ext/sha256.hh" +#include "monocypher/ext/sha512.hh" #include "monocypher/ext/xsalsa20.hh" #include #include // for `tie` diff --git a/tests/Test_Blake3.cc b/tests/Test_Blake3.cc new file mode 100644 index 0000000..2b1e760 --- /dev/null +++ b/tests/Test_Blake3.cc @@ -0,0 +1,61 @@ +// +// Created by Jens Alfke on 5/28/24. +// + +#include "hexString.hh" +#include "Monocypher.hh" +#include "monocypher/ext/blake3.hh" +#include +#include // for `tie` + +#include "catch.hpp" + +using namespace std; +using namespace monocypher; + +// Digest and HMAC of empty string taken from the official test vectors, +// https://github.com/BLAKE3-team/BLAKE3/blob/master/test_vectors/test_vectors.json + +TEST_CASE("Blake3") { + auto h1 = ext::blake3::create(""sv); + string str1 = hexString(h1); + cout << str1 << "\n"; + CHECK(str1 == "AF1349B9 F5F9A1A6 A0404DEA 36DCC949 9BCB25C9 ADC112B7 CC9A93CA E41F3262"); + + h1 = ext::blake3::create("hello world"sv); + str1 = hexString(h1); + cout << str1 << "\n"; + CHECK(str1 == "D74981EF A70A0C88 0B8D8C19 85D075DB CBF679B9 9A5F9914 E5AAF96B 831A9E24"); + + ext::blake3::builder b; + b.update("hello"sv).update(" "sv).update("world"sv); + ext::blake3 h2 = b.final(); + + string str2 = hexString(h2); + cout << str2 << "\n"; + CHECK(str2 == str1); + CHECK(h2 == h1); + + // (No HMAC support in Blake3 yet) +} + +TEST_CASE("Blake3 HMAC") { + secret_byte_array<32> key("whats the Elvish word for friend", 32); + + auto mac = ext::blake3::createMAC(""sv, key); + string macStr = hexString(mac); + cout << "HMAC = " << macStr << endl; + CHECK(macStr == "92B2B756 04ED3C76 1F9D6F62 392C8A92 27AD0EA3 F09573E7 83F1498A 4ED60D26"); + + mac = ext::blake3::createMAC("hello world"sv, key); + macStr = hexString(mac); + cout << "HMAC = " << macStr << endl; + CHECK(macStr == "546A11CF 08472EE6 8FB83C3F 28AB2DC2 1EF620A6 F03A64B4 29E4BAC4 E454D2B2"); + + typename ext::blake3::mac_builder hm(key); + hm.update("hello"sv).update(" "sv).update("world"sv); + ext::blake3 mac2 = hm.final(); + cout << "HMAC = " << hexString(mac2) << endl; + CHECK(mac2 == mac); + +} diff --git a/vendor/BLAKE3 b/vendor/BLAKE3 new file mode 160000 index 0000000..0816bad --- /dev/null +++ b/vendor/BLAKE3 @@ -0,0 +1 @@ +Subproject commit 0816badf3ada3ec48e712dd4f4cbc2cd60828278