nx_sha256.nx source
↩ module page · 549 lines · 24579 B
1// sha256.nx -- SHA-256 in pure NishiLang (Phase G9, FIPS 180-4).
2//
3// Canonical: this is the substrate-wide canonical SHA-256
4// implementation per [[feedback-no-tool-proliferation-bit-level]].
5// HMAC-SHA256 / HKDF-SHA256 / DRBG-SHA256 etc. compose THIS file's
6// sha256 primitive; they're distinct primitives (different specs:
7// FIPS 198-1 HMAC, RFC 5869 HKDF, NIST SP 800-90A DRBG) but all
8// share THIS sha256 as their SHA-256 backbone. Re-implementing
9// the SHA-256 K-table or round function inline is refused.
10//
11// license_tier: INDEPENDENT_REDERIVE
12// genealogy_id: international-research-sources/nist/fips_180_4
13//
14// Used for: content-addressed build artifacts (F6), session tokens
15// (rand.nx + sha256 = HMAC), TLS 1.3 handshake (G15), Git-style
16// object addressing, reproducible-build attestation.
17//
18// Implementation follows FIPS 180-4 section 6.2 exactly -- no
19// precomputed tables beyond the standard K[0..63] round constants.
20// Pure i64 arithmetic; all 32-bit ops masked with 0xFFFFFFFF.
21//
22// API:
23// sha256_init(*ctx) — reset a fresh Sha256 context
24// sha256_update(*ctx, *u8 bytes, len) — feed input chunks
25// sha256_final(*ctx, *u8 out32) — write 32-byte digest
26// sha256_digest(*u8 bytes, len, *u8 out32) — one-shot convenience
27//
28// The context is ~128 bytes: 8 words of hash state + 64-byte partial
29// block buffer + 8-byte length counter + an index. Caller allocates
30// (stack or heap) and passes pointer.
31//
32// nx_safety_envelope: (schema: nishi-library/seeds/safety-critical-standards.toml)
33// intended_use: "SHA-256 cryptographic hash -- HMAC + HKDF
34// + content-addressed storage + digital
35// signatures + Wheeler-DDC integrity chain"
36// sil_target: SIL3 (integrity primitive; collision or
37// preimage attack = signature forgery)
38// asil_target: QM
39// dal_target: DAL B
40// iec_62304_class: B
41// evidence: [no_floating_point, no_table_lookup,
42// bit_equal_reproducible,
43// FIPS_180-4_Sec_5_3_3_init_vector,
44// NIST_CAVP_test_vectors_VERIFIED,
45// constant_time_by_construction,
46// license_tier_INDEPENDENT_REDERIVE]
47// hazard_register: [bug-tape-length-extension-attack,
48// bug-tape-implementation-skipping-final-block,
49// bug-tape-state-not-cleared-after-use]
50// residual_risk: "Length-extension attack applies to raw
51// SHA-256. Callers MUST use HMAC-SHA-256
52// (nx_hmac) for keyed scenarios; never raw
53// SHA-256(key || msg). Substrate cannot
54// enforce this from the hash primitive's
55// boundary; it's a composition responsibility."
56// verdict: NOT_YET_EVALUATED
57
58import "nx_syscalls.nx"
59import "nx_bits.nx"
60const K_MAGIC_536870912: i64 = 536870912
61
62struct Sha256 {
63 // Hash state H[0..7] as i64 (low 32 bits used).
64 h0: i64, h1: i64, h2: i64, h3: i64,
65 h4: i64, h5: i64, h6: i64, h7: i64,
66
67 // Legacy byte-packed block fields (retained for struct-size
68 // compatibility; no longer the active buffer -- see bufptr below).
69 b0: i64, b1: i64, b2: i64, b3: i64,
70 b4: i64, b5: i64, b6: i64, b7: i64,
71
72 // Byte index into the 64-byte block (0..63) and total bits
73 // processed (for final padding).
74 idx: i64,
75 bit_len: i64,
76
77 // Scratch buffers allocated ONCE per context in sha256_init (the perf rewrite,
78 // 2026-06-10: kills the per-block mmap syscall + the per-round K if-chain +
79 // the linear-scan byte access). ALL three are indexed only by PUBLIC counters
80 // (byte position / round number 0..63 / schedule index) -- never by secret data --
81 // so constant_time_by_construction is preserved (no secret-indexed table access).
82 bufptr: i64, // -> 64-byte contiguous block buffer (O(1) byte access)
83 kptr: i64, // -> 64 round constants, materialized once from sha256_k()
84 wptr: i64, // -> 64-word message schedule, reused every block
85
86 // Hardware SHA-NI scratch (the perf path, 2026-07-02). Allocated once per ctx.
87 // k32ptr -> 64 CONTIGUOUS i32 round constants (the SHA-NI intrinsic wants packed 32-bit
88 // K, whereas kptr above is 64 i64 for the software loop). st8ptr -> 8 CONTIGUOUS i32
89 // working state a..h, marshalled from h0..h7 around each __sha256_ni_block call. ni_ok
90 // caches the CPUID SHA-feature probe (1=use hardware, 0=software fallback) so cpuid runs
91 // once per hash, not once per block. All three are indexed only by PUBLIC counters ->
92 // constant_time_by_construction is preserved.
93 k32ptr: i64, // -> 64 i32 round constants (packed), for the SHA-NI intrinsic
94 st8ptr: i64, // -> 8 i32 working state a..h, marshalled around __sha256_ni_block
95 ni_ok: i64, // 1 = CPU has SHA-NI (cpuid(7,0):EBX bit-29) -> hardware compress; 0 = software
96}
97
98// Mask utilities.
99const M32: i64 = 0xFFFFFFFF
100
101// Delegated to nx_bits_rotr32. SHA-256 round does 6 rotates per
102// word * 64 rounds = 384 rotates per block.
103func rotr32(x: i64, n: i64) -> i64 {
104 return nx_bits_rotr32(x, n)
105}
106
107func shr32(x: i64, n: i64) -> i64 {
108 return (x >> n) & M32
109}
110
111// SHA-256 round constants K[0..63]. Standard cube-roots-of-primes.
112// We encode as a simple index -> constant lookup; each returns the
113// i64 with the 32-bit constant in the low bits.
114func sha256_k(i: i64) -> i64 {
115 if i == 0 { return 0x428a2f98 }
116 if i == 1 { return 0x71374491 }
117 if i == 2 { return 0xb5c0fbcf }
118 if i == 3 { return 0xe9b5dba5 }
119 if i == 4 { return 0x3956c25b }
120 if i == 5 { return 0x59f111f1 }
121 if i == 6 { return 0x923f82a4 }
122 if i == 7 { return 0xab1c5ed5 }
123 if i == 8 { return 0xd807aa98 }
124 if i == 9 { return 0x12835b01 }
125 if i == 10 { return 0x243185be }
126 if i == 11 { return 0x550c7dc3 }
127 if i == 12 { return 0x72be5d74 }
128 if i == 13 { return 0x80deb1fe }
129 if i == 14 { return 0x9bdc06a7 }
130 if i == 15 { return 0xc19bf174 }
131 if i == 16 { return 0xe49b69c1 }
132 if i == 17 { return 0xefbe4786 }
133 if i == 18 { return 0x0fc19dc6 }
134 if i == 19 { return 0x240ca1cc }
135 if i == 20 { return 0x2de92c6f }
136 if i == 21 { return 0x4a7484aa }
137 if i == 22 { return 0x5cb0a9dc }
138 if i == 23 { return 0x76f988da }
139 if i == 24 { return 0x983e5152 }
140 if i == 25 { return 0xa831c66d }
141 if i == 26 { return 0xb00327c8 }
142 if i == 27 { return 0xbf597fc7 }
143 if i == 28 { return 0xc6e00bf3 }
144 if i == 29 { return 0xd5a79147 }
145 if i == 30 { return 0x06ca6351 }
146 if i == 31 { return 0x14292967 }
147 if i == 32 { return 0x27b70a85 }
148 if i == 33 { return 0x2e1b2138 }
149 if i == 34 { return 0x4d2c6dfc }
150 if i == 35 { return 0x53380d13 }
151 if i == 36 { return 0x650a7354 }
152 if i == 37 { return 0x766a0abb }
153 if i == 38 { return 0x81c2c92e }
154 if i == 39 { return 0x92722c85 }
155 if i == 40 { return 0xa2bfe8a1 }
156 if i == 41 { return 0xa81a664b }
157 if i == 42 { return 0xc24b8b70 }
158 if i == 43 { return 0xc76c51a3 }
159 if i == 44 { return 0xd192e819 }
160 if i == 45 { return 0xd6990624 }
161 if i == 46 { return 0xf40e3585 }
162 if i == 47 { return 0x106aa070 }
163 if i == 48 { return 0x19a4c116 }
164 if i == 49 { return 0x1e376c08 }
165 if i == 50 { return 0x2748774c }
166 if i == 51 { return 0x34b0bcb5 }
167 if i == 52 { return 0x391c0cb3 }
168 if i == 53 { return 0x4ed8aa4a }
169 if i == 54 { return 0x5b9cca4f }
170 if i == 55 { return 0x682e6ff3 }
171 if i == 56 { return 0x748f82ee }
172 if i == 57 { return 0x78a5636f }
173 if i == 58 { return 0x84c87814 }
174 if i == 59 { return 0x8cc70208 }
175 if i == 60 { return 0x90befffa }
176 if i == 61 { return 0xa4506ceb }
177 if i == 62 { return 0xbef9a3f7 }
178 if i == 63 { return 0xc67178f2 }
179 return 0
180}
181
182// Access byte n (0..63) of the current block buffer. O(1) -- the buffer is contiguous
183// (was a linear scan over 8 byte-packed fields per access; n is a public position).
184func blk_byte(c: *Sha256, n: i64) -> i64 {
185 let p: *u8 = c.bufptr as *u8
186 return p[n] as i64
187}
188
189// Set byte n (0..63) of the current block buffer. O(1) contiguous store.
190func blk_set_byte(c: *Sha256, n: i64, v: i64) -> i64 {
191 let p: *u8 = c.bufptr as *u8
192 p[n] = (v & 0xFF) as u8
193 return 0
194}
195
196// Pack bytes [4*i .. 4*i+4) of the current block into a 32-bit
197// big-endian word (SHA-256 spec is big-endian).
198func blk_word(c: *Sha256, i: i64) -> i64 {
199 let off: i64 = i * 4
200 let b0: i64 = blk_byte(c, off + 0)
201 let b1: i64 = blk_byte(c, off + 1)
202 let b2: i64 = blk_byte(c, off + 2)
203 let b3: i64 = blk_byte(c, off + 3)
204 return ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3) & M32
205}
206
207// Hardware SHA-NI block compression: marshal the working state h0..h7 into the 8-word i32
208// buffer, run one full SHA-256 block via the fused __sha256_ni_block intrinsic (which reads
209// the 64 raw big-endian bytes at bufptr and the packed i32 K table), marshal the updated
210// state back. Bit-identical to sha256_compress_sw (validated by nx_shani_block_probe against
211// the software oracle for many blocks + the NIST KAT). ~hardware speed vs the ~40 MB/s soft path.
212func sha256_compress_ni(c: *Sha256) -> i64 {
213 let st: *i32 = c.st8ptr as *i32
214 st[0] = (c.h0 & M32) as i32; st[1] = (c.h1 & M32) as i32
215 st[2] = (c.h2 & M32) as i32; st[3] = (c.h3 & M32) as i32
216 st[4] = (c.h4 & M32) as i32; st[5] = (c.h5 & M32) as i32
217 st[6] = (c.h6 & M32) as i32; st[7] = (c.h7 & M32) as i32
218 let _r: i64 = __sha256_ni_block(c.st8ptr as *u8, c.bufptr as *u8, c.k32ptr as *u8)
219 c.h0 = (st[0] as i64) & M32; c.h1 = (st[1] as i64) & M32
220 c.h2 = (st[2] as i64) & M32; c.h3 = (st[3] as i64) & M32
221 c.h4 = (st[4] as i64) & M32; c.h5 = (st[5] as i64) & M32
222 c.h6 = (st[6] as i64) & M32; c.h7 = (st[7] as i64) & M32
223 return 0
224}
225
226// MULTI-BLOCK SHA-NI (2026-07-02, organ-level, NO new intrinsic): marshal state -> i32 buffer ONCE,
227// run __sha256_ni_block over `nblk` consecutive 64-byte blocks read DIRECTLY from `blocks` (state
228// stays resident in st8ptr between calls), marshal back ONCE. Eliminates the per-block marshal AND
229// the per-byte blk_set_byte buffering of the byte-at-a-time path -> the SHA-NI GB/s lever. Each
230// __sha256_ni_block is the identical proven compression -> bit-identical to N separate compresses.
231func sha256_compress_ni_blocks(c: *Sha256, blocks: i64, nblk: i64) -> i64 {
232 let st: *i32 = c.st8ptr as *i32
233 st[0] = (c.h0 & M32) as i32; st[1] = (c.h1 & M32) as i32
234 st[2] = (c.h2 & M32) as i32; st[3] = (c.h3 & M32) as i32
235 st[4] = (c.h4 & M32) as i32; st[5] = (c.h5 & M32) as i32
236 st[6] = (c.h6 & M32) as i32; st[7] = (c.h7 & M32) as i32
237 var b: i64 = 0
238 while b < nblk {
239 let blkp: i64 = blocks + b * 64
240 let _r: i64 = __sha256_ni_block(c.st8ptr as *u8, blkp as *u8, c.k32ptr as *u8)
241 b = b + 1
242 }
243 c.h0 = (st[0] as i64) & M32; c.h1 = (st[1] as i64) & M32
244 c.h2 = (st[2] as i64) & M32; c.h3 = (st[3] as i64) & M32
245 c.h4 = (st[4] as i64) & M32; c.h5 = (st[5] as i64) & M32
246 c.h6 = (st[6] as i64) & M32; c.h7 = (st[7] as i64) & M32
247 return 0
248}
249
250// One compression function call: process the 64 bytes currently in the block buffer.
251// Mutates c.h0..c.h7. Routes to hardware SHA-NI when the CPU supports it (probed once in
252// sha256_init -> c.ni_ok); the pure-integer software path below stays the ORACLE/fallback.
253func sha256_compress(c: *Sha256) -> i64 {
254 if c.ni_ok == 1 { return sha256_compress_ni(c) }
255 // Message schedule W[0..63] + round constants K[0..63] -- both per-ctx scratch
256 // (allocated once in sha256_init), so no per-block mmap syscall and no K if-chain.
257 let w: *i64 = c.wptr as *i64
258 let k: *i64 = c.kptr as *i64
259 var i: i64 = 0
260 while i < 16 {
261 w[i] = blk_word(c, i)
262 i = i + 1
263 }
264 i = 16
265 while i < 64 {
266 let x15: i64 = w[i - 15]
267 let x2: i64 = w[i - 2]
268 // sigma0/sigma1 with the rotates inlined (was 4 rotr32 calls/iter -> pure arithmetic)
269 let s0: i64 = (((x15 >> 7) | (x15 << 25)) ^ ((x15 >> 18) | (x15 << 14)) ^ (x15 >> 3)) & M32
270 let s1: i64 = (((x2 >> 17) | (x2 << 15)) ^ ((x2 >> 19) | (x2 << 13)) ^ (x2 >> 10)) & M32
271 w[i] = (w[i - 16] + s0 + w[i - 7] + s1) & M32
272 i = i + 1
273 }
274 var a: i64 = c.h0
275 var b: i64 = c.h1
276 var cc: i64 = c.h2
277 var d: i64 = c.h3
278 var e: i64 = c.h4
279 var ff: i64 = c.h5
280 var g: i64 = c.h6
281 var h: i64 = c.h7
282 i = 0
283 while i < 64 {
284 // Sigma1(e), Sigma0(a) with rotates inlined (was 6 rotr32 calls/round)
285 let S1: i64 = (((e >> 6) | (e << 26)) ^ ((e >> 11) | (e << 21)) ^ ((e >> 25) | (e << 7))) & M32
286 let ch: i64 = ((e & ff) ^ ((e ^ M32) & g)) & M32
287 let t1: i64 = (h + S1 + ch + k[i] + w[i]) & M32
288 let S0: i64 = (((a >> 2) | (a << 30)) ^ ((a >> 13) | (a << 19)) ^ ((a >> 22) | (a << 10))) & M32
289 let mj: i64 = ((a & b) ^ (a & cc) ^ (b & cc)) & M32
290 let t2: i64 = (S0 + mj) & M32
291 h = g
292 g = ff
293 ff = e
294 e = (d + t1) & M32
295 d = cc
296 cc = b
297 b = a
298 a = (t1 + t2) & M32
299 i = i + 1
300 }
301 c.h0 = (c.h0 + a) & M32
302 c.h1 = (c.h1 + b) & M32
303 c.h2 = (c.h2 + cc) & M32
304 c.h3 = (c.h3 + d) & M32
305 c.h4 = (c.h4 + e) & M32
306 c.h5 = (c.h5 + ff) & M32
307 c.h6 = (c.h6 + g) & M32
308 c.h7 = (c.h7 + h) & M32
309 return 0
310}
311
312// Initialise state. H[0..7] values from FIPS 180-4 section 5.3.3
313// (first 32 bits of fractional parts of square roots of first 8
314// primes).
315// Shared allocation-free initializer: all five scratch pointers are supplied by the owning path.
316func sha256_seed_allocated(c: *Sha256) -> i64 {
317 c.h0 = 0x6a09e667; c.h1 = 0xbb67ae85; c.h2 = 0x3c6ef372; c.h3 = 0xa54ff53a
318 c.h4 = 0x510e527f; c.h5 = 0x9b05688c; c.h6 = 0x1f83d9ab; c.h7 = 0x5be0cd19
319 // Per-ctx scratch, allocated once (amortized over every block of this hash):
320 // Materialize the canonical K table once (sha256_k stays the single source of the
321 // constants -- DRY; the if-chain now runs 64x per HASH, not 64x per BLOCK).
322 let kp: *i64 = c.kptr as *i64
323 var i: i64 = 0
324 while i < 64 { kp[i] = sha256_k(i); i = i + 1 }
325 let bp: *u8 = c.bufptr as *u8
326 i = 0
327 while i < 64 { bp[i] = 0 as u8; i = i + 1 }
328 c.idx = 0
329 c.bit_len = 0
330
331 // ---- Hardware SHA-NI setup (additive; software path is the oracle/fallback) ----
332 // Packed i32 K table for the intrinsic + an 8-word i32 state marshalling buffer.
333 let k32: *i32 = c.k32ptr as *i32
334 i = 0
335 while i < 64 { k32[i] = (sha256_k(i) & M32) as i32; i = i + 1 }
336 // Probe CPU SHA support ONCE per context: cpuid(leaf=7, subleaf=0):EBX bit-29 = SHA.
337 // 1<<29 = 0x20000000 = 536870912. Gate the compress path on this; a CPU without SHA-NI
338 // transparently uses the software compression (byte-identical result, just slower).
339 if (__cpuid_ebx(7, 0) & K_MAGIC_536870912) != 0 { c.ni_ok = 1 } else { c.ni_ok = 0 }
340 return 0
341}
342
343func sha256_init(c: *Sha256) -> i64 {
344 c.bufptr = sys_mmap(64) as i64
345 c.kptr = sys_mmap(64 * 8) as i64
346 c.wptr = sys_mmap(64 * 8) as i64
347 c.k32ptr = sys_mmap(64 * 4) as i64
348 c.st8ptr = sys_mmap(8 * 4) as i64
349 return sha256_seed_allocated(c)
350}
351
352// Feed `n` bytes. Buffers partial blocks; compresses full blocks
353// as soon as they fill.
354func sha256_update(c: *Sha256, bytes: *u8, n: i64) -> i64 {
355 var i: i64 = 0
356 // BULK FAST PATH: when block-aligned (idx==0) and SHA-NI is available, process all full 64-byte
357 // blocks straight from the input via the resident-state multi-block compress -- skipping both the
358 // byte-at-a-time blk_set_byte buffering and the per-block state marshalling. Bit-identical.
359 if c.idx == 0 {
360 if c.ni_ok == 1 {
361 let nblk: i64 = n / 64
362 if nblk > 0 {
363 sha256_compress_ni_blocks(c, (bytes as i64) + i, nblk)
364 c.bit_len = c.bit_len + nblk * 512
365 i = i + nblk * 64
366 }
367 }
368 }
369 while i < n {
370 blk_set_byte(c, c.idx, bytes[i])
371 c.idx = c.idx + 1
372 c.bit_len = c.bit_len + 8
373 if c.idx == 64 {
374 sha256_compress(c)
375 c.idx = 0
376 }
377 i = i + 1
378 }
379 return 0
380}
381
382// Finalise: append 0x80, pad with zeros, append 8-byte bit length,
383// then do one or two final compressions. Writes 32 bytes to `out`.
384func sha256_final(c: *Sha256, out: *u8) -> i64 {
385 // Remember total bit length before padding.
386 let total_bits: i64 = c.bit_len
387 // Append 0x80.
388 blk_set_byte(c, c.idx, 0x80)
389 c.idx = c.idx + 1
390 // If not enough room for 8-byte length in this block, pad rest
391 // with zeros + compress.
392 if c.idx > 56 {
393 while c.idx < 64 {
394 blk_set_byte(c, c.idx, 0)
395 c.idx = c.idx + 1
396 }
397 sha256_compress(c)
398 c.idx = 0
399 }
400 // Pad zeros up to byte 56.
401 while c.idx < 56 {
402 blk_set_byte(c, c.idx, 0)
403 c.idx = c.idx + 1
404 }
405 // Write 64-bit big-endian length in bytes 56..63.
406 blk_set_byte(c, 56, (total_bits >> 56) & 0xFF)
407 blk_set_byte(c, 57, (total_bits >> 48) & 0xFF)
408 blk_set_byte(c, 58, (total_bits >> 40) & 0xFF)
409 blk_set_byte(c, 59, (total_bits >> 32) & 0xFF)
410 blk_set_byte(c, 60, (total_bits >> 24) & 0xFF)
411 blk_set_byte(c, 61, (total_bits >> 16) & 0xFF)
412 blk_set_byte(c, 62, (total_bits >> 8) & 0xFF)
413 blk_set_byte(c, 63, total_bits & 0xFF)
414 sha256_compress(c)
415 // Emit H[0..7] as big-endian 4-byte words.
416 out[0] = (c.h0 >> 24) & 0xFF
417 out[1] = (c.h0 >> 16) & 0xFF
418 out[2] = (c.h0 >> 8) & 0xFF
419 out[3] = c.h0 & 0xFF
420 out[4] = (c.h1 >> 24) & 0xFF
421 out[5] = (c.h1 >> 16) & 0xFF
422 out[6] = (c.h1 >> 8) & 0xFF
423 out[7] = c.h1 & 0xFF
424 out[8] = (c.h2 >> 24) & 0xFF
425 out[9] = (c.h2 >> 16) & 0xFF
426 out[10] = (c.h2 >> 8) & 0xFF
427 out[11] = c.h2 & 0xFF
428 out[12] = (c.h3 >> 24) & 0xFF
429 out[13] = (c.h3 >> 16) & 0xFF
430 out[14] = (c.h3 >> 8) & 0xFF
431 out[15] = c.h3 & 0xFF
432 out[16] = (c.h4 >> 24) & 0xFF
433 out[17] = (c.h4 >> 16) & 0xFF
434 out[18] = (c.h4 >> 8) & 0xFF
435 out[19] = c.h4 & 0xFF
436 out[20] = (c.h5 >> 24) & 0xFF
437 out[21] = (c.h5 >> 16) & 0xFF
438 out[22] = (c.h5 >> 8) & 0xFF
439 out[23] = c.h5 & 0xFF
440 out[24] = (c.h6 >> 24) & 0xFF
441 out[25] = (c.h6 >> 16) & 0xFF
442 out[26] = (c.h6 >> 8) & 0xFF
443 out[27] = c.h6 & 0xFF
444 out[28] = (c.h7 >> 24) & 0xFF
445 out[29] = (c.h7 >> 16) & 0xFF
446 out[30] = (c.h7 >> 8) & 0xFF
447 out[31] = c.h7 & 0xFF
448 return 0
449}
450
451// One-shot: hash `n` bytes, write 32-byte digest to `out`.
452// Release only scratch owned by this initialized context; the caller owns c.
453// Reset pointers so explicit cleanup is safe to repeat after completion/failure.
454func sha256_destroy(c: *Sha256) -> i64 {
455 if c.bufptr!=0 { sys_munmap(c.bufptr as *u8,64);c.bufptr=0 }
456 if c.kptr!=0 { sys_munmap(c.kptr as *u8,64*8);c.kptr=0 }
457 if c.wptr!=0 { sys_munmap(c.wptr as *u8,64*8);c.wptr=0 }
458 if c.k32ptr!=0 { sys_munmap(c.k32ptr as *u8,64*4);c.k32ptr=0 }
459 if c.st8ptr!=0 { sys_munmap(c.st8ptr as *u8,8*4);c.st8ptr=0 }
460 return 0
461}
462
463func sha256_digest(bytes: *u8, n: i64, out: *u8) -> i64 {
464 let ctx_raw: *u8 = sys_mmap(__size_of(Sha256))
465 let ctx: *Sha256 = ctx_raw as *Sha256
466 sha256_init(ctx)
467 sha256_update(ctx, bytes, n)
468 sha256_final(ctx, out)
469 sha256_destroy(ctx)
470 sys_munmap(ctx_raw,__size_of(Sha256))
471 return 0
472}
473
474// Native Linux x86-64 checked observation path using the existing shared allocator owner.
475// No cross-backend portability claim: sys_munmap currently uses the native x86-64 release ABI.
476// Synchronous caller-owned scratch: do not publish it or pass it to forked children.
477const SHA256_WORD_ALIGN: i64 = 8
478const SHA256_BLOCK_BYTES: i64 = 64
479const SHA256_ROUND_WORDS: i64 = 64
480const SHA256_WIDE_WORD: i64 = 8
481const SHA256_PACKED_WORD: i64 = 4
482const SHA256_STATE_WORDS: i64 = 8
483const SHA256_DIGEST_BYTES: i64 = 32
484const SHA256_SIGNED_MAX: i64 = 9223372036854775807
485const SHA256_BITS_PER_BYTE: i64 = 8
486const SHA256_E_INPUT: i64 = 0-1
487const SHA256_E_WORKSPACE: i64 = 0-2
488const SHA256_E_MAPPING: i64 = 0-3
489const SHA256_E_RELEASE: i64 = 0-4
490
491func sha256_context_aligned_bytes() -> i64 {
492 return ((__size_of(Sha256)+SHA256_WORD_ALIGN-1)/SHA256_WORD_ALIGN)*SHA256_WORD_ALIGN
493}
494func sha256_workspace_bytes() -> i64 {
495 return sha256_context_aligned_bytes()+SHA256_BLOCK_BYTES+2*SHA256_ROUND_WORDS*SHA256_WIDE_WORD+SHA256_ROUND_WORDS*SHA256_PACKED_WORD+SHA256_STATE_WORDS*SHA256_PACKED_WORD
496}
497func sha256_checked_input(bytes: *u8, n: i64, out: *u8) -> i64 {
498 if n < 0 || n > SHA256_SIGNED_MAX/SHA256_BITS_PER_BYTE { return 0 }
499 let source: i64=bytes as i64; let target: i64=out as i64
500 if source < 0 || (n > 0 && source == 0) || source > SHA256_SIGNED_MAX-n { return 0 }
501 if target <= 0 || target > SHA256_SIGNED_MAX-SHA256_DIGEST_BYTES { return 0 }
502 return 1
503}
504func sha256_ranges_overlap(a: i64, an: i64, b: i64, bn: i64) -> i64 {
505 if an == 0 || bn == 0 { return 0 }; return a < b+bn && b < a+an
506}
507// Borrowed scratch. Never call sha256_destroy: the buffers share one allocation.
508// No allocation/release occurs here. Refused boundary inputs leave output unchanged.
509// Initialize caller-owned scratch for incremental update/final; never call destroy on it.
510func sha256_init_workspace(workspace: *u8, capacity: i64) -> i64 {
511 let base: i64=workspace as i64; let needed: i64=sha256_workspace_bytes()
512 if base <= 0 || capacity < needed || base > SHA256_SIGNED_MAX-needed || base%SHA256_WORD_ALIGN != 0 { return SHA256_E_WORKSPACE }
513 let ctx: *Sha256=workspace as *Sha256; var p: i64=base+sha256_context_aligned_bytes()
514 ctx.bufptr=p; p=p+SHA256_BLOCK_BYTES
515 ctx.kptr=p; p=p+SHA256_ROUND_WORDS*SHA256_WIDE_WORD
516 ctx.wptr=p; p=p+SHA256_ROUND_WORDS*SHA256_WIDE_WORD
517 ctx.k32ptr=p; p=p+SHA256_ROUND_WORDS*SHA256_PACKED_WORD
518 ctx.st8ptr=p
519 return sha256_seed_allocated(ctx)
520}
521func sha256_digest_workspace(bytes: *u8, n: i64, out: *u8, workspace: *u8, capacity: i64) -> i64 {
522 if sha256_checked_input(bytes,n,out) != 1 { return SHA256_E_INPUT }
523 let base: i64=workspace as i64; let needed: i64=sha256_workspace_bytes()
524 if base <= 0 || capacity < needed || base > SHA256_SIGNED_MAX-needed || base%SHA256_WORD_ALIGN != 0 { return SHA256_E_WORKSPACE }
525 if sha256_ranges_overlap(base,needed,bytes as i64,n) == 1 || sha256_ranges_overlap(base,needed,out as i64,SHA256_DIGEST_BYTES) == 1 { return SHA256_E_WORKSPACE }
526 let initialized:i64=sha256_init_workspace(workspace,capacity)
527 if initialized != 0 { return initialized }
528 let ctx:*Sha256=workspace as *Sha256
529 sha256_update(ctx,bytes,n); sha256_final(ctx,out)
530 return 0
531}
532// Takes ownership of an actual whole sys_mmap_shared(workspace_bytes()) result.
533// A failed mapping leaves output unchanged. A release failure may follow computed output;
534// callers must accept output only on0. Never supply an arena pointer or undersized mapping.
535func sha256_digest_mapping_native(bytes: *u8, n: i64, out: *u8, mapping: i64) -> i64 {
536 if mapping <= 0 { return SHA256_E_MAPPING }
537 let size: i64=sha256_workspace_bytes()
538 let result: i64=sha256_digest_workspace(bytes,n,out,mapping as *u8,size)
539 let released: i64=sys_munmap(mapping as *u8,size)
540 if result != 0 { return result }
541 if released != 0 { return SHA256_E_RELEASE }
542 return 0
543}
544func sha256_digest_checked_native(bytes: *u8, n: i64, out: *u8) -> i64 {
545 if sha256_checked_input(bytes,n,out) != 1 { return SHA256_E_INPUT }
546 // The existing shared wrapper returns errno; sys_mmap's failure policy is fatal.
547 let mapping: i64=sys_mmap_shared(sha256_workspace_bytes()) as i64
548 return sha256_digest_mapping_native(bytes,n,out,mapping)
549}