#ifndef HASH_MD6_2019_04_23_H #define HASH_MD6_2019_04_23_H #include #include #include #include #include #include #include #include // See http://groups.csail.mit.edu/cis/md6/ #if(__cplusplus >= 201703L) namespace al::crypto::stream::hash { #else namespace al { namespace crypto { namespace stream { namespace hash { #endif template> class hash_md6 // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) : public al::crypto::stream::hash::hash_md_base { private: using base_class_type = al::crypto::stream::hash::hash_md_base; // size of compression input block, in words static constexpr std::size_t md6_n = 89U; // Size of compression output, in words a c-word block is also called a "chunk" static constexpr std::size_t md6_c = std::tuple_size::value; static constexpr std::size_t md6_w = 64U; // These five values give lengths of the components of // compression input block. They should sum to md6_n. static constexpr std::size_t md6_q = 15U; // Q words in compression block (>=0) static constexpr std::size_t md6_k = 8U; // key words per compression block (>=0) static constexpr std::size_t md6_u = (64U / md6_w); // words for unique node ID (0 or 64/w) static constexpr std::size_t md6_v = (64U / md6_w); // words for control word (0 or 64/w) static constexpr std::size_t md6_b = 64U; // data words per compression block (>0) static_assert(md6_n == static_cast(md6_q + md6_k + md6_u + md6_v + md6_b), "Error: Bolck configuration seems to be incorrect. Please check parameters."); static constexpr std::size_t md6_default_L = 64U; static constexpr std::size_t md6_default_r = 40U + (ResultBitCount / 4U); struct state_block_type { using message_buffer_type = std::array; state_block_type() = default; state_block_type(const state_block_type& other) = default; state_block_type(state_block_type&& other) noexcept = default; ~state_block_type() = default; auto operator=(const state_block_type& other) -> state_block_type& // NOLINT(cert-oop54-cpp) { if(this != &other) { my_message_buffer = other.my_message_buffer; my_message_index = other.my_message_index; my_i_for_level = other.my_i_for_level; } return *this; } auto operator=(state_block_type&& other) noexcept -> state_block_type& { my_message_buffer = static_cast(other.my_message_buffer); my_message_index = other.my_message_index; my_i_for_level = other.my_i_for_level; return *this; } // stack of 29 64-word partial blocks waiting to be // completed and compressed. // my_message_buffer[1] is for compressing text data (input); // my_message_buffer[my_ell] corresponds to node at level my_ell in the tree. message_buffer_type my_message_buffer { }; // NOLINT(misc-non-private-member-variables-in-classes) // my_message_index[my_ell] = // number of bytes already placed in my_message_buffer[my_ell] // for 1 <= my_ell < max_stack_height, // with (0 <= bits[my_ell] <= b * w) std::size_t my_message_index { }; // NOLINT(misc-non-private-member-variables-in-classes) // i_for_level[my_ell] = // index of the node my_message_buffer[my_ell] on this level (0,1,...), // when it is output std::size_t my_i_for_level { }; // NOLINT(misc-non-private-member-variables-in-classes) }; static constexpr std::size_t md6_max_stack_height = 29U; using state_block_container_type = std::array; struct md6_state { // max_stack_height determines the maximum number of bits that // can be processed by this implementation (with default L). static constexpr std::size_t md6_max_stack_height = 29U; // md6 mode specification parameter. 0 <= L <= 255 // L == 0 means purely sequential (Merkle-Damgaard) // L >= 29 means purely tree-based // Default is md6_default_L = 64 (hierarchical) static constexpr auto my_L() -> std::size_t { return md6_default_L; } // Number of rounds, with (0 <= r <= 255). static constexpr auto my_r() -> std::size_t { return md6_default_r; } // TBD: We would like to consider leaving this constructor blank. // In other words we would prefer a constructor that does nothing // such as md6_state { }. // TBD: But when we do this (empty contstructor) we find that // not all the md6 tests pass. Why? It should be simple to find // out what is not being properly initialized. md6_state() = default; md6_state(const md6_state&) = delete; md6_state(md6_state&& other) = delete; ~md6_state() = default; auto operator=(const md6_state&) -> md6_state& = delete; auto operator=(md6_state&& other) -> md6_state& = delete; state_block_container_type state_blocks; // NOLINT(misc-non-private-member-variables-in-classes) }; public: hash_md6() : my_a(4200U) { } hash_md6(const hash_md6& other) : base_class_type(other), my_state (other.my_state), my_state_blocks_top(other.my_state_blocks_top), my_ell (other.my_ell), my_do_finalize_flag(other.my_do_finalize_flag), my_a (other.my_a) { } hash_md6(hash_md6&& other) noexcept : base_class_type(other), my_state (other.my_state), my_state_blocks_top(other.my_state_blocks_top), my_ell (other.my_ell), my_do_finalize_flag(other.my_do_finalize_flag), my_a (other.my_a) { } ~hash_md6() override = default; auto operator=(const hash_md6& other) -> hash_md6& // NOLINT(cert-oop54-cpp) { static_cast(base_class_type::operator=(other)); if(this != &other) { my_state = other.my_state; my_state_blocks_top = other.my_state_blocks_top; my_ell = other.my_ell; my_do_finalize_flag = other.my_do_finalize_flag; my_a = other.my_a; } return *this; } auto initialize() -> void override { base_class_type::initialize(); // Perform the initialization of the md6. my_state_blocks_top = 1U; for(auto& sb : my_state.state_blocks) { sb.my_message_buffer.fill(0U); sb.my_message_index = 0U; sb.my_i_for_level = 0U; } my_ell = 0U; set_do_finalize_flag(false); } auto process_data(const std::uint8_t* message, const std::size_t count) -> void override { std::size_t process_index = 0U; std::size_t process_chunk_size = 0U; while(process_index < count) { if(base_class_type::message_index == base_class_type::message_buffer_static_size) { my_ell = 1; set_do_finalize_flag(false); this->transform(); // Note: the transform() method is responsible // for setting message_index back to 0. // In this case, manually reset message_index to 0. base_class_type::message_index = 0U; } base_class_type::message_index += process_chunk_size; base_class_type::message_length_total += process_chunk_size; process_index += process_chunk_size; process_chunk_size = (std::min)((count - process_index), (base_class_type::message_buffer_static_size - base_class_type::message_index)); my_state.state_blocks[1U].my_message_index += process_chunk_size; std::copy(message + process_index, // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) message + (process_index + process_chunk_size), // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) base_class_type::message_buffer.begin() + static_cast(base_class_type::message_index)); } } auto finalize() -> void override { if(this->my_state_blocks_top == 1U) { my_ell = 1; } else { auto it = std::find_if(my_state.state_blocks.cbegin() + 1U, my_state.state_blocks.cend(), [](const state_block_type& state_block) -> bool { return (state_block.my_message_index > 0U); }); const std::ptrdiff_t distance_from_1_to_top = (it != my_state.state_blocks.cend()) ? std::distance(my_state.state_blocks.cbegin() + 1U, it) : 0; my_ell = 1U + static_cast(distance_from_1_to_top); } // Finish any processing that still needs to be done. set_do_finalize_flag(true); transform(); } auto get_result() const -> typename base_class_type::result_type override { constexpr std::size_t full_or_partial_bytes = ResultBitCount / 8U; constexpr std::size_t transform_context_size = std::tuple_size::value; constexpr std::size_t transform_context_offset = transform_context_size - (full_or_partial_bytes / 8U); static_assert(transform_context_size == 16U, "Error in understanding transform context size"); typename base_class_type::result_type result; // Extract the hash result. base_class_type::copy_transform_context_reverse(result.data(), transform_context_offset); return result; } private: using allocator_for_transform_memory_void_pointer_type = AllocatorForTransformMemoryType; using transform_memory_container_type = util::dynamic_array::template rebind_alloc>; md6_state my_state { }; std::size_t my_state_blocks_top { }; std::size_t my_ell { }; bool my_do_finalize_flag { }; transform_memory_container_type my_a; AL_CRYPTO_NODISCARD auto get_do_finalize_flag() const -> bool { return my_do_finalize_flag; } void set_do_finalize_flag(const bool is_final) { my_do_finalize_flag = is_final; } AL_CRYPTO_NODISCARD auto is_final_compression() const -> bool { return (get_do_finalize_flag() && (my_ell == my_state_blocks_top)); } AL_CRYPTO_NODISCARD auto get_transform_index() const -> std::uint_fast8_t { // Returns 0, 1, 2. // 0 Do not do any transformation. // 1 Do the first part of the transfomation, but not the second final compress // 2 Do both the first part of the transformation as well as the final compress. std::uint_fast8_t result = 0U; const bool message_buffer_is_full = (my_state.state_blocks[my_ell].my_message_index == base_class_type::message_buffer_static_size); const bool conditions_for_part_one_are_true = (get_do_finalize_flag() || message_buffer_is_full); const bool conditions_for_part_two_are_true = (conditions_for_part_one_are_true && (!is_final_compression())); if(conditions_for_part_two_are_true) { result = 2U; } else if(conditions_for_part_one_are_true) { result = 1U; } else { result = 0U; } return result; } template static void md6_compress_rotation(const std::size_t i, std::uint64_t* A, const std::uint64_t& S) { constexpr std::size_t t0 = 17U; // index for linear feedback constexpr std::size_t t1 = 18U; // index for first input to first and constexpr std::size_t t2 = 21U; // index for second input to first and constexpr std::size_t t3 = 31U; // index for first input to second and constexpr std::size_t t4 = 67U; // index for second input to second and constexpr std::size_t t5 = 89U; // last tap std::uint64_t x = S; x ^= A[(i + MyStep) - t5]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) end-around feedback x ^= A[(i + MyStep) - t0]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) linear feedback x ^= (A[(i + MyStep) - t1] & A[(i + MyStep) - t2]); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) first quadratic term x ^= (A[(i + MyStep) - t3] & A[(i + MyStep) - t4]); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) second quadratic term x ^= (x >> MyRs); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) right-shift A[i + MyStep] = x ^ (x << MyLs); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) } void transform_part01_compress_normal() { // Make a copy of my_i_for_level at level my_ell. const std::size_t my_i_for_level_ell = my_state.state_blocks[my_ell].my_i_for_level; // Compress the block at this level. // The result is stored in my_state.C. // compress block at level my_ell, and store the // c-word result in the transform context. // Set the number of pad bits. const std::size_t p = (md6_b * md6_w) - (my_state.state_blocks[my_ell].my_message_index * 8U); // Declare and initialize 15 constant 64-bit words. constexpr std::array Q = {{ 0x7311C2812425CFA0ULL, 0x6432286434AAC8E7ULL, 0xB60450E9EF68B7C1ULL, 0xE8FB23908D9F06F1ULL, 0xDD2E76CBA691E5BFULL, 0x0CD0D63B2C30BC41ULL, 0x1F8CCF6823058F8AULL, 0x54E5ED5B88E3775DULL, 0x4AD12AAE0A6D6031ULL, 0x3E7F16BB88222E0DULL, 0x8AF8671D3FB50C2CULL, 0x995AD1178BD25C31ULL, 0xC878C1DD04C4B633ULL, 0x3B72066C7A1552ACULL, 0x0D6F3522631EFFCBULL, }}; const std::size_t local_r = my_state.my_r(); const std::size_t local_L = my_state.my_L(); // Perform md6 block compression using all the "standard" inputs. // Pack the components into N for compression. // Pack the data into the n-word array N before compression. typename transform_memory_container_type::pointer my_n = my_a.data(); // Q: Q in words 0 - 14. std::copy(Q.cbegin(), Q.cend(), my_n); std::size_t ni = md6_q; // K: key in words 15 - 22. std::fill(my_n + ni, my_n + (ni + Q.size()), 0U); ni += md6_k; // U: unique node ID in 23. // Make "unique nodeID" U based on level my_ell and position i // within level; place it at specified destination. // my_ell = integer level number, 1 <= my_ell <= ... // i = index within level, i = 0, 1, 2, ... const std::uint64_t U = ( (std::uint64_t(my_ell) << 56U) | std::uint64_t(my_state.state_blocks[my_ell].my_i_for_level)); my_n[ni] = U; ni += md6_u; // V: control word in 24 // r = number of rounds // L = mode parameter (maximum tree height) // z = 1 if (and only if) this is final compression operation // p = number of pad bits in a block to be compressed // keylen = number of bytes in key // d = desired hash output length const std::uint64_t V = ( ((static_cast(0)) << 60U) // reserved, width 4 bits | ((static_cast(local_r)) << 48U) // width 12 bits | ((static_cast(local_L)) << 40U) // width 8 bits | ((static_cast(is_final_compression() ? 1U : 0U)) << 36U) // width 4 bits | ((static_cast(p)) << 20U) // width 16 bits | ((0U) << 12U) // width 8 bits | (static_cast(ResultBitCount))); // width 12 bits my_n[ni] = V; ni += md6_v; if(my_ell == 1) { // Copy the message buffer (with uint8_t 8-bit data) into the destination // my_state.state_blocks[1U].my_message_buffer (with 64-bit elements). base_class_type::copy_message_buffer_reverse(my_state.state_blocks[1U].my_message_buffer.data()); base_class_type::message_buffer.fill(0U); base_class_type::message_index = 0U; } // B: Data words 25 - 88. std::copy(my_state.state_blocks[my_ell].my_message_buffer.cbegin(), my_state.state_blocks[my_ell].my_message_buffer.cend(), my_n + ni); std::uint64_t* local_a_pointer = my_a.data(); // Perform the md6 "main compression loop" on the array A. // This is where most of the computation occurs; it is the "heart" // of the md6 compression algorithm. constexpr auto S0 = static_cast(0x0123456789ABCDEFULL); std::uint64_t S = S0; std::size_t i = md6_n; for(std::size_t j = 0U; j < (local_r * md6_c); j += md6_c) { // Unroll loop c=16 times. (One "round" of computation.) // Shift amounts are embedded in macros RLnn. md6_compress_rotation<10U, 11U, 0U>(i, local_a_pointer, S); md6_compress_rotation< 5U, 24U, 1U>(i, local_a_pointer, S); md6_compress_rotation<13U, 9U, 2U>(i, local_a_pointer, S); md6_compress_rotation<10U, 16U, 3U>(i, local_a_pointer, S); md6_compress_rotation<11U, 15U, 4U>(i, local_a_pointer, S); md6_compress_rotation<12U, 9U, 5U>(i, local_a_pointer, S); md6_compress_rotation< 2U, 27U, 6U>(i, local_a_pointer, S); md6_compress_rotation< 7U, 15U, 7U>(i, local_a_pointer, S); md6_compress_rotation<14U, 6U, 8U>(i, local_a_pointer, S); md6_compress_rotation<15U, 2U, 9U>(i, local_a_pointer, S); md6_compress_rotation< 7U, 29U, 10U>(i, local_a_pointer, S); md6_compress_rotation<13U, 8U, 11U>(i, local_a_pointer, S); md6_compress_rotation<11U, 15U, 12U>(i, local_a_pointer, S); md6_compress_rotation< 7U, 5U, 13U>(i, local_a_pointer, S); md6_compress_rotation< 6U, 31U, 14U>(i, local_a_pointer, S); md6_compress_rotation<12U, 9U, 15U>(i, local_a_pointer, S); constexpr auto Smask = static_cast(0x7311C2812425CFA0ULL); // Advance the round constant S to the next round constant. S = (S << 1U) ^ (S >> (md6_w - 1U)) ^ (S & Smask); i += 16U; } // Copy the output into the transform context. std::copy(local_a_pointer + ((local_r * md6_c) + md6_n) - md6_c, // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) local_a_pointer + (local_r * md6_c) + md6_n, // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) base_class_type::transform_context.begin()); // Contains the key info. std::fill(local_a_pointer, local_a_pointer + ((local_r * md6_c) + md6_n), // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) 0U); // Clear the bytes and the counter for this level. my_state.state_blocks[my_ell] = state_block_type(); // Increment the index for this level. // TBD: Is this the place where we can eliminate the use of extra levels? my_state.state_blocks[my_ell].my_i_for_level = my_i_for_level_ell + 1; } void transform_part02_compress_nonfinal() { // Increment ell to the next higher level unless we are // already at the maximum (which is the top level). if(my_ell < my_state.my_L()) { ++my_ell; } // Increment the top if necessary. if(my_ell > my_state_blocks_top) { my_state_blocks_top = my_ell; } // Start sequential mode with IV=0 at that level if necessary // (All that is needed is to set bits[next_level] to c*w, // since the bits themselves are already zeroed, either // initially, or at the end of md6_compress_block.) // The copy the result onto the next level. std::copy(base_class_type::transform_context.cbegin(), base_class_type::transform_context.cend(), my_state.state_blocks[my_ell].my_message_buffer.begin() + (my_state.state_blocks[my_ell].my_message_index / 8U)); my_state.state_blocks[my_ell].my_message_index += (base_class_type::transform_context.size() * 8U); } auto transform_n() -> bool { // Do processing of level my_ell (and higher, if necessary) blocks. bool transform_again = false; std::uint_fast8_t local_transform_index = get_transform_index(); if(local_transform_index != 0U) { transform_part01_compress_normal(); if(local_transform_index == 2U) { transform_part02_compress_nonfinal(); transform_again = true; } } return transform_again; } auto transform() -> void override { std::uint_fast32_t n = 0U; // Actually, it should be theoretically possible to // calculate the number of cycles n ( less than 29?) // that are needed. // TBD: Verify the upper limit to this while loop. // It is possibly expected to be 29. Or is it 89. while((transform_n()) && (n < 29U)) { ++n; } } }; #if(__cplusplus >= 201703L) } // namespace al::crypto::stream::hash #else } // namespace hash } // namespace stream } // namespace crypto } // namespace al #endif #endif // HASH_MD6_2019_04_23_H