Skip to main content

laser_wire/
hashing.rs

1// The one content-hash every SDK shares, so a content-addressed id (a deduped
2// memory item, a converged graph node) is identical across languages. Pure and
3// dependency-free FNV-1a, no clock or entropy (id GENERATION lives SDK-side. This
4// is deterministic hashing, which belongs in the contract). A port reproduces it
5// from the same byte segments and the fixture pins the result.
6
7const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
8const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
9
10fn fnv1a(salt: u8, segments: &[&[u8]]) -> u64 {
11    let mut hash = FNV_OFFSET;
12    hash ^= u64::from(salt);
13    hash = hash.wrapping_mul(FNV_PRIME);
14    for segment in segments {
15        for &byte in *segment {
16            hash ^= u64::from(byte);
17            hash = hash.wrapping_mul(FNV_PRIME);
18        }
19    }
20    hash
21}
22
23/// A deterministic 128-bit content id over `segments`, the concatenation of which
24/// is the content being addressed. Two independently salted FNV-1a passes fill
25/// the high and low halves, so the same segments always yield the same id and a
26/// different input yields a different one. The caller assembles the segments
27/// (e.g. an owner, a discriminator byte, and a body) and the order is part of the
28/// contract.
29pub fn content_id(segments: &[&[u8]]) -> u128 {
30    (u128::from(fnv1a(0x4d, segments)) << 64) | u128::from(fnv1a(0xc7, segments))
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36
37    #[test]
38    fn given_the_same_segments_when_hashed_then_should_be_stable() {
39        let a = content_id(&[b"owner", &[1], b"body"]);
40        let b = content_id(&[b"owner", &[1], b"body"]);
41        assert_eq!(a, b);
42    }
43
44    #[test]
45    fn given_different_segments_when_hashed_then_should_differ() {
46        assert_ne!(content_id(&[b"a"]), content_id(&[b"b"]));
47    }
48
49    #[test]
50    fn given_the_pinned_memory_segments_when_hashed_then_should_match_the_golden_id() {
51        // The cross-SDK golden vector: an empty stream segment, agent "agent", a
52        // separator, the Fact kind byte (1), and body "x". The SDK and the Python
53        // reference both render this u128 as the ULID below, so the dedup id agrees
54        // across languages. Rendered with the crate's Crockford encoder, the same
55        // one every wire id displays through.
56        let id = content_id(&[&[0], b"agent", &[0], &[1], b"x"]);
57        let rendered = crate::agent::RecordId::from_u128(id).to_string();
58        assert_eq!(rendered, "1A9GVS6SJ6SNS4KY0H19130WCW");
59    }
60}