Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
Loading...
Searching...
No Matches
sha256.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * SHA-256, from FIPS 180-4.
5 *
6 * Present because a pseudopotential's identity is the hash of the file it ships
7 * as. The registry that consumes Q3's results already identifies a
8 * pseudopotential by the SHA-256 of its contents, and adopting that scheme
9 * verbatim rather than inventing a second one is a stated requirement -- two
10 * identity schemes for the same file turn every cross-check into a mapping
11 * exercise.
12 *
13 * Implemented rather than depended upon. The algorithm is a published standard
14 * of about a hundred lines with official test vectors, so an implementation can
15 * prove itself; a build dependency for it would have to be provisioned on every
16 * runner. FIPS 180-4, Sec. 5.3.3 for the initial hash value, Sec. 4.2.2 for the
17 * round constants, Sec. 6.2.2 for the compression function.
18 *
19 * Not a security primitive here and not offered as one: this identifies files,
20 * it does not authenticate them.
21 */
22
23#include <array>
24#include <cstdint>
25#include <cstdio>
26#include <stdexcept>
27#include <string>
28#include <vector>
29
30namespace q3 {
31
32/// Computes the SHA-256 digest of a byte sequence or a file.
33class SHA256 {
34 public:
35 /// Lowercase hexadecimal digest of the given bytes.
36 [[nodiscard]] static std::string hexadecimal_digest(const std::string& message) {
37 const std::array<std::uint32_t, 8> state = digest(message);
38
39 std::string hexadecimal;
40 hexadecimal.reserve(64);
41 for (std::uint32_t word : state) {
42 char buffer[9];
43 std::snprintf(buffer, sizeof(buffer), "%08x", word);
44 hexadecimal += buffer;
45 }
46 return hexadecimal;
47 }
48
49 /**
50 * Lowercase hexadecimal digest of a file's contents.
51 *
52 * Read as binary and hashed byte for byte, so the answer matches what any
53 * other tool reports for the same file rather than what it reports for some
54 * normalization of it.
55 *
56 * A read error is an error, not a short file. fread() returns a short count
57 * for both end-of-file and failure, so a mid-file I/O error on an unchecked
58 * loop yields a perfectly well-formed digest -- of a prefix. That is the
59 * one outcome this class must never produce: a confidently wrong identity
60 * is indistinguishable from a real one, which is the whole argument for
61 * hashing contents in the first place.
62 */
63 [[nodiscard]] static std::string hexadecimal_digest_of_file(const std::string& path) {
64 std::FILE* file = std::fopen(path.c_str(), "rb");
65 if (file == nullptr) {
66 throw std::runtime_error("SHA256: cannot open '" + path + "'");
67 }
68
69 std::string contents;
70 char buffer[65536];
71 std::size_t read = 0;
72 try {
73 while ((read = std::fread(buffer, 1, sizeof(buffer), file)) > 0) {
74 contents.append(buffer, read);
75 }
76 } catch (...) {
77 std::fclose(file);
78 throw;
79 }
80 const bool read_failed = std::ferror(file) != 0;
81 std::fclose(file);
82 if (read_failed) {
83 throw std::runtime_error("SHA256: error reading '" + path + "'");
84 }
85 return hexadecimal_digest(contents);
86 }
87
88 private:
89 [[nodiscard]] static std::uint32_t rotate_right(std::uint32_t value, int places) {
90 return (value >> places) | (value << (32 - places));
91 }
92
93 /// The eight-word state after compressing the padded message. FIPS 180-4 Sec. 6.2.2.
94 [[nodiscard]] static std::array<std::uint32_t, 8> digest(const std::string& message) {
95 std::array<std::uint32_t, 8> state = {0x6a09e667u,
96 0xbb67ae85u,
97 0x3c6ef372u,
98 0xa54ff53au,
99 0x510e527fu,
100 0x9b05688cu,
101 0x1f83d9abu,
102 0x5be0cd19u};
103
104 /**
105 * Padding: a single 1 bit, then zeros, then the message length in bits
106 * as a 64-bit big-endian integer, to a multiple of 64 bytes.
107 */
108 std::vector<unsigned char> padded(message.begin(), message.end());
109 const std::uint64_t length_in_bits = static_cast<std::uint64_t>(message.size()) * 8;
110 padded.push_back(0x80);
111 while (padded.size() % 64 != 56) {
112 padded.push_back(0x00);
113 }
114 for (int shift = 56; shift >= 0; shift -= 8) {
115 padded.push_back(static_cast<unsigned char>((length_in_bits >> shift) & 0xffu));
116 }
117
118 std::array<std::uint32_t, 64> schedule{};
119 for (std::size_t block = 0; block < padded.size(); block += 64) {
120 for (int index = 0; index < 16; index++) {
121 const std::size_t offset = block + static_cast<std::size_t>(index) * 4;
122 schedule[index] = (static_cast<std::uint32_t>(padded[offset]) << 24) |
123 (static_cast<std::uint32_t>(padded[offset + 1]) << 16) |
124 (static_cast<std::uint32_t>(padded[offset + 2]) << 8) |
125 static_cast<std::uint32_t>(padded[offset + 3]);
126 }
127 for (int index = 16; index < 64; index++) {
128 const std::uint32_t previous = schedule[index - 15];
129 const std::uint32_t recent = schedule[index - 2];
130 const std::uint32_t sigma_zero =
131 rotate_right(previous, 7) ^ rotate_right(previous, 18) ^ (previous >> 3);
132 const std::uint32_t sigma_one =
133 rotate_right(recent, 17) ^ rotate_right(recent, 19) ^ (recent >> 10);
134 schedule[index] =
135 schedule[index - 16] + sigma_zero + schedule[index - 7] + sigma_one;
136 }
137
138 std::array<std::uint32_t, 8> working = state;
139 for (int index = 0; index < 64; index++) {
140 const std::uint32_t capital_sigma_one = rotate_right(working[4], 6) ^
141 rotate_right(working[4], 11) ^
142 rotate_right(working[4], 25);
143 const std::uint32_t choice = (working[4] & working[5]) ^ (~working[4] & working[6]);
144 const std::uint32_t first = working[7] + capital_sigma_one + choice +
145 round_constants()[index] + schedule[index];
146 const std::uint32_t capital_sigma_zero = rotate_right(working[0], 2) ^
147 rotate_right(working[0], 13) ^
148 rotate_right(working[0], 22);
149 const std::uint32_t majority = (working[0] & working[1]) ^
150 (working[0] & working[2]) ^
151 (working[1] & working[2]);
152 const std::uint32_t second = capital_sigma_zero + majority;
153
154 working[7] = working[6];
155 working[6] = working[5];
156 working[5] = working[4];
157 working[4] = working[3] + first;
158 working[3] = working[2];
159 working[2] = working[1];
160 working[1] = working[0];
161 working[0] = first + second;
162 }
163 for (int index = 0; index < 8; index++) {
164 state[index] += working[index];
165 }
166 }
167 return state;
168 }
169
170 /// First 32 bits of the fractional parts of the cube roots of the first 64 primes.
171 [[nodiscard]] static const std::array<std::uint32_t, 64>& round_constants() {
172 static const std::array<std::uint32_t, 64> constants = {
173 0x428a2f98u, 0x71374491u, 0xb5c0fbcfu, 0xe9b5dba5u, 0x3956c25bu, 0x59f111f1u,
174 0x923f82a4u, 0xab1c5ed5u, 0xd807aa98u, 0x12835b01u, 0x243185beu, 0x550c7dc3u,
175 0x72be5d74u, 0x80deb1feu, 0x9bdc06a7u, 0xc19bf174u, 0xe49b69c1u, 0xefbe4786u,
176 0x0fc19dc6u, 0x240ca1ccu, 0x2de92c6fu, 0x4a7484aau, 0x5cb0a9dcu, 0x76f988dau,
177 0x983e5152u, 0xa831c66du, 0xb00327c8u, 0xbf597fc7u, 0xc6e00bf3u, 0xd5a79147u,
178 0x06ca6351u, 0x14292967u, 0x27b70a85u, 0x2e1b2138u, 0x4d2c6dfcu, 0x53380d13u,
179 0x650a7354u, 0x766a0abbu, 0x81c2c92eu, 0x92722c85u, 0xa2bfe8a1u, 0xa81a664bu,
180 0xc24b8b70u, 0xc76c51a3u, 0xd192e819u, 0xd6990624u, 0xf40e3585u, 0x106aa070u,
181 0x19a4c116u, 0x1e376c08u, 0x2748774cu, 0x34b0bcb5u, 0x391c0cb3u, 0x4ed8aa4au,
182 0x5b9cca4fu, 0x682e6ff3u, 0x748f82eeu, 0x78a5636fu, 0x84c87814u, 0x8cc70208u,
183 0x90befffau, 0xa4506cebu, 0xbef9a3f7u, 0xc67178f2u};
184 return constants;
185 }
186};
187
188} // namespace q3
Computes the SHA-256 digest of a byte sequence or a file.
Definition sha256.hpp:33
static std::string hexadecimal_digest(const std::string &message)
Lowercase hexadecimal digest of the given bytes.
Definition sha256.hpp:36
static std::string hexadecimal_digest_of_file(const std::string &path)
Definition sha256.hpp:63