docs/superpowers/plans/2026-05-29-hashes.md
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Wrap raylib's ComputeCRC32 / ComputeMD5 / ComputeSHA1 / ComputeSHA256 with safe Rust signatures over &[u8], returning canonical-byte-order digests. CRC32 is free-thread; the three crypto hashes require &RaylibThread to eliminate the static-buffer concurrent-call race.
Architecture: New focused module raylib/src/core/hashes.rs exposing 4 free functions + a private read_static_hash<const N, const M> const-generic helper that handles the static-buffer copy + byte-order conversion. MD5 stores words little-endian (RFC 1321); SHA-1 and SHA-256 big-endian (FIPS 180). CRC32 returns u32 directly with no static-buffer hazard.
Tech Stack: Rust 1.85 (edition 2024), no new dependencies (no error type — oversize input panics via assert!). Tier-1 unit tests for CRC32; Tier-2 tests for the crypto hashes via software_renderer + test_harness::with_headless.
Spec reference: docs/superpowers/specs/2026-05-29-hashes-design.md.
Files:
raylib/src/core/hashes.rsraylib/src/core/mod.rsraylib/src/prelude.rsThis task gets the module compiling with full implementations before any tests are written. The bodies are short enough that scaffolding stubs separately from implementing them would be over-ceremony — write them right the first time, validate via tests in later tasks.
pub mod declarations in raylib/src/core/mod.rsRun: grep -nE '^pub mod ' raylib/src/core/mod.rs
Expected: a block of pub mod lines. Note where pixel was added (the just-completed workstream) and slot hashes alphabetically before input or wherever fits the existing convention.
pub use crate::core::xxx::*; glob re-exports in raylib/src/prelude.rsRun: grep -nE 'pub use crate::core::' raylib/src/prelude.rs | head -10
Expected: a series of glob re-exports. The just-added pixel is at around line 63; add the new hashes glob in alphabetical order.
raylib/src/core/hashes.rs with the full module bodyWrite the file with this exact content:
//! Compute CRC32, MD5, SHA-1, and SHA-256 hashes via raylib's built-in
//! implementations.
//!
//! For security-sensitive use cases (password hashing, signatures,
//! integrity verification against an adversary), prefer the
//! well-vetted crates from the [RustCrypto] organization:
//!
//! - [`crc32fast`] for CRC32
//! - [`md-5`] for MD5 (note: MD5 is cryptographically broken)
//! - [`sha1`] for SHA-1 (also broken)
//! - [`sha2`] for SHA-256
//!
//! These wrappers are appropriate for non-security uses: file
//! integrity (against bit-rot, not against an attacker), deterministic
//! asset IDs, content-addressed caches.
//!
//! # Thread safety
//!
//! [`compute_crc32`] is pure compute (no shared state) and safe to
//! call from any thread. The three crypto-hash functions write into a
//! shared static buffer inside raylib; they require [`RaylibThread`]
//! to pin the call to the raylib thread, eliminating concurrent-call
//! races.
//!
//! [RustCrypto]: https://github.com/RustCrypto
//! [`crc32fast`]: https://crates.io/crates/crc32fast
//! [`md-5`]: https://crates.io/crates/md-5
//! [`sha1`]: https://crates.io/crates/sha1
//! [`sha2`]: https://crates.io/crates/sha2
use crate::core::RaylibThread;
use crate::ffi;
/// Read `N` u32 words from raylib's static-buffer pointer immediately
/// after the FFI call and return them as canonical-order bytes.
///
/// `BE` selects the byte order: pass `true` for SHA-1 / SHA-256, `false`
/// for MD5.
///
/// # Safety
///
/// `ptr` must come directly from a `Compute{MD5,SHA1,SHA256}` call and
/// must not have been invalidated by another Compute* call from this
/// thread. The caller's `&RaylibThread` capability prevents concurrent
/// calls from other threads. The pointer is 4-byte aligned (`static
/// unsigned int hash[N]`).
unsafe fn read_static_hash<const N: usize, const M: usize>(
ptr: *const u32,
be: bool,
) -> [u8; M] {
assert_eq!(M, N * 4, "read_static_hash: M must equal N * 4");
// SAFETY: caller guarantees ptr points at a valid `static unsigned int
// hash[N]` from raylib; we read N u32s and convert to bytes immediately.
let words: &[u32] = unsafe { std::slice::from_raw_parts(ptr, N) };
let mut out = [0u8; M];
for (i, &w) in words.iter().enumerate() {
let bytes = if be { w.to_be_bytes() } else { w.to_le_bytes() };
out[i * 4..(i + 1) * 4].copy_from_slice(&bytes);
}
out
}
/// Compute the CRC32 hash of `data` using the CRC-32/ISO-HDLC variant
/// (the same one used by zlib / PNG / gzip / Ethernet).
///
/// Pure compute — no shared state, safe to call from any thread.
///
/// # Panics
///
/// Panics if `data.len() > i32::MAX` (raylib's `dataSize` is `int`).
pub fn compute_crc32(data: &[u8]) -> u32 {
assert!(
data.len() <= i32::MAX as usize,
"input length {} exceeds raylib's i32 dataSize limit",
data.len(),
);
// SAFETY: ComputeCRC32 takes `unsigned char *` but only reads from
// `data` (the cast `*const u8 → *mut u8` is sound — no write side).
// Returns the u32 directly; no static-buffer concerns.
unsafe { ffi::ComputeCRC32(data.as_ptr() as *mut _, data.len() as i32) }
}
/// Compute the MD5 hash of `data`. Returns the 16-byte digest in the
/// canonical byte order (the same bytes as the standard 32-hex-char
/// representation).
///
/// # Security
///
/// MD5 is cryptographically broken — do **not** use it for password
/// hashing, signatures, or any adversarial integrity check. Use the
/// [`md-5`] crate from RustCrypto if you need a vetted, streaming
/// implementation.
///
/// This wrapper is appropriate for non-security uses: deterministic
/// asset IDs, content-addressed caches, file deduplication where the
/// adversary is bit-rot rather than a human.
///
/// # Thread safety
///
/// raylib's `ComputeMD5` writes its result into a process-wide static
/// buffer; the safe wrapper copies the bytes out immediately, but
/// concurrent calls from multiple threads would race against each
/// other's static. The [`RaylibThread`] parameter pins the call to
/// the raylib thread, eliminating the race.
///
/// # Panics
///
/// Panics if `data.len() > i32::MAX`.
///
/// [`md-5`]: https://crates.io/crates/md-5
pub fn compute_md5(_thread: &RaylibThread, data: &[u8]) -> [u8; 16] {
assert!(
data.len() <= i32::MAX as usize,
"input length {} exceeds raylib's i32 dataSize limit",
data.len(),
);
// SAFETY: ComputeMD5 takes `unsigned char *` but only reads from data
// (verified at raylib-sys/raylib/src/rcore.c:3209). Returns a pointer
// to `static unsigned int hash[4]` (rcore.c:3213). We hold
// `&RaylibThread` so concurrent overwrites from other threads are
// impossible; we copy out into an owned array before this fn returns.
let ptr = unsafe {
ffi::ComputeMD5(data.as_ptr() as *mut _, data.len() as i32)
};
// SAFETY: ptr is the static-hash pointer just returned; valid for the
// immediate read. be = false → MD5 is little-endian per RFC 1321.
unsafe { read_static_hash::<4, 16>(ptr, false) }
}
/// Compute the SHA-1 hash of `data`. Returns the 20-byte digest in
/// the canonical byte order.
///
/// # Security
///
/// SHA-1 is cryptographically broken — do **not** use it for
/// security-sensitive purposes. Use the [`sha1`] crate from
/// RustCrypto. This wrapper is appropriate for non-security uses
/// (asset IDs, caches, content-addressed storage with no adversary).
///
/// # Thread safety + # Panics
///
/// Same as [`compute_md5`].
///
/// [`sha1`]: https://crates.io/crates/sha1
pub fn compute_sha1(_thread: &RaylibThread, data: &[u8]) -> [u8; 20] {
assert!(
data.len() <= i32::MAX as usize,
"input length {} exceeds raylib's i32 dataSize limit",
data.len(),
);
// SAFETY: ComputeSHA1 takes `unsigned char *` but only reads (verified
// at rcore.c:3327-ish). Returns a pointer to `static unsigned int
// hash[5]` (rcore.c:3331). Thread witness prevents concurrent
// overwrites; we copy out immediately.
let ptr = unsafe {
ffi::ComputeSHA1(data.as_ptr() as *mut _, data.len() as i32)
};
// SAFETY: ptr is the static-hash pointer just returned. be = true →
// SHA-1 uses big-endian word storage per FIPS 180-1.
unsafe { read_static_hash::<5, 20>(ptr, true) }
}
/// Compute the SHA-256 hash of `data`. Returns the 32-byte digest in
/// the canonical byte order.
///
/// # Security
///
/// raylib's SHA-256 implementation is **not constant-time** and has
/// not been audited for side-channel resistance. For
/// security-sensitive use (password hashing, HMAC, MAC verification),
/// prefer the [`sha2`] crate from RustCrypto.
///
/// # Thread safety + # Panics
///
/// Same as [`compute_md5`].
///
/// [`sha2`]: https://crates.io/crates/sha2
pub fn compute_sha256(_thread: &RaylibThread, data: &[u8]) -> [u8; 32] {
assert!(
data.len() <= i32::MAX as usize,
"input length {} exceeds raylib's i32 dataSize limit",
data.len(),
);
// SAFETY: ComputeSHA256 takes `unsigned char *` but only reads
// (verified at rcore.c:3458-ish). Returns a pointer to `static
// unsigned int hash[8]` (rcore.c:3462). Thread witness prevents
// concurrent overwrites; we copy out immediately.
let ptr = unsafe {
ffi::ComputeSHA256(data.as_ptr() as *mut _, data.len() as i32)
};
// SAFETY: ptr is the static-hash pointer just returned. be = true →
// SHA-256 uses big-endian word storage per FIPS 180-2.
unsafe { read_static_hash::<8, 32>(ptr, true) }
}
raylib/src/core/mod.rsFind the existing pub mod pixel; line (added in the just-completed workstream). Add pub mod hashes; immediately above it (alphabetical). Then add a corresponding pub use hashes::*; matching the re-export convention used by neighboring modules.
In raylib/src/prelude.rs, find the pub use crate::core::pixel::*; line (around line 63) and add pub use crate::core::hashes::*; immediately above it (alphabetical).
Run: cargo build -p raylib --features full 2>&1 | tail -10
Expected: Finished line, no error[...] lines. Compilation warnings about RaylibThread being unused inside the _thread parameter are silenced by the leading underscore — verify no warnings fire.
Run: cargo clippy -p raylib --features full -- -D warnings 2>&1 | tail -10
Expected: no warnings.
Lints to watch for:
clippy::cast_ref_to_mut on the as *mut _ casts of data.as_ptr() — same pattern as pixel-pointers. If it fires, add the #[allow] with a one-line comment pointing at the SAFETY note. Try without first.
clippy::needless_pass_by_value on _thread: &RaylibThread — won't fire because it's a reference, not by value.
Step 8: Commit Task 1
Run via the Bash tool:
git add raylib/src/core/hashes.rs raylib/src/core/mod.rs raylib/src/prelude.rs
git commit -m "$(cat <<'EOF'
feat(hashes): scaffold module with 4 public fns + read_static_hash helper
New raylib/src/core/hashes.rs surfaces:
- compute_crc32(data: &[u8]) -> u32
No thread witness — pure compute, no shared mutable state.
- compute_md5(thread: &RaylibThread, data: &[u8]) -> [u8; 16]
- compute_sha1(thread: &RaylibThread, data: &[u8]) -> [u8; 20]
- compute_sha256(thread: &RaylibThread, data: &[u8]) -> [u8; 32]
Three crypto hashes take &RaylibThread to pin the call to the
raylib thread, eliminating concurrent-call races against raylib's
static-buffer return (`static unsigned int hash[N]` at
rcore.c:3213/3331/3462).
- Private read_static_hash<const N, const M> helper handles the
static-buffer copy + byte-order conversion (be = false for MD5 per
RFC 1321; be = true for SHA-1/256 per FIPS 180).
Wired into raylib::core via `pub mod hashes;` + glob re-export, and
into raylib::prelude. Module-level rustdoc steers security-sensitive
callers at the RustCrypto crates (crc32fast / md-5 / sha1 / sha2)
and notes the thread-safety asymmetry between CRC32 (free-thread)
and the three crypto hashes.
Implementation is complete; tests follow in the next tasks.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
EOF
)"
Files:
raylib/src/core/hashes.rs (append a #[cfg(test)] mod tests block)CRC32 needs no thread witness, so its tests are pure Tier-1. Three known vectors validate the wrapper and the CRC-32/ISO-HDLC variant identification.
Append to raylib/src/core/hashes.rs:
#[cfg(test)]
mod tests {
use super::*;
/// Format bytes as a lowercase hex string for assertion convenience.
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
#[test]
fn crc32_known_vectors() {
// CRC-32/ISO-HDLC (zlib / PNG / gzip / Ethernet).
assert_eq!(compute_crc32(b""), 0x00000000);
assert_eq!(compute_crc32(b"123456789"), 0xCBF43926);
assert_eq!(
compute_crc32(b"The quick brown fox jumps over the lazy dog"),
0x414FA339,
);
}
}
Run: cargo test -p raylib --lib --features full crc32_known_vectors 2>&1 | tail -15
Expected: test result: ok. 1 passed; 0 failed.
If compute_crc32(b"123456789") != 0xCBF43926, raylib is using a different CRC variant than CRC-32/ISO-HDLC. The fix is either to update the rustdoc to name the actual variant, or to pre-process the input differently — but the table at rcore.c:3165 starts with 0x77073096, which IS the standard zlib polynomial reflected, so this should pass on first run.
git add raylib/src/core/hashes.rs
git commit -m "$(cat <<'EOF'
test(hashes): CRC32 known-vector coverage (Tier-1)
Three standard test vectors confirm raylib's ComputeCRC32 produces
CRC-32/ISO-HDLC (the variant used by zlib, PNG, gzip, Ethernet):
- empty input -> 0x00000000
- "123456789" -> 0xCBF43926 (the canonical CRC-32/ISO-HDLC vector)
- "The quick brown fox jumps over the lazy dog" -> 0x414FA339
CRC32 has no static-buffer hazard so the tests are Tier-1 (no
RaylibThread witness needed; no software_renderer feature gate).
Co-Authored-By: Claude Opus 4.7 <[email protected]>
EOF
)"
Files:
raylib/src/core/hashes.rs (append to mod tests)Verify the assert!(data.len() <= i32::MAX as usize) actually fires. We can't allocate i32::MAX + 1 bytes for a real test, but we can construct a &[u8] of that length via slice-from-raw-parts on a tiny backing buffer — the assertion in compute_crc32 only inspects data.len(), not the bytes themselves, so a fake-length slice is safe to construct in this narrow test context.
Append to mod tests:
#[test]
#[should_panic(expected = "exceeds raylib's i32 dataSize limit")]
fn crc32_panics_on_oversize_input() {
// Construct a fake-length slice without allocating i32::MAX bytes.
// SAFETY: the assert! in compute_crc32 inspects `data.len()` BEFORE
// any byte is read. The FFI call never happens (assertion panics
// first). The backing pointer is non-null but the length we're
// claiming makes any dereference UB — but no dereference occurs.
// This trick is only safe because the function panics before
// reading. Do NOT replicate this pattern outside the test.
let real_byte = 0u8;
let fake_slice: &[u8] = unsafe {
std::slice::from_raw_parts(&real_byte as *const u8, i32::MAX as usize + 1)
};
let _ = compute_crc32(fake_slice);
}
Run: cargo test -p raylib --lib --features full crc32_panics_on_oversize_input 2>&1 | tail -10
Expected: test result: ok. 1 passed; 0 failed (the #[should_panic] is satisfied because the assert fires).
If the test doesn't panic (i.e. it would fall into the FFI call with a wrong length), the assertion isn't actually guarding the boundary — verify the assert! in compute_crc32 actually reads data.len() before doing anything else.
git add raylib/src/core/hashes.rs
git commit -m "$(cat <<'EOF'
test(hashes): assert! panics on oversize input to compute_crc32
Confirms the `data.len() <= i32::MAX` guard fires before any FFI
call. Constructs a fake-length slice via slice::from_raw_parts on a
single-byte backing buffer; the trick is safe ONLY because the
function panics before any byte is read (documented in the test).
This regression-tests just the CRC32 path; the three crypto hashes
share the same assert pattern and are covered by inspection (the
assertion is identical in each).
Co-Authored-By: Claude Opus 4.7 <[email protected]>
EOF
)"
Files:
raylib/src/core/hashes.rs (append to mod tests)MD5 / SHA-1 / SHA-256 all need a &RaylibThread. We use test_harness::with_headless(1, 1, |_rl, thread| { ... }) which opens a 1×1 software-renderer window (no GPU required) just to obtain the thread witness. The software_renderer feature must be active for these tests to be visible.
Append to mod tests:
#[cfg(feature = "software_renderer")]
#[test]
fn md5_known_vectors() {
crate::test_harness::with_headless(1, 1, |_rl, thread| {
// RFC 1321 standard test vectors.
assert_eq!(
hex(&compute_md5(thread, b"")),
"d41d8cd98f00b204e9800998ecf8427e",
);
assert_eq!(
hex(&compute_md5(thread, b"abc")),
"900150983cd24fb0d6963f7d28e17f72",
);
assert_eq!(
hex(&compute_md5(thread, b"The quick brown fox jumps over the lazy dog")),
"9e107d9d372bb6826bd81d3542a419d6",
);
});
}
Run: cargo test -p raylib --lib --features full md5_known_vectors 2>&1 | tail -20
Expected: test result: ok. 1 passed; 0 failed.
If any vector mismatches, the byte order is wrong. Toggle be = true in the read_static_hash::<4, 16>(ptr, false) call (i.e. change the literal in compute_md5's body to read_static_hash::<4, 16>(ptr, true)) and re-run. If it then passes, MD5 in raylib happens to use big-endian word storage (contradicts RFC 1321 but possible if raylib's implementation is non-standard); update the SAFETY/byte-order comment in compute_md5 to document the actual byte order used.
git add raylib/src/core/hashes.rs
git commit -m "$(cat <<'EOF'
test(hashes): MD5 known-vector coverage (Tier-2)
Three RFC 1321 standard vectors run through compute_md5:
- MD5("") -> d41d8cd98f00b204e9800998ecf8427e
- MD5("abc") -> 900150983cd24fb0d6963f7d28e17f72
- MD5(quick brown fox) -> 9e107d9d372bb6826bd81d3542a419d6
Tier-2 via test_harness::with_headless(1, 1, ...) which opens a 1×1
software-renderer window just for the &RaylibThread witness; no
drawing happens. Gated on `feature = "software_renderer"`.
Validates the to_le_bytes byte-order conversion in
read_static_hash::<4, 16>.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
EOF
)"
Files:
raylib/src/core/hashes.rs (append to mod tests)Same pattern as Task 4 with SHA-1 vectors.
Append to mod tests:
#[cfg(feature = "software_renderer")]
#[test]
fn sha1_known_vectors() {
crate::test_harness::with_headless(1, 1, |_rl, thread| {
// FIPS 180-1 standard test vectors.
assert_eq!(
hex(&compute_sha1(thread, b"")),
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
);
assert_eq!(
hex(&compute_sha1(thread, b"abc")),
"a9993e364706816aba3e25717850c26c9cd0d89d",
);
assert_eq!(
hex(&compute_sha1(thread, b"The quick brown fox jumps over the lazy dog")),
"2fd4e1c67a2d28fced849ee1bb76e7391b93eb12",
);
});
}
Run: cargo test -p raylib --lib --features full sha1_known_vectors 2>&1 | tail -15
Expected: test result: ok. 1 passed; 0 failed.
If a vector mismatches, the byte-order toggle is the same fix: flip read_static_hash::<5, 20>(ptr, true) to false in compute_sha1's body and re-run. SHA-1 should be big-endian per FIPS 180-1.
git add raylib/src/core/hashes.rs
git commit -m "$(cat <<'EOF'
test(hashes): SHA-1 known-vector coverage (Tier-2)
Three FIPS 180-1 standard vectors:
- SHA1("") -> da39a3ee5e6b4b0d3255bfef95601890afd80709
- SHA1("abc") -> a9993e364706816aba3e25717850c26c9cd0d89d
- SHA1(quick brown fox) -> 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12
Same Tier-2 with_headless pattern as md5_known_vectors. Validates
the to_be_bytes byte-order conversion in
read_static_hash::<5, 20>.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
EOF
)"
Files:
raylib/src/core/hashes.rs (append to mod tests)Same pattern with SHA-256 vectors.
Append to mod tests:
#[cfg(feature = "software_renderer")]
#[test]
fn sha256_known_vectors() {
crate::test_harness::with_headless(1, 1, |_rl, thread| {
// FIPS 180-2 standard test vectors.
assert_eq!(
hex(&compute_sha256(thread, b"")),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
);
assert_eq!(
hex(&compute_sha256(thread, b"abc")),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
);
assert_eq!(
hex(&compute_sha256(thread, b"The quick brown fox jumps over the lazy dog")),
"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
);
});
}
Run: cargo test -p raylib --lib --features full sha256_known_vectors 2>&1 | tail -15
Expected: test result: ok. 1 passed; 0 failed.
Same byte-order toggle fix if any vector mismatches: flip read_static_hash::<8, 32>(ptr, true) to false in compute_sha256's body and re-run. SHA-256 should be big-endian per FIPS 180-2.
git add raylib/src/core/hashes.rs
git commit -m "$(cat <<'EOF'
test(hashes): SHA-256 known-vector coverage (Tier-2)
Three FIPS 180-2 standard vectors:
- SHA256("") -> e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
- SHA256("abc") -> ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
- SHA256(quick brown fox) -> d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592
Same Tier-2 with_headless pattern. Validates the to_be_bytes
byte-order conversion in read_static_hash::<8, 32>.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
EOF
)"
Files: none (verification only)
Run: cargo test -p raylib --lib --features full 2>&1 | tail -10
Expected: every test passes. The hashes module contributes 5 new #[test] fns (1 Tier-1 CRC32 vectors + 1 Tier-1 oversize panic + 3 Tier-2 crypto vectors). Pre-hashes total was 67; new total should be 72.
If any test failed, the diagnostics name the offending function/vector. Fix the byte-order toggle (Tasks 4-6 mention the fix) and re-run.
Run: cargo clippy -p raylib --features full -- -D warnings 2>&1 | tail -10
Expected: no warnings.
Run: RUSTDOCFLAGS="-Dwarnings" cargo doc -p raylib --features full --no-deps 2>&1 | tail -10
Expected: clean build. Intradoc-link [RaylibThread] / [compute_md5] / [crc32fast] etc. must resolve. If any break, double-check the import paths in the module-level doc.
Run: mdbook build book 2>&1 | tail -5
Expected: clean. The hashes module isn't referenced from any book chapter; this just confirms nothing leaked.
Files:
docs/superpowers/notes/cheatsheet-parity-audit.mdCHANGELOG.mdReconcile the audit doc and CHANGELOG to reflect the four new public APIs.
Compute* entries in the auditRun: grep -nE 'ComputeCRC32|ComputeMD5|ComputeSHA1|ComputeSHA256' docs/superpowers/notes/cheatsheet-parity-audit.md
Expected: matches in §1 (currently 🟥 GAP rows), possibly in §4 (future-workstream entry for hashes). Each gets a touch.
For each Compute* line in §1, change 🟥 ... — GAP. Action: future-workstream-hashes. to:
- ✅ `ComputeCRC32` — wrapped at `raylib/src/core/hashes.rs::compute_crc32`.
Same pattern for MD5/SHA1/SHA256 (each pointing at its compute_* fn). The exact original wording may differ; adapt the new line to match the surrounding ✅ rows' format.
§0 currently shows (post-pixel-pointers): "Covered: 488 / Gaps: 11 / Small fixes: 5 / Medium workstreams: 6". Decrement:
hashes removed; the count goes down by 1 even though 4 entries flipped because all 4 were part of the single hashes workstream).Adjust the headline "11, not 49" if needed: 11 − 4 = 7.
hashes workstream from §4The §4 entry for hashes (containing Compute* enumeration) is done. Either:
mixed-audio (currently #2) to #1, OR**DONE (commit <SHA>):** to mark it complete.Match whichever style pixel-pointers used in its reconciliation. Likely "remove + renumber".
In CHANGELOG.md, under ## 6.0.0-rc.1 (unreleased) → ### Added, append:
- **Hashes:** new module `raylib::core::hashes` with safe wrappers
over raylib's built-in hash functions:
- `compute_crc32(data: &[u8]) -> u32` — CRC-32/ISO-HDLC, free-thread.
- `compute_md5(thread: &RaylibThread, data: &[u8]) -> [u8; 16]` — MD5
digest in canonical byte order. Requires `&RaylibThread` to pin
the call to raylib's thread (the C function returns a pointer to
a shared static buffer that concurrent calls would race against).
- `compute_sha1(thread: &RaylibThread, data: &[u8]) -> [u8; 20]`
- `compute_sha256(thread: &RaylibThread, data: &[u8]) -> [u8; 32]`
All four re-exported through `raylib::prelude`. Module-level rustdoc
steers security-sensitive callers at the RustCrypto crates
(`crc32fast`, `md-5`, `sha1`, `sha2`); MD5 and SHA-1 are
cryptographically broken, and raylib's SHA-256 implementation is
not constant-time. Closes the `hashes` workstream from the
cheatsheet-parity audit.
Run: grep -nE 'ComputeCRC32|ComputeMD5|ComputeSHA1|ComputeSHA256' docs/superpowers/notes/cheatsheet-parity-audit.md
Expected: every remaining match is in the ✅ list (§1) or §5 reconciliation notes; no 🟥 rows left.
Run: grep -n 'compute_md5\|compute_sha1\|compute_sha256\|compute_crc32' CHANGELOG.md | head -5
Expected: the new bullet shows in the 6.0.0-rc.1 block.
git add docs/superpowers/notes/cheatsheet-parity-audit.md CHANGELOG.md
git commit -m "$(cat <<'EOF'
docs(hashes): mark Compute{CRC32,MD5,SHA1,SHA256} as covered
The hashes workstream from cheatsheet-parity-audit.md §4 #2 is done;
reconcile the audit + CHANGELOG accordingly.
- cheatsheet-parity-audit.md:
- §1 entries for the four Compute* fns flip 🟥 -> ✅ pointing at
raylib/src/core/hashes.rs.
- §0 totals: Covered 488 -> 492, gaps 11 -> 7, workstreams 6 -> 5.
- §4 workstream entry for `hashes` removed; remaining items
renumbered (mixed-audio -> #1).
- CHANGELOG.md ## 6.0.0-rc.1 (unreleased) ### Added: lists the new
raylib::core::hashes module with the four public items and a
pointer to the RustCrypto crates for security-sensitive use.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
EOF
)"
Files:
Modify: CLAUDE.md
Step 1: Confirm clean working tree
Run: git status --short
Expected: only the three permitted untracked files (TODO.md, prompt.md, next-session-prompt.md); no M lines.
fork/6.0-rc and fork/unstableRun:
git push fork 6.0-rc 2>&1 | tail -3
git push fork 6.0-rc:unstable 2>&1 | tail -3
Expected: both pushes succeed with the same SHA range.
Find the status line: grep -n 'hashes ← NEXT\|pixel-pointers ✅' CLAUDE.md. The current line shows pixel-pointers ✅ → hashes ← NEXT → mixed-audio → .... Replace with:
pixel-pointers ✅ → hashes ✅ → mixed-audio ← NEXT → raylib-test → ...
(Preserve the rest of the chain — raylib-test → UBSAN → rustdoc rewrite → safe abstractions → WS9 showcase → final-release.)
git add CLAUDE.md
git commit -m "$(cat <<'EOF'
docs(roadmap): hashes complete; mixed-audio is next
CLAUDE.md status line: pre-WS9 queue head flips from hashes to
mixed-audio. See docs/superpowers/notes/cheatsheet-parity-audit.md
for the updated cheatsheet coverage (492 / 500 in-scope).
Co-Authored-By: Claude Opus 4.7 <[email protected]>
EOF
)"
git push fork 6.0-rc 2>&1 | tail -3
git push fork 6.0-rc:unstable 2>&1 | tail -3
raylib/src/core/hashes.rs exists with the four public items + private read_static_hash helper + module-level rustdoc steering at RustCrypto.raylib/src/core/mod.rs declares pub mod hashes; and re-exports.raylib/src/prelude.rs includes the re-export.data: &[u8] only (no &RaylibThread); the three crypto hashes take (_thread: &RaylibThread, data: &[u8]).crc32_known_vectors and crc32_panics_on_oversize_input tests pass under cargo test -p raylib --lib.md5/sha1/sha256_known_vectors tests pass under cargo test -p raylib --lib --features full.cargo build --workspace --features full clean.cargo clippy --workspace --features full -- -D warnings clean.RUSTDOCFLAGS="-Dwarnings" cargo doc -p raylib --features full --no-deps clean.mdbook build book clean.cheatsheet-parity-audit.md reconciled: §0 totals, §1 ✅ rows, §4 workstream removed.CHANGELOG.md ## 6.0.0-rc.1 (unreleased) ### Added lists the new module + its four public items.CLAUDE.md status line: pre-WS9 queue head flips hashes ✅ → mixed-audio ← NEXT.Co-Authored-By: Claude Opus 4.7 <[email protected]> trailer.fork/6.0-rc and fork/unstable.Next workstream: mixed-audio (AttachAudioMixedProcessor / DetachAudioMixedProcessor — needs its own RAII handle distinct from the per-stream variant). Brainstorm starts when the owner is ready.