code wiki / (root) / nx_sha512.nx

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}