code wiki / (root) / sha512.nx

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}