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