Created
January 9, 2026 05:18
-
-
Save jakergrossman/c12623a0422dd6a9bc91469fa6028959 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* SPDX-License-Identifier: CC0-1.0 */ | |
| #include <array> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <algorithm> | |
| #include <span> | |
| #include <bit> | |
| namespace sha2 | |
| { | |
| using std::size_t; | |
| using std::array; | |
| using std::span; | |
| using std::byte; | |
| using std::rotr; | |
| template < | |
| size_t DigestBits, | |
| size_t BlockBits, | |
| typename WordType, | |
| typename BitSizeType, | |
| const auto& RoundConstants, | |
| const array<WordType, 8> InitialState | |
| > | |
| class sha2 | |
| { | |
| public: | |
| static constexpr size_t digest_bits = DigestBits; | |
| static constexpr size_t digest_bytes = DigestBits/8; | |
| static constexpr size_t block_bits = BlockBits; | |
| static constexpr size_t block_bytes = BlockBits/8; | |
| static constexpr size_t rounds = RoundConstants.size(); | |
| static constexpr auto initial_hash = InitialState; | |
| static constexpr auto round_constants = RoundConstants; | |
| using word_t = WordType; | |
| using bitsize_t = BitSizeType; | |
| using hash_t = array<word_t, 8>; | |
| using digest_t = array<byte, digest_bytes>; | |
| constexpr sha2(void) = default; | |
| template<std::ranges::contiguous_range R> | |
| constexpr sha2(R&& data) | |
| requires std::same_as<std::ranges::range_value_t<R>, byte> | |
| { | |
| update(data); | |
| finish(); | |
| } | |
| template<std::ranges::contiguous_range R> | |
| void update(R&& data) | |
| requires std::same_as<std::ranges::range_value_t<R>, byte> | |
| { | |
| auto bytes = std::as_bytes(span{data}); | |
| size_t rem = bytes.size(); | |
| size_t pos = 0; | |
| while (rem > 0) | |
| { | |
| size_t free = blockbuf.size() - blockpos; | |
| size_t tocopy = std::min(free, rem); | |
| std::copy_n(bytes.data()+pos, tocopy, blockbuf.begin()+blockpos); | |
| blockpos += tocopy; | |
| if (blockpos == blockbuf.size()) | |
| { | |
| compress_block(blockbuf); | |
| blockpos = 0; | |
| } | |
| pos += tocopy; | |
| rem -= tocopy; | |
| } | |
| total_bits += bytes.size()*8; | |
| } | |
| digest_t finish(void) | |
| { | |
| /* always in-range, since a full block update() would have reset blockpos to 0 */ | |
| blockbuf[blockpos++] = byte(0x80); | |
| /* zero-pad/compress rest of block if not enough room for bit size */ | |
| size_t bits_pos = block_bytes - sizeof(bitsize_t); | |
| if (blockpos >= bits_pos) | |
| { | |
| std::fill(blockbuf.begin() + blockpos, blockbuf.end(), byte(0)); | |
| compress_block(blockbuf); | |
| blockpos = 0; | |
| } | |
| /* write bit count 'l' */ | |
| auto bitbytes = std::as_bytes(span{&total_bits, 1}); | |
| auto bitdest = blockbuf.begin() + bits_pos; | |
| /* zero pad up to bitsize position */ | |
| std::fill(blockbuf.begin() + blockpos, bitdest, byte(0)); | |
| /* fill bitsize (which is stored native-endian so may need swapping */ | |
| be_copy(bitbytes.begin(), bitbytes.end(), bitdest); | |
| compress_block(blockbuf); | |
| return digest(); | |
| } | |
| digest_t digest(void) const | |
| { | |
| digest_t digest; | |
| auto it = digest.begin(); | |
| for (size_t word_i = 0; word_i < digest.size() / sizeof(word_t); word_i++) | |
| { | |
| /* copy native-endian state into digest as big-endian word */ | |
| auto bytes = std::as_bytes(span{&state[word_i], 1}); | |
| be_copy(bytes.begin(), bytes.end(), it); | |
| it += sizeof(word_t); | |
| } | |
| return digest; | |
| } | |
| private: | |
| hash_t state = initial_hash; | |
| bitsize_t total_bits = 0; | |
| size_t blockpos = 0; | |
| alignas(word_t) array<byte, block_bytes> blockbuf; | |
| void compress_block(span<const byte, block_bytes> block) | |
| { | |
| array<word_t, 16> W; /* sliding window of W to care about */ | |
| hash_t work = state; | |
| for (size_t t = 0; t < rounds; t++) | |
| { | |
| constexpr size_t mask = W.size() - 1; /* limit indices into W */ | |
| size_t Widx = t & mask; | |
| if (t < 16) | |
| { | |
| /* convert big-endian block word to native-endian word */ | |
| auto target = std::as_writable_bytes(span{&W[Widx], 1}); | |
| auto start = block.begin() + t*sizeof(word_t); | |
| auto end = start + sizeof(word_t); | |
| be_copy(start, end, target.begin()); | |
| } | |
| else | |
| { | |
| word_t Wt_2 = W[(t-2) & mask]; | |
| word_t Wt_7 = W[(t-7) & mask]; | |
| word_t Wt_15 = W[(t-15) & mask]; | |
| word_t Wt_16 = W[(t-16) & mask]; | |
| W[Widx] = sigma1(Wt_2) + Wt_7 + sigma0(Wt_15) + Wt_16; | |
| } | |
| word_t Wt = W[Widx]; | |
| word_t Kt = round_constants[t]; | |
| auto& [a,b,c,d,e,f,g,h] = work; | |
| word_t T1 = h + big_sigma1(e) + ch(e, f, g) + Kt + Wt; | |
| word_t T2 = big_sigma0(a) + maj(a, b, c); | |
| h = g; | |
| g = f; | |
| f = e; | |
| e = d + T1; | |
| d = c; | |
| c = b; | |
| b = a; | |
| a = T1 + T2; | |
| } | |
| for (size_t i = 0; i < state.size(); i++) | |
| { | |
| state[i] += work[i]; | |
| } | |
| } | |
| static word_t ch(word_t x, word_t y, word_t z) | |
| { | |
| return (x & y) ^ (~x & z); // FIPS.180-4: (Equation 4.2, Equation 4.8) | |
| } | |
| static word_t maj(word_t x, word_t y, word_t z) | |
| { | |
| return (x & y) ^ (x & z) ^ (y & z); // FIPS.180-4: (Equation 4.3, Equation 4.9) | |
| } | |
| static word_t big_sigma0(word_t x) | |
| { | |
| return std::is_same_v<word_t, uint32_t> | |
| ? rotr(x, 2) ^ rotr(x,13) ^ rotr(x,22) // FIPS.180-4: (Equation 4.4) | |
| : rotr(x,28) ^ rotr(x,34) ^ rotr(x,39); // FIPS.180-4: (Equation 4.10) | |
| } | |
| static word_t big_sigma1(word_t x) | |
| { | |
| return std::is_same_v<word_t, uint32_t> | |
| ? rotr(x, 6) ^ rotr(x,11) ^ rotr(x,25) // FIPS.180-4: (Equation 4.5) | |
| : rotr(x,14) ^ rotr(x,18) ^ rotr(x,41); // FIPS.180-4: (Equation 4.11) | |
| } | |
| static word_t sigma0(word_t x) | |
| { | |
| return std::is_same_v<word_t, uint32_t> | |
| ? rotr(x,7) ^ rotr(x,18) ^ (x >> 3) // FIPS.180-4: (Equation 4.6) | |
| : rotr(x,1) ^ rotr(x, 8) ^ (x >> 7); // FIPS.180-4: (Equation 4.12) | |
| } | |
| static word_t sigma1(word_t x) | |
| { | |
| return std::is_same_v<word_t, uint32_t> | |
| ? rotr(x,17) ^ rotr(x,19) ^ (x >> 10) // FIPS.180-4: (Equation 4.7) | |
| : rotr(x,19) ^ rotr(x,61) ^ (x >> 6); // FIPS.180-4: (Equation 4.13) | |
| } | |
| template <typename InputIt, typename OutputIt> | |
| void be_copy(InputIt first, InputIt last, OutputIt dest) const | |
| { | |
| /* copy a range of bytes, reversing them on little-endian architectures */ | |
| if constexpr (std::endian::big == std::endian::native) | |
| { | |
| std::copy(first, last, dest); | |
| } | |
| else | |
| { | |
| std::reverse_copy(first, last, dest); | |
| } | |
| } | |
| }; | |
| static constexpr array<uint32_t, 8> sha256_H0 = { | |
| 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, | |
| 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19, | |
| }; | |
| static constexpr array<uint32_t, 8> sha224_H0 = { | |
| 0xC1059ED8, 0x367CD507, 0x3070DD17, 0xF70E5939, | |
| 0xFFC00B31, 0x68581511, 0x64F98FA7, 0xBEFA4FA4, | |
| }; | |
| static constexpr array<uint32_t, 64> sha256_K = { | |
| 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, | |
| 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, | |
| 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, | |
| 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, | |
| 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, | |
| 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, | |
| 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, | |
| 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2, | |
| }; | |
| static constexpr array<uint64_t, 8> sha512t224_H0 = { | |
| 0x8C3D37C819544DA2, 0x73E1996689DCD4D6, 0x1DFAB7AE32FF9C82, 0x679DD514582F9FCF, | |
| 0x0F6D2B697BD44DA8, 0x77E36F7304C48942, 0x3F9D85A86A1D36C8, 0x1112E6AD91D692A1, | |
| }; | |
| static constexpr array<uint64_t, 8> sha512t256_H0 = { | |
| 0x22312194FC2BF72C, 0x9F555FA3C84C64C2, 0x2393B86B6F53B151, 0x963877195940EABD, | |
| 0x96283EE2A88EFFE3, 0xBE5E1E2553863992, 0x2B0199FC2C85B8AA, 0x0EB72DDC81C52CA2, | |
| }; | |
| static constexpr array<uint64_t, 8> sha384_H0 = { | |
| 0xCBBB9D5DC1059ED8, 0x629A292A367CD507, 0x9159015A3070DD17, 0x152FECD8F70E5939, | |
| 0x67332667FFC00B31, 0x8EB44A8768581511, 0xDB0C2E0D64F98FA7, 0x47B5481DBEFA4FA4, | |
| }; | |
| static constexpr array<uint64_t, 8> sha512_H0 = { | |
| 0x6A09E667F3BCC908, 0xBB67AE8584CAA73B, 0x3C6EF372FE94F82B, 0xA54FF53A5F1D36F1, | |
| 0x510E527FADE682D1, 0x9B05688C2B3E6C1F, 0x1F83D9ABFB41BD6B, 0x5BE0CD19137E2179, | |
| }; | |
| static constexpr array<uint64_t, 80> sha512_K = { | |
| 0x428A2F98D728AE22, 0x7137449123EF65CD, 0xB5C0FBCFEC4D3B2F, 0xE9B5DBA58189DBBC, | |
| 0x3956C25BF348B538, 0x59F111F1B605D019, 0x923F82A4AF194F9B, 0xAB1C5ED5DA6D8118, | |
| 0xD807AA98A3030242, 0x12835B0145706FBE, 0x243185BE4EE4B28C, 0x550C7DC3D5FFB4E2, | |
| 0x72BE5D74F27B896F, 0x80DEB1FE3B1696B1, 0x9BDC06A725C71235, 0xC19BF174CF692694, | |
| 0xE49B69C19EF14AD2, 0xEFBE4786384F25E3, 0x0FC19DC68B8CD5B5, 0x240CA1CC77AC9C65, | |
| 0x2DE92C6F592B0275, 0x4A7484AA6EA6E483, 0x5CB0A9DCBD41FBD4, 0x76F988DA831153B5, | |
| 0x983E5152EE66DFAB, 0xA831C66D2DB43210, 0xB00327C898FB213F, 0xBF597FC7BEEF0EE4, | |
| 0xC6E00BF33DA88FC2, 0xD5A79147930AA725, 0x06CA6351E003826F, 0x142929670A0E6E70, | |
| 0x27B70A8546D22FFC, 0x2E1B21385C26C926, 0x4D2C6DFC5AC42AED, 0x53380D139D95B3DF, | |
| 0x650A73548BAF63DE, 0x766A0ABB3C77B2A8, 0x81C2C92E47EDAEE6, 0x92722C851482353B, | |
| 0xA2BFE8A14CF10364, 0xA81A664BBC423001, 0xC24B8B70D0F89791, 0xC76C51A30654BE30, | |
| 0xD192E819D6EF5218, 0xD69906245565A910, 0xF40E35855771202A, 0x106AA07032BBD1B8, | |
| 0x19A4C116B8D2D0C8, 0x1E376C085141AB53, 0x2748774CDF8EEB99, 0x34B0BCB5E19B48A8, | |
| 0x391C0CB3C5C95A63, 0x4ED8AA4AE3418ACB, 0x5B9CCA4F7763E373, 0x682E6FF3D6B2B8A3, | |
| 0x748F82EE5DEFB2FC, 0x78A5636F43172F60, 0x84C87814A1F0AB72, 0x8CC702081A6439EC, | |
| 0x90BEFFFA23631E28, 0xA4506CEBDE82BDE9, 0xBEF9A3F7B2C67915, 0xC67178F2E372532B, | |
| 0xCA273ECEEA26619C, 0xD186B8C721C0C207, 0xEADA7DD6CDE0EB1E, 0xF57D4F7FEE6ED178, | |
| 0x06F067AA72176FBA, 0x0A637DC5A2C898A6, 0x113F9804BEF90DAE, 0x1B710B35131C471B, | |
| 0x28DB77F523047D84, 0x32CAAB7B40C72493, 0x3C9EBE0A15C9BEBC, 0x431D67C49C100D4C, | |
| 0x4CC5D4BECB3E42B6, 0x597F299CFC657E2A, 0x5FCB6FAB3AD6FAEC, 0x6C44198C4A475817, | |
| }; | |
| /* Hash functions based on SHA-256, using different initial hash and digest bits */ | |
| template <const auto& InitialState, size_t TruncateBits = 256> | |
| using sha256x = sha2< | |
| TruncateBits, | |
| 512, | |
| uint32_t, | |
| uint64_t, | |
| sha256_K, | |
| InitialState | |
| >; | |
| /* Hash functions based on SHA-512, using different initial hash and digest bits */ | |
| template <const auto& InitialState, size_t TruncateBits = 512> | |
| using sha512x = sha2< | |
| TruncateBits, | |
| 1024, | |
| uint64_t, | |
| __uint128_t, | |
| sha512_K, | |
| InitialState | |
| >; | |
| using sha224 = sha256x<sha224_H0, 224>; | |
| using sha256 = sha256x<sha256_H0>; | |
| using sha512t224 = sha512x<sha512t224_H0, 224>; | |
| using sha512t256 = sha512x<sha512t256_H0, 256>; | |
| using sha384 = sha512x<sha384_H0, 384>; | |
| using sha512 = sha512x<sha512_H0>; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment