open
https://gitlab.synchro.net/main/sbbs/-/issues/1193
## Summary
`File` exposes streaming, native hashes for MD5 and SHA-1 but nothing for SHA-256:
| Property | Added | Notes |
|---|---|---|
| `File.md5_hex` / `File.md5_base64` | 3.11 | streams the file |
| `File.sha1_hex` / `File.sha1_base64` | 3.19 | streams the file |
| `File.sha256_hex` / `File.sha256_base64` | — | **missing** |
The `md5_calc()` / `sha1_calc()` globals do not fill the gap, because they take a *string*: hashing a 600 MB disc image with one means holding the whole image in memory. There is no `sha256_calc()` either.
SHA-256 is what upstream publishers actually distribute today — ScummVM ships a `.sha256` beside each archive, GitHub releases publish SHA-256 sums — so verifying a download against the publisher's own value currently requires hand-rolling the hash.
## Proposal
Add `File.sha256_hex` and `File.sha256_base64` alongside the SHA-1 pair in `js_file.cpp`. The existing block already handles `FILE_PROP_MD5_HEX` / `FILE_PROP_MD5_B64` / `FILE_PROP_SHA1_HEX` / `FILE_PROP_SHA1_B64` through one open/read-loop/close path, so this is three more `case` labels plus two entries in the property table — the streaming behavior comes for free.
A `sha256_calc(text[,hex])` global would round out the `md5_calc`/`sha1_calc` set, but it does **not** address the case above; if only one is added, the `File` properties are the useful one.
Minor API note, worth considering only if the global is added: `md5_calc(text[,hex=false])` defaults to returning **raw digest bytes**. Nearly every caller wants hex, and raw bytes in a JS string invite avoidable byte-handling mistakes. A new `sha256_calc()` might reasonably default `hex=true` despite the asymmetry.
## Current workaround
Ten `xtrn/` fetchers needed SHA-256 to verify downloaded game data against a pinned constant. They now share `exec/load/sha256.js`, which streams the file through cryptlib's `CryptContext(CryptContext.ALGO.SHA2)` in 64 KB chunks.
That helper is written as a **polyfill**, so this issue resolving requires no call-site changes anywhere:
```js
if (f.sha256_hex !== undefined)
return String(f.sha256_hex).toLowerCase();
// ...otherwise the CryptContext path
```
Installs without the native property keep working; installs with it take the native path automatically.
## Notes gathered while writing that helper
Two things that were misdocumented in the fetchers' comments, in case they are useful for the implementation or for JSDOCS:
- `CryptContext.ALGO.SHA2` is **SHA-256** specifically — a fixed 256-bit digest, not a family selector. Confirmed against the NIST vectors for `"abc"` and `""`.
- The idiomatic trailing `cc.encrypt("")` does **not** finalize the digest. Reading `.hashvalue` does: its getter calls `cryptEncrypt(p->ctx, p, 0)` before fetching the attribute. Hashing `"a"`,`"b"`,`"c"` with no final call yields the correct digest.
Binary handling through `File.read()` in `"rb"` mode was verified correct against `sha256sum` for a file spanning every byte value 0x00-0xFF plus embedded NULs, CR/LF and `0x1A`, and digests are identical at 64 KB and 7-byte chunk sizes.
— *Authored by Claude (Claude Code), on behalf of @rswindell*
--- SBBSecho 3.37-Linux
* Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)