Signet Forge 0.1.1
C++20 Parquet library with AI-native extensions
DEMO
Loading...
Searching...
No Matches
sha256.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright 2026 Johnson Ogundeji
3#pragma once
4
14
15#include <array>
16#include <cstddef>
17#include <cstdint>
18#include <cstring>
19#include <vector>
20
21namespace signet::forge::crypto {
22namespace detail::sha256 {
23
24// ===========================================================================
25// SHA-256 (FIPS 180-4)
26// ===========================================================================
27
31static constexpr uint32_t H0[8] = {
32 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
33 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
34};
35
39static constexpr uint32_t K[64] = {
40 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
41 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
42 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
43 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
44 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
45 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
46 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
47 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
48 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
49 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
50 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
51 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
52 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
53 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
54 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
55 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
56};
57
59// BVP FIX (2026-04-17): guard n==0 which causes UB (x << 32 is
60// undefined for uint32_t in C/C++). SHA-256 never uses n=0, but
61// the function should be correct at all boundary inputs per the
62// Boundary Verification Protocol.
63inline constexpr uint32_t rotr(uint32_t x, int n) {
64 if (n == 0) return x;
65 return (x >> n) | (x << (32 - n));
66}
67
69inline constexpr uint32_t Ch(uint32_t x, uint32_t y, uint32_t z) {
70 return (x & y) ^ (~x & z);
71}
72
73inline constexpr uint32_t Maj(uint32_t x, uint32_t y, uint32_t z) {
74 return (x & y) ^ (x & z) ^ (y & z);
75}
76
77inline constexpr uint32_t Sigma0(uint32_t x) {
78 return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22);
79}
80
81inline constexpr uint32_t Sigma1(uint32_t x) {
82 return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25);
83}
84
85inline constexpr uint32_t sigma0(uint32_t x) {
86 return rotr(x, 7) ^ rotr(x, 18) ^ (x >> 3);
87}
88
89inline constexpr uint32_t sigma1(uint32_t x) {
90 return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10);
91}
92
94inline uint32_t load_be32(const uint8_t* p) {
95 return (static_cast<uint32_t>(p[0]) << 24)
96 | (static_cast<uint32_t>(p[1]) << 16)
97 | (static_cast<uint32_t>(p[2]) << 8)
98 | (static_cast<uint32_t>(p[3]));
99}
100
102inline void store_be32(uint8_t* p, uint32_t v) {
103 p[0] = static_cast<uint8_t>(v >> 24);
104 p[1] = static_cast<uint8_t>(v >> 16);
105 p[2] = static_cast<uint8_t>(v >> 8);
106 p[3] = static_cast<uint8_t>(v);
107}
108
110inline void store_be64(uint8_t* p, uint64_t v) {
111 p[0] = static_cast<uint8_t>(v >> 56);
112 p[1] = static_cast<uint8_t>(v >> 48);
113 p[2] = static_cast<uint8_t>(v >> 40);
114 p[3] = static_cast<uint8_t>(v >> 32);
115 p[4] = static_cast<uint8_t>(v >> 24);
116 p[5] = static_cast<uint8_t>(v >> 16);
117 p[6] = static_cast<uint8_t>(v >> 8);
118 p[7] = static_cast<uint8_t>(v);
119}
120
125inline void compress(uint32_t h[8], const uint8_t block[64]) {
126 // Step 1: Prepare the message schedule W[0..63]
127 uint32_t W[64];
128
129 // W[0..15] = the sixteen 32-bit words from the message block
130 for (int t = 0; t < 16; ++t) {
131 W[t] = load_be32(block + t * 4);
132 }
133
134 // W[16..63] = derived from earlier words
135 for (int t = 16; t < 64; ++t) {
136 W[t] = sigma1(W[t - 2]) + W[t - 7] + sigma0(W[t - 15]) + W[t - 16];
137 }
138
139 // Step 2: Initialize working variables
140 uint32_t a = h[0], b = h[1], c = h[2], d = h[3];
141 uint32_t e = h[4], f = h[5], g = h[6], hh = h[7];
142
143 // Step 3: 64 rounds of compression
144 for (int t = 0; t < 64; ++t) {
145 uint32_t T1 = hh + Sigma1(e) + Ch(e, f, g) + K[t] + W[t];
146 uint32_t T2 = Sigma0(a) + Maj(a, b, c);
147 hh = g;
148 g = f;
149 f = e;
150 e = d + T1;
151 d = c;
152 c = b;
153 b = a;
154 a = T1 + T2;
155 }
156
157 // Step 4: Update hash state
158 h[0] += a; h[1] += b; h[2] += c; h[3] += d;
159 h[4] += e; h[5] += f; h[6] += g; h[7] += hh;
160}
161
170inline std::array<uint8_t, 32> sha256(const uint8_t* data, size_t size) {
171 // Initialize hash state
172 uint32_t h[8];
173 std::memcpy(h, H0, sizeof(h));
174
175 // Process complete 64-byte blocks
176 size_t full_blocks = size / 64;
177 for (size_t i = 0; i < full_blocks; ++i) {
178 compress(h, data + i * 64);
179 }
180
181 // Pad the final block(s)
182 // Padding format: message || 1-bit || 0*-bits || 64-bit big-endian length
183 size_t remainder = size % 64;
184 uint8_t final_block[128] = {}; // At most 2 blocks needed
185
186 // Copy remaining bytes
187 if (remainder > 0)
188 std::memcpy(final_block, data + full_blocks * 64, remainder);
189
190 // Append the 1-bit (0x80 byte)
191 final_block[remainder] = 0x80;
192
193 // Determine how many final blocks we need
194 size_t final_blocks;
195 if (remainder < 56) {
196 // Length fits in the same block (bytes 56..63)
197 final_blocks = 1;
198 store_be64(final_block + 56, static_cast<uint64_t>(size) * 8);
199 } else {
200 // Need a second block for the length
201 final_blocks = 2;
202 store_be64(final_block + 120, static_cast<uint64_t>(size) * 8);
203 }
204
205 // Process final block(s)
206 for (size_t i = 0; i < final_blocks; ++i) {
207 compress(h, final_block + i * 64);
208 }
209
210 // Output as big-endian bytes
211 std::array<uint8_t, 32> digest;
212 for (int i = 0; i < 8; ++i) {
213 store_be32(digest.data() + i * 4, h[i]);
214 }
215
216 return digest;
217}
218
220inline std::array<uint8_t, 32> sha256(const std::vector<uint8_t>& data) {
221 return sha256(data.data(), data.size());
222}
223
231inline std::array<uint8_t, 32> sha256_concat(
232 const uint8_t* a, size_t a_size,
233 const uint8_t* b, size_t b_size) {
234
235 static constexpr uint8_t LABEL[] = "signet-forge-hybrid-kem-v1";
236 std::vector<uint8_t> combined;
237 combined.reserve(sizeof(LABEL) - 1 + a_size + b_size);
238 combined.insert(combined.end(), LABEL, LABEL + sizeof(LABEL) - 1);
239 combined.insert(combined.end(), a, a + a_size);
240 combined.insert(combined.end(), b, b + b_size);
241 return sha256(combined.data(), combined.size());
242}
243
244} // namespace detail::sha256
245} // namespace signet::forge::crypto
void compress(uint32_t h[8], const uint8_t block[64])
Process a single 64-byte (512-bit) message block.
Definition sha256.hpp:125
constexpr uint32_t Maj(uint32_t x, uint32_t y, uint32_t z)
Definition sha256.hpp:73
constexpr uint32_t sigma1(uint32_t x)
Definition sha256.hpp:89
constexpr uint32_t Ch(uint32_t x, uint32_t y, uint32_t z)
SHA-256 logical functions (FIPS 180-4 Section 4.1.2)
Definition sha256.hpp:69
void store_be32(uint8_t *p, uint32_t v)
Store a uint32 as 4 big-endian bytes.
Definition sha256.hpp:102
constexpr uint32_t Sigma0(uint32_t x)
Definition sha256.hpp:77
void store_be64(uint8_t *p, uint64_t v)
Store a uint64 as 8 big-endian bytes.
Definition sha256.hpp:110
std::array< uint8_t, 32 > sha256_concat(const uint8_t *a, size_t a_size, const uint8_t *b, size_t b_size)
Hash concatenation of two byte spans with domain separation: SHA-256(label || a || b).
Definition sha256.hpp:231
std::array< uint8_t, 32 > sha256(const uint8_t *data, size_t size)
Compute SHA-256 hash of arbitrary-length input.
Definition sha256.hpp:170
constexpr uint32_t rotr(uint32_t x, int n)
Right-rotate a 32-bit word by n bits.
Definition sha256.hpp:63
constexpr uint32_t sigma0(uint32_t x)
Definition sha256.hpp:85
uint32_t load_be32(const uint8_t *p)
Load a big-endian uint32 from 4 bytes.
Definition sha256.hpp:94
constexpr uint32_t Sigma1(uint32_t x)
Definition sha256.hpp:81