Skip to main content

graphrefly_core/
hash.rs

1//! Synchronous SHA-256 hashing.
2//!
3//! GraphReFly's public substrate exposes `sha256Hex` (cache-key /
4//! content-addressing helper used by versioning, snapshot paths, and the
5//! presentation-layer adapters). The TS reference impl is async only
6//! because `crypto.subtle.digest` returns a `Promise` — there is nothing
7//! intrinsically async about SHA-256.
8//!
9//! Per the Rust-port invariant "No async runtime in Core" (CLAUDE.md
10//! invariant 4 / D070 / D077), the hashing itself stays **fully
11//! synchronous** here. The async-everywhere wrapping demanded by the
12//! `Impl` parity contract is applied only at the napi boundary
13//! (`graphrefly-bindings-js`), never in this crate.
14//!
15//! @module
16
17use sha2::{Digest, Sha256};
18
19/// Hex-encode the SHA-256 digest of `bytes`. Pure, synchronous, no
20/// allocation beyond the 64-char output `String`. Mirrors the TS
21/// `sha256Hex` byte semantics: the caller is responsible for encoding a
22/// `string` input to UTF-8 bytes before calling (the napi boundary does
23/// this).
24#[must_use]
25pub fn sha256_hex(bytes: &[u8]) -> String {
26    let mut hasher = Sha256::new();
27    hasher.update(bytes);
28    let digest = hasher.finalize();
29    hex::encode(digest)
30}
31
32#[cfg(test)]
33mod tests {
34    use super::sha256_hex;
35
36    #[test]
37    fn sha256_hex_known_vectors() {
38        // RFC-known: SHA-256("") and SHA-256("abc").
39        assert_eq!(
40            sha256_hex(b""),
41            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
42        );
43        assert_eq!(
44            sha256_hex(b"abc"),
45            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
46        );
47    }
48
49    #[test]
50    fn sha256_hex_matches_ts_string_byte_semantics() {
51        // The TS `sha256Hex("hello")` path UTF-8-encodes the string;
52        // the Rust napi boundary does the same, so the digest must match
53        // the well-known SHA-256("hello").
54        assert_eq!(
55            sha256_hex("hello".as_bytes()),
56            "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
57        );
58    }
59}