Skip to main content

nub_flat/
hash.rs

1//! Content-addressing for the flat personality.
2//!
3//! # This is not a cryptographic hash
4//!
5//! It is FNV-1a run four times under different seeds and concatenated.
6//! FNV is trivially collidable by construction — anyone who can choose
7//! two inputs can make them collide. A personality that accepts objects
8//! from an untrusted party **must** use a real cryptographic hash
9//! (JAVM uses BLAKE2 through `javm-cap`); using this one there would let
10//! an attacker substitute one program for another.
11//!
12//! It is here because the flat personality's job is to be the smallest
13//! complete example of the `Personality`/`GuestPersonality` pair, and to
14//! make nub's own benchmarks runnable. In that setting there is no
15//! adversary, and a dependency-free 40-line hash keeps the example
16//! readable and keeps the guest build free of a crate that would want
17//! CPU feature detection on `x86_64-unknown-none`.
18//!
19//! The one real constraint it must satisfy: host and guest compute the
20//! *same* value, since the host publishes by hash and invokes by hash.
21//! That is what the round-trip test pins.
22
23/// 32-byte object identity, matching `nub_kernel::ObjHash`'s shape.
24pub type Hash = [u8; 32];
25
26/// The wire's put-failure sentinel. `GuestStore::put_object` must never
27/// return this value for a real object; with four independent 64-bit
28/// lanes the odds of hitting all-ones are nil, but
29/// [`content_hash`] forces a bit clear rather than relying on that.
30pub const ERROR_SENTINEL: Hash = [0xFF; 32];
31
32const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
33const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
34
35/// Domain separators, one per output lane, so the four passes cannot
36/// degenerate into the same function.
37const SEEDS: [u64; 4] = [
38    0x9E37_79B9_7F4A_7C15,
39    0xBF58_476D_1CE4_E5B9,
40    0x94D0_49BB_1331_11EB,
41    0x2545_F491_4F6C_DD1D,
42];
43
44/// Content-address `bytes`.
45///
46/// See the module docs: adequate for identity, useless against an
47/// adversary.
48pub fn content_hash(bytes: &[u8]) -> Hash {
49    let mut out = [0u8; 32];
50    for (lane, seed) in SEEDS.iter().enumerate() {
51        let mut h = FNV_OFFSET ^ seed;
52        // Length first, so appending zeros cannot leave the digest
53        // unchanged the way a pure byte fold would.
54        for b in (bytes.len() as u64).to_le_bytes() {
55            h = (h ^ u64::from(b)).wrapping_mul(FNV_PRIME);
56        }
57        for &b in bytes {
58            h = (h ^ u64::from(b)).wrapping_mul(FNV_PRIME);
59        }
60        out[lane * 8..lane * 8 + 8].copy_from_slice(&h.to_le_bytes());
61    }
62    // Keep the all-ones sentinel unreachable, so a legitimate object can
63    // never be mistaken for a put failure on the wire.
64    out[31] &= 0x7F;
65    out
66}