Add a pure-sx streaming SHA-256 (FIPS 180-4) stdlib module, importable as `#import "modules/std/hash.sx";`. All 32-bit word arithmetic is done in s64 and masked back with `& MASK32`, so digests are deterministic and platform-independent — no shelling out, no native crypto. API: - init() -> Sha256 (by-value *self pattern) - update(*Sha256, string) (multi-block + partial-block buffering) - final(*Sha256) -> string (32-byte digest as lowercase hex) - sha256_hex(string) -> string (one-shot) - sha256_file([:0]u8) -> ?string (digest of a file via fs.read_file) Verified against FIPS/NIST known-answer vectors and `shasum -a 256`: "" , "abc", the 56- and 112-byte multi-block vectors, 1000×'a', and the 64/65-byte block boundaries; chunked update() matches the one-shot call. examples/0710-modules-sha256.sx pins the KAT vectors + the streaming invariant; gate green (zig build, zig build test, run_examples 370/0/0/0).
38 lines
1.5 KiB
Plaintext
38 lines
1.5 KiB
Plaintext
// Streaming SHA-256 (FIPS 180-4) from `modules/std/hash.sx`.
|
|
//
|
|
// Known-answer vectors (empty, "abc", the 112-byte NIST multi-block
|
|
// vector) plus the streaming invariant: feeding the same bytes in
|
|
// several `update` chunks yields the same digest as the one-shot call.
|
|
|
|
#import "modules/std.sx";
|
|
#import "modules/std/hash.sx";
|
|
|
|
check :: (label: string, got: string, want: string) {
|
|
if got == want {
|
|
print("{}: ok\n", label);
|
|
} else {
|
|
print("{}: FAIL got {} want {}\n", label, got, want);
|
|
}
|
|
}
|
|
|
|
main :: () {
|
|
check("empty", sha256_hex(""),
|
|
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
|
|
check("abc", sha256_hex("abc"),
|
|
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
|
|
|
|
// 112-byte input — spans more than one 64-byte block.
|
|
multi := "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu";
|
|
check("multi", sha256_hex(multi),
|
|
"cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1");
|
|
|
|
// Streaming must equal one-shot regardless of chunk boundaries.
|
|
h := init();
|
|
h.update("abcdefghbcdefghicdefghijdefghijke"); // 33 bytes
|
|
h.update("fghijklfghijklmghijklmnhijklmno"); // crosses block edge
|
|
h.update("ijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
|
|
streamed := h.final();
|
|
check("stream-eq-oneshot",
|
|
if streamed == sha256_hex(multi) then "yes" else "no", "yes");
|
|
}
|