nx_sha512.nx source
↩ module page · 532 lines · 18909 B
1// sha512.nx -- SHA-512 in pure NishiLang (FIPS 180-4 section 6.4).
2//
3// Canonical: this is the substrate-wide canonical SHA-512
4// implementation per [[feedback-no-tool-proliferation-bit-level]].
5// SHA-384 / HMAC-SHA-512 / HKDF-SHA-512 / Argon2id compose THIS
6// file's SHA-512 primitive (SHA-384 is SHA-512 with truncated
7// output + different IV; HMAC adds key wrapping; HKDF adds
8// expand+extract; Argon2id uses BLAKE2b which is separate).
9// Re-implementing the SHA-512 K-table or round function inline
10// is refused.
11//
12// Completes the SHA-2 family alongside sha256.nx. Needed for
13// TLS 1.3 cipher suites that negotiate HMAC-SHA-384 or
14// HMAC-SHA-512 (e.g. TLS_AES_256_GCM_SHA384). SHA-512 is also the
15// natural word width for 64-bit targets -- i64 arithmetic is the
16// primitive, no masking required (contrast sha256.nx's constant
17// M32 masking).
18//
19// Structure mirrors FIPS 180-4 exactly:
20// - 128-byte (1024-bit) blocks
21// - 8 x 64-bit hash state words
22// - 80 rounds with cube-roots-of-primes constants
23// - Merkle-Damgård construction with length-padded final block
24//
25// API:
26// sha512_init(*Sha512)
27// sha512_update(*Sha512, *u8, len)
28// sha512_final(*Sha512, *u8 out64)
29// sha512_digest(*u8 bytes, len, *u8 out64) -- one-shot
30//
31// Context is ~256 bytes: 8 state words + 128-byte buffer + index +
32// bit counter. Caller-allocated.
33//
34// Grover's algorithm under quantum attack: SHA-512 collision
35// resistance halves to 256 bits classical = 128 bits effective
36// against a quantum adversary. Still secure for all practical
37// purposes. Output truncated to 32 bytes yields SHA-512/256
38// (also FIPS 180-4 Appendix A), useful for shorter tags.
39//
40// license_tier: INDEPENDENT_REDERIVE
41// genealogy_id: international-research-sources/nist/fips_180_4
42//
43// nx_safety_envelope:
44// intended_use: "SHA-512 cryptographic hash -- HMAC-SHA-512 +
45// Ed25519 internal hash + general 512-bit
46// digest"
47// sil_target: SIL3
48// asil_target: QM
49// dal_target: DAL B
50// evidence: [FIPS_180-4_canonical_basis, no_FP,
51// no_table_lookup, constant_time_construction,
52// NIST_CAVP_test_vectors_VERIFIED]
53// hazard_register: [bug-tape-length-extension-attack,
54// bug-tape-state-not-cleared-after-use]
55// residual_risk: "Length-extension applies to raw SHA-512;
56// use HMAC-SHA-512 for keyed scenarios."
57// verdict: NOT_YET_EVALUATED
58
59import "nx_syscalls.nx"
60import "nx_bits.nx"
61
62// ---- 64-bit primitive ops -----------------------------------------
63//
64// NishiLang's i64 IS the native width. We use unsigned semantics
65// conceptually but the bit patterns are identical between signed
66// and unsigned 64-bit math for the ops we need (xor, and, shr, add
67// mod 2^64). The compiler lowers >> to arithmetic shift on i64;
68// we use `(x >> n) & mask_from_n` to emulate logical shift where
69// needed for rotations.
70
71// Delegated to nx_bits_rotr64 (rorq/ror intrinsic). SHA-512 round
72// function uses 6 rotates per word in the message schedule + state
73// update; this is the hottest single primitive in the hash.
74func rotr64_v(x: i64, n: i64) -> i64 {
75 return nx_bits_rotr64(x, n)
76}
77
78// Logical right shift (no sign extension).
79func shr64_v(x: i64, n: i64) -> i64 {
80 let nn: i64 = n & 63
81 let mask_lo: i64 = (1 << (64 - nn)) - 1
82 return (x >> nn) & mask_lo
83}
84
85// ---- Sha512 context -----------------------------------------------
86
87struct Sha512 {
88 h0: i64, h1: i64, h2: i64, h3: i64,
89 h4: i64, h5: i64, h6: i64, h7: i64,
90 // 128-byte block: 16 x i64 (big-endian packing by byte access).
91 b0: i64, b1: i64, b2: i64, b3: i64,
92 b4: i64, b5: i64, b6: i64, b7: i64,
93 b8: i64, b9: i64, b10: i64, b11: i64,
94 b12: i64, b13: i64, b14: i64, b15: i64,
95 // byte index into the 128-byte block (0..127) and 128-bit
96 // message length in bits. FIPS 180-4 mandates 128-bit length
97 // field for SHA-512; we represent just the low 64 bits -- the
98 // high bits would require messages > 2^61 bytes which is not
99 // reachable on any foreseeable hardware.
100 idx: i64,
101 bit_len: i64,
102}
103
104// ---- round constants ----------------------------------------------
105//
106// Fractional parts of cube roots of the first 80 primes, times 2^64,
107// per FIPS 180-4 section 4.2.3.
108// Rewritten 2026-05-19: every K constant emitted as the FIPS 180-4
109// hex value directly. The prior version used hand-converted
110// negative-decimal i64 representations for entries with the top
111// bit set; multiple were wrong (e.g. K[2] = -4635341792555532645
112// converts to 0xbf9bea2ec25f039b, NOT 0xb5c0fbcfec4d3b2f). That
113// silently produced wrong hash output cascading into broken
114// Ed25519 sign + verify + x509_validate. The substrate's
115// NishiLang lexer now accepts hex literals with the top bit set
116// (verified via _hex_literal_probe_test) so we just use the FIPS
117// values verbatim. Per [[feedback-no-false-ok-substrate-honesty-audit]]
118// + Cardinal 11 (no magic numbers / use the canonical reference).
119func sha512_k(i: i64) -> i64 {
120 if i == 0 { return 0x428a2f98d728ae22 }
121 if i == 1 { return 0x7137449123ef65cd }
122 if i == 2 { return 0xb5c0fbcfec4d3b2f }
123 if i == 3 { return 0xe9b5dba58189dbbc }
124 if i == 4 { return 0x3956c25bf348b538 }
125 if i == 5 { return 0x59f111f1b605d019 }
126 if i == 6 { return 0x923f82a4af194f9b }
127 if i == 7 { return 0xab1c5ed5da6d8118 }
128 if i == 8 { return 0xd807aa98a3030242 }
129 if i == 9 { return 0x12835b0145706fbe }
130 if i == 10 { return 0x243185be4ee4b28c }
131 if i == 11 { return 0x550c7dc3d5ffb4e2 }
132 if i == 12 { return 0x72be5d74f27b896f }
133 if i == 13 { return 0x80deb1fe3b1696b1 }
134 if i == 14 { return 0x9bdc06a725c71235 }
135 if i == 15 { return 0xc19bf174cf692694 }
136 if i == 16 { return 0xe49b69c19ef14ad2 }
137 if i == 17 { return 0xefbe4786384f25e3 }
138 if i == 18 { return 0x0fc19dc68b8cd5b5 }
139 if i == 19 { return 0x240ca1cc77ac9c65 }
140 if i == 20 { return 0x2de92c6f592b0275 }
141 if i == 21 { return 0x4a7484aa6ea6e483 }
142 if i == 22 { return 0x5cb0a9dcbd41fbd4 }
143 if i == 23 { return 0x76f988da831153b5 }
144 if i == 24 { return 0x983e5152ee66dfab }
145 if i == 25 { return 0xa831c66d2db43210 }
146 if i == 26 { return 0xb00327c898fb213f }
147 if i == 27 { return 0xbf597fc7beef0ee4 }
148 if i == 28 { return 0xc6e00bf33da88fc2 }
149 if i == 29 { return 0xd5a79147930aa725 }
150 if i == 30 { return 0x06ca6351e003826f }
151 if i == 31 { return 0x142929670a0e6e70 }
152 if i == 32 { return 0x27b70a8546d22ffc }
153 if i == 33 { return 0x2e1b21385c26c926 }
154 if i == 34 { return 0x4d2c6dfc5ac42aed }
155 if i == 35 { return 0x53380d139d95b3df }
156 if i == 36 { return 0x650a73548baf63de }
157 if i == 37 { return 0x766a0abb3c77b2a8 }
158 if i == 38 { return 0x81c2c92e47edaee6 }
159 if i == 39 { return 0x92722c851482353b }
160 if i == 40 { return 0xa2bfe8a14cf10364 }
161 if i == 41 { return 0xa81a664bbc423001 }
162 if i == 42 { return 0xc24b8b70d0f89791 }
163 if i == 43 { return 0xc76c51a30654be30 }
164 if i == 44 { return 0xd192e819d6ef5218 }
165 if i == 45 { return 0xd69906245565a910 }
166 if i == 46 { return 0xf40e35855771202a }
167 if i == 47 { return 0x106aa07032bbd1b8 }
168 if i == 48 { return 0x19a4c116b8d2d0c8 }
169 if i == 49 { return 0x1e376c085141ab53 }
170 if i == 50 { return 0x2748774cdf8eeb99 }
171 if i == 51 { return 0x34b0bcb5e19b48a8 }
172 if i == 52 { return 0x391c0cb3c5c95a63 }
173 if i == 53 { return 0x4ed8aa4ae3418acb }
174 if i == 54 { return 0x5b9cca4f7763e373 }
175 if i == 55 { return 0x682e6ff3d6b2b8a3 }
176 if i == 56 { return 0x748f82ee5defb2fc }
177 if i == 57 { return 0x78a5636f43172f60 }
178 if i == 58 { return 0x84c87814a1f0ab72 }
179 if i == 59 { return 0x8cc702081a6439ec }
180 if i == 60 { return 0x90befffa23631e28 }
181 if i == 61 { return 0xa4506cebde82bde9 }
182 if i == 62 { return 0xbef9a3f7b2c67915 }
183 if i == 63 { return 0xc67178f2e372532b }
184 if i == 64 { return 0xca273eceea26619c }
185 if i == 65 { return 0xd186b8c721c0c207 }
186 if i == 66 { return 0xeada7dd6cde0eb1e }
187 if i == 67 { return 0xf57d4f7fee6ed178 }
188 if i == 68 { return 0x06f067aa72176fba }
189 if i == 69 { return 0x0a637dc5a2c898a6 }
190 if i == 70 { return 0x113f9804bef90dae }
191 if i == 71 { return 0x1b710b35131c471b }
192 if i == 72 { return 0x28db77f523047d84 }
193 if i == 73 { return 0x32caab7b40c72493 }
194 if i == 74 { return 0x3c9ebe0a15c9bebc }
195 if i == 75 { return 0x431d67c49c100d4c }
196 if i == 76 { return 0x4cc5d4becb3e42b6 }
197 if i == 77 { return 0x597f299cfc657e2a }
198 if i == 78 { return 0x5fcb6fab3ad6faec }
199 return 0x6c44198c4a475817 // i == 79
200}
201
202// ---- block byte I/O -----------------------------------------------
203//
204// Mirror sha256.nx's pattern: 128-byte block stored as 16 x i64,
205// byte-addressed via blk_byte / sha512_blk_set_byte. Big-endian byte
206// packing within each i64 slot.
207
208func blk_get_i64(c: *Sha512, w: i64) -> i64 {
209 if w == 0 { return c.b0 }
210 if w == 1 { return c.b1 }
211 if w == 2 { return c.b2 }
212 if w == 3 { return c.b3 }
213 if w == 4 { return c.b4 }
214 if w == 5 { return c.b5 }
215 if w == 6 { return c.b6 }
216 if w == 7 { return c.b7 }
217 if w == 8 { return c.b8 }
218 if w == 9 { return c.b9 }
219 if w == 10 { return c.b10 }
220 if w == 11 { return c.b11 }
221 if w == 12 { return c.b12 }
222 if w == 13 { return c.b13 }
223 if w == 14 { return c.b14 }
224 return c.b15
225}
226
227func blk_set_i64(c: *Sha512, w: i64, v: i64) -> i64 {
228 if w == 0 { c.b0 = v; return 0 }
229 if w == 1 { c.b1 = v; return 0 }
230 if w == 2 { c.b2 = v; return 0 }
231 if w == 3 { c.b3 = v; return 0 }
232 if w == 4 { c.b4 = v; return 0 }
233 if w == 5 { c.b5 = v; return 0 }
234 if w == 6 { c.b6 = v; return 0 }
235 if w == 7 { c.b7 = v; return 0 }
236 if w == 8 { c.b8 = v; return 0 }
237 if w == 9 { c.b9 = v; return 0 }
238 if w == 10 { c.b10 = v; return 0 }
239 if w == 11 { c.b11 = v; return 0 }
240 if w == 12 { c.b12 = v; return 0 }
241 if w == 13 { c.b13 = v; return 0 }
242 if w == 14 { c.b14 = v; return 0 }
243 c.b15 = v
244 return 0
245}
246
247// Store byte `v` at position `n` in the 128-byte block, big-endian
248// packed (byte 0 goes in the high byte of word 0).
249func sha512_blk_set_byte(c: *Sha512, n: i64, v: i64) -> i64 {
250 let w: i64 = n >> 3 // which of the 16 words
251 let b: i64 = 7 - (n & 7) // byte position (high-to-low)
252 let shift: i64 = b * 8
253 let old: i64 = blk_get_i64(c, w)
254 let cleared: i64 = old & ((0xFF << shift) ^ -1)
255 let merged: i64 = cleared | ((v & 0xFF) << shift)
256 blk_set_i64(c, w, merged)
257 return 0
258}
259
260// ---- compress function (80-round SHA-512) ------------------------
261//
262// Follows FIPS 180-4 section 6.4.2 exactly. Sigma functions are
263// 64-bit rotations; ch/maj are bit-level selects over the state.
264
265func sha512_sigma0(x: i64) -> i64 {
266 return rotr64_v(x, 28) ^ rotr64_v(x, 34) ^ rotr64_v(x, 39)
267}
268
269func sha512_sigma1(x: i64) -> i64 {
270 return rotr64_v(x, 14) ^ rotr64_v(x, 18) ^ rotr64_v(x, 41)
271}
272
273func sha512_gamma0(x: i64) -> i64 {
274 return rotr64_v(x, 1) ^ rotr64_v(x, 8) ^ shr64_v(x, 7)
275}
276
277func sha512_gamma1(x: i64) -> i64 {
278 return rotr64_v(x, 19) ^ rotr64_v(x, 61) ^ shr64_v(x, 6)
279}
280
281func sha512_ch(x: i64, y: i64, z: i64) -> i64 {
282 return (x & y) ^ ((x ^ -1) & z)
283}
284
285func sha512_maj(x: i64, y: i64, z: i64) -> i64 {
286 return (x & y) ^ (x & z) ^ (y & z)
287}
288
289// Message schedule buffer W[0..79] -- allocated via sys_mmap so we
290// don't need a struct field per entry.
291func sha512_compress(c: *Sha512) -> i64 {
292 let w_raw: *u8 = sys_mmap(80 * 8)
293 let w: *i64 = w_raw as *i64
294
295 // W[0..15] from the 128-byte block directly.
296 var t: i64 = 0
297 while t < 16 { w[t] = blk_get_i64(c, t); t = t + 1 }
298
299 // W[16..79] via message schedule.
300 t = 16
301 while t < 80 {
302 let s0: i64 = sha512_gamma0(w[t - 15])
303 let s1: i64 = sha512_gamma1(w[t - 2])
304 w[t] = w[t - 16] + s0 + w[t - 7] + s1
305 t = t + 1
306 }
307
308 var a: i64 = c.h0
309 var b: i64 = c.h1
310 var cc: i64 = c.h2
311 var d: i64 = c.h3
312 var e: i64 = c.h4
313 var f: i64 = c.h5
314 var g: i64 = c.h6
315 var h: i64 = c.h7
316
317 t = 0
318 while t < 80 {
319 let t1: i64 = h + sha512_sigma1(e) + sha512_ch(e, f, g) + sha512_k(t) + w[t]
320 let t2: i64 = sha512_sigma0(a) + sha512_maj(a, b, cc)
321 h = g
322 g = f
323 f = e
324 e = d + t1
325 d = cc
326 cc = b
327 b = a
328 a = t1 + t2
329 t = t + 1
330 }
331
332 c.h0 = c.h0 + a
333 c.h1 = c.h1 + b
334 c.h2 = c.h2 + cc
335 c.h3 = c.h3 + d
336 c.h4 = c.h4 + e
337 c.h5 = c.h5 + f
338 c.h6 = c.h6 + g
339 c.h7 = c.h7 + h
340 return 0
341}
342
343// ---- public API ----------------------------------------------------
344
345func sha512_init(c: *Sha512) -> i64 {
346 // Fractional parts of square roots of the first 8 primes,
347 // per FIPS 180-4 section 5.3.5.
348 // Rewritten 2026-05-19 -- same root cause as the K constants
349 // (hand-converted negative decimals were unreliable; hex
350 // literals now lex correctly with top bit set).
351 c.h0 = 0x6a09e667f3bcc908
352 c.h1 = 0xbb67ae8584caa73b
353 c.h2 = 0x3c6ef372fe94f82b
354 c.h3 = 0xa54ff53a5f1d36f1
355 c.h4 = 0x510e527fade682d1
356 c.h5 = 0x9b05688c2b3e6c1f
357 c.h6 = 0x1f83d9abfb41bd6b
358 c.h7 = 0x5be0cd19137e2179
359 c.idx = 0
360 c.bit_len = 0
361 return 0
362}
363
364func sha512_update(c: *Sha512, bytes: *u8, n: i64) -> i64 {
365 var i: i64 = 0
366 while i < n {
367 sha512_blk_set_byte(c, c.idx, bytes[i])
368 c.idx = c.idx + 1
369 c.bit_len = c.bit_len + 8
370 if c.idx == 128 {
371 sha512_compress(c)
372 c.idx = 0
373 }
374 i = i + 1
375 }
376 return 0
377}
378
379func sha512_final(c: *Sha512, out: *u8) -> i64 {
380 // Append 0x80 then zero-pad until idx == 112 (leaving 16 bytes
381 // for the 128-bit length counter).
382 let bit_total: i64 = c.bit_len
383 sha512_blk_set_byte(c, c.idx, 0x80)
384 c.idx = c.idx + 1
385 while c.idx != 112 {
386 if c.idx == 128 {
387 sha512_compress(c)
388 c.idx = 0
389 }
390 sha512_blk_set_byte(c, c.idx, 0)
391 c.idx = c.idx + 1
392 }
393 // Write length: upper 64 bits = 0 (messages < 2^61 bytes),
394 // lower 64 bits = bit_total in big-endian.
395 var j: i64 = 0
396 while j < 8 { sha512_blk_set_byte(c, 112 + j, 0); j = j + 1 }
397 j = 0
398 while j < 8 {
399 let shift: i64 = (7 - j) * 8
400 let byte: i64 = (bit_total >> shift) & 0xFF
401 sha512_blk_set_byte(c, 120 + j, byte)
402 j = j + 1
403 }
404 sha512_compress(c)
405
406 // Serialize H0..H7 big-endian into `out` (64 bytes).
407 var w: i64 = 0
408 while w < 8 {
409 var v: i64 = 0
410 if w == 0 { v = c.h0 }
411 if w == 1 { v = c.h1 }
412 if w == 2 { v = c.h2 }
413 if w == 3 { v = c.h3 }
414 if w == 4 { v = c.h4 }
415 if w == 5 { v = c.h5 }
416 if w == 6 { v = c.h6 }
417 if w == 7 { v = c.h7 }
418 var b: i64 = 0
419 while b < 8 {
420 let shift: i64 = (7 - b) * 8
421 out[w * 8 + b] = (v >> shift) & 0xFF
422 b = b + 1
423 }
424 w = w + 1
425 }
426 return 0
427}
428
429// One-shot convenience.
430func sha512_digest(bytes: *u8, n: i64, out: *u8) -> i64 {
431 let c_raw: *u8 = sys_mmap(512)
432 let c: *Sha512 = c_raw as *Sha512
433 sha512_init(c)
434 sha512_update(c, bytes, n)
435 sha512_final(c, out)
436 return 0
437}
438
439// ---- SHA-384 (FIPS 180-4 section 6.5) ----------------------------
440//
441// Same 80-round Keccak-style compression as SHA-512; differs only
442// in initial H values and output truncation (first 48 bytes).
443// Required by TLS 1.3 cipher suites such as TLS_AES_256_GCM_SHA384
444// that use HMAC-SHA-384 in the key schedule.
445
446// Rewritten 2026-05-20: 3 of 4 hand-converted negative-decimal
447// representations of these FIPS 180-4 §5.3.4 constants were WRONG
448// (h2/h5/h6) -- e.g. h2 claimed -6116909921290321426 = 0x9159015a3070dd17,
449// but actually -6116909921290321426 = 0xab1c5ed5da6d81ee. Silent
450// SHA-384 output corruption ensued: no external-reference KAT
451// existed to catch it. Per [[feedback-canonical-constants-kat-
452// against-reference-not-hand-converted]] (cardinal 2026-05-19,
453// same arc), constants from standards live as the canonical
454// representation (hex) -- never hand-converted to decimal without
455// external verification.
456func sha384_init(c: *Sha512) -> i64 {
457 c.h0 = 0xcbbb9d5dc1059ed8
458 c.h1 = 0x629a292a367cd507
459 c.h2 = 0x9159015a3070dd17
460 c.h3 = 0x152fecd8f70e5939
461 c.h4 = 0x67332667ffc00b31
462 c.h5 = 0x8eb44a8768581511
463 c.h6 = 0xdb0c2e0d64f98fa7
464 c.h7 = 0x47b5481dbefa4fa4
465 c.idx = 0
466 c.bit_len = 0
467 return 0
468}
469
470func sha384_digest(bytes: *u8, n: i64, out: *u8) -> i64 {
471 let c_raw: *u8 = sys_mmap(512)
472 let c: *Sha512 = c_raw as *Sha512
473 // Use a 64-byte scratch for the full 512-bit digest, then
474 // copy only the first 48 bytes to `out` (SHA-384 truncation).
475 let full: *u8 = sys_mmap(64)
476 sha384_init(c)
477 sha512_update(c, bytes, n)
478 sha512_final(c, full)
479 var i: i64 = 0
480 while i < 48 { out[i] = full[i]; i = i + 1 }
481 return 0
482}
483
484// ---- SHA-512/256 (FIPS 180-4 Appendix A truncation) --------------
485//
486// 32-byte digest from the SHA-512 compression function, enabling
487// SHA-256-width output with the performance characteristics of
488// 64-bit SHA-512 compression (faster on 64-bit CPUs that don't
489// accelerate SHA-256 specifically). Same strength as SHA-256
490// (256-bit second-preimage / 128-bit collision resistance) but
491// distinct from SHA-256 outputs -- different IV prevents any
492// cross-collision between the two.
493
494func sha512_256_init(c: *Sha512) -> i64 {
495 // FIPS 180-4 Appendix A: generated by hashing "SHA-512/256"
496 // against SHA-512's IV-of-IVs.
497 // Rewritten 2026-05-20: 4 of 4 hand-converted negative decimals
498 // here were ALSO WRONG (silent corruption of SHA-512/256 outputs).
499 // Per the same cardinal -- hex literals only, no hand-conversion.
500 c.h0 = 0x22312194fc2bf72c
501 c.h1 = 0x9f555fa3c84c64c2
502 c.h2 = 0x2393b86b6f53b151
503 c.h3 = 0x963877195940eabd
504 c.h4 = 0x96283ee2a88effe3
505 c.h5 = 0xbe5e1e2553863992
506 c.h6 = 0x2b0199fc2c85b8aa
507 c.h7 = 0x0eb72ddc81c52ca2
508 c.idx = 0
509 c.bit_len = 0
510 return 0
511}
512
513func sha512_256_digest(bytes: *u8, n: i64, out: *u8) -> i64 {
514 let c_raw: *u8 = sys_mmap(512)
515 let c: *Sha512 = c_raw as *Sha512
516 let full: *u8 = sys_mmap(64)
517 sha512_256_init(c)
518 sha512_update(c, bytes, n)
519 sha512_final(c, full)
520 var i: i64 = 0
521 while i < 32 { out[i] = full[i]; i = i + 1 }
522 return 0
523}
524
525// Compile-only smoke. Real KAT ("abc" -> ddaf35a193617aba...)
526// validation pending execution harness.
527func main() -> i64 {
528 let msg: *u8 = "abc"
529 let d: *u8 = sys_mmap(64)
530 sha512_digest(msg, 3, d)
531 return d[0] as i64
532}