Standard library Dodo 0.1.4

Hashing and checksums

Choose hashes and checksums, process chunks incrementally, frame composite keys, and provide keyed collection policies.

On this page

Use std/hash.fnv1a64 for a stable fingerprint of trusted bytes and std/checksum.crc32 for accidental-corruption checks. These separate portable imports use only fixed-size state and do not request an allocator or entropy.

A hash condenses input into a fixed-width integer. Different inputs can produce the same integer: this is a collision. A hash map therefore uses both a hash and an equality test. A checksum helps detect accidental changes in bytes; it does not prove who created them. Match the algorithm to that purpose before copying a hash into a file format or using it as an identifier.

Quickstart

Save this as hash_start.dodo:

package hash_start
import "std/hash"
import "std/checksum"

fn main() -> i32 {
    assert_eq(hash.fnv1a64(b"hello"), 0xa430d84680aabd0bu64)
    assert_eq(checksum.crc32(b"123456789"), 0xcbf43926u32)
    return 0
}
dodo run hash_start.dodo

Expected output: none; exit 0 confirms both known results. These one-shot functions cannot fail. A checksum mismatch in an application means you should reject or reacquire the damaged data. Neither checksum nor FNV authenticates it. For hash tables accepting untrusted keys, use SipHash with an unpredictable caller-supplied key; if key acquisition fails, stop that operation. The library has no general OS entropy adapter and a fixed example seed is not a substitute.

API and contracts

std/hash provides allocation-free byte hashing and statically dispatched map policies. std/checksum is an independent import for accidental-corruption checks. Neither package imports collections, allocators, an operating system, or an entropy provider. Both use fixed-size state and O(n) time for n input bytes; there are no large tables or native-layout reads.

Algorithms and stable results

Choose an algorithm

API Definition Intended use
hash.Fnv1a64, hash.fnv1a64 FNV-1a-64; offset basis 14695981039346656037, prime 1099511628211, unsigned arithmetic modulo 2^64 Trusted byte keys, stable noncryptographic fingerprints
hash.SipHash24, hash.siphash24 SipHash-2-4, 128-bit key, 64-bit result Hash-table keys potentially controlled by an adversary
checksum.Crc32, checksum.crc32 CRC-32/ISO-HDLC, reflected polynomial 0xEDB88320, initial and final XOR 0xFFFFFFFF Accidental corruption detection
checksum.Adler32, checksum.adler32 Adler-32, modulus 65521, initial sums 1 and 0 Zlib-compatible checksums

Results are stable across architectures and future releases for the same named algorithm, exact input bytes, and seed/key. An algorithm change requires a new name. Return values are integers; serialize them explicitly with the desired protocol byte order. No algorithm reads padding or a native struct layout. All algorithm arithmetic is explicitly modular where specified, independently of Dodo’s ordinary checked integer arithmetic.

Hash chunks without joining them

All state types expose update(&mut self, &[u8]), finish(&self), and reset(&mut self). finish returns a snapshot without consuming, modifying, or resetting state; repeated calls agree, and further updates continue the original stream. Empty updates have no effect. reset restores the initial seed/key. Fnv1a64.new() uses the standard offset basis; Fnv1a64.seeded(seed) replaces it exactly. The seed does not make FNV secure. The one-shot helpers produce the same result as new, update, finish. SipHash accepts any stream length and encodes the length modulo 256 in its final block as specified by the algorithm.

The following complete example hashes two input chunks as one stream. It also checks that taking a snapshot does not consume the state.

package hash_chunks
import "std/hash"

fn main() -> i32 {
    state := hash.Fnv1a64.new()
    state.update(b"he")
    assert_eq(state.finish(), hash.fnv1a64(b"he"))
    state.update(b"llo")
    assert_eq(state.finish(), hash.fnv1a64(b"hello"))
    state.reset()
    assert_eq(state.finish(), hash.fnv1a64(b""))
    return 0
}

Save as hash_chunks.dodo and run dodo run hash_chunks.dodo; success exits with zero and prints nothing. Chunk boundaries have no effect on the result. The checksum state types work the same way, but return u32 rather than u64.

Keyed hashing and caller-supplied keys

SipHash24.new(k0, k1) and siphash24(data, k0, k1) interpret the two key words as the little-endian encodings of key bytes 0..7 and 8..15. The caller supplies all 128 bits. For maps exposed to untrusted input, obtain an unpredictable key through an application-selected entropy adapter and retain it for the map’s lifetime. Independent map keys can limit the scope of a leaked key. These packages do not acquire entropy, create a process-global key, or silently replace missing entropy with a fixed seed. Fixed keys in examples and tests serve reproducibility only.

Key128.new(k0, k1) holds explicit key words, exposed through word0 and word1. Its hasher, i32_policy, and u64_policy methods construct keyed states/policies. obtain_key(&mut source) invokes the statically dispatched capability source.next_key(&mut self) -> Key128!KeyError exactly once. The source type and its next_key method must be public for dispatch from the hash package. KeyError.Unavailable propagates unchanged: there is no retry, fallback, implicit seed, or entropy access. The application implements the source and is responsible for unpredictable production keys. A source can return a fresh key per request or apply an explicit application key-management policy. Failure may advance source state according to that source’s contract, but never returns a usable key. The library provides no deterministic source as a production default; fixtures/examples define clearly named deterministic test sources.

SipHash is used here as a keyed table hash with a 64-bit output. It is not an unkeyed collision-resistant digest, password hash, general-purpose message authentication API, or encryption algorithm. No cryptographic API is provided; there is no constant-time or key-erasure guarantee. Checksums and FNV provide no protection against an adversary who can choose or alter the bytes.

Statically dispatched contracts and value encodings

Encode composite keys unambiguously

Generic hashing helpers expect H.update(&mut self, data: &[u8]). A complete incremental state additionally supplies finish(&self) -> u64 and reset(&mut self). Calls are monomorphized; there are no traits, closures, reflection, implicit hashing, or vtables.

write_u64(state, value) emits exactly eight little-endian bytes. write_i32(state, value) emits exactly four little-endian two’s-complement bytes, including for negative values. write_bytes(state, data) prefixes a variable-sized field with its length encoded as a little-endian u64, then emits its bytes. This framing distinguishes, for example, the two fields ab,c from a,bc. User-defined records should hash logical fields explicitly, including a discriminant where variants require one; never hash their memory representation. Floating-point keys need an explicit equality policy and matching treatment of signed zero and NaNs; no default floating policy exists.

The important distinction is between a byte stream and a sequence of fields. Updating with b"ab" and then b"c" hashes the same stream as b"a" and then b"bc". Use write_bytes for each field when that boundary is part of the key:

package framed_hash
import "std/hash"

fn main() -> i32 {
    first := hash.Fnv1a64.new()
    hash.write_bytes(&mut first, b"ab")
    hash.write_bytes(&mut first, b"c")
    second := hash.Fnv1a64.new()
    hash.write_bytes(&mut second, b"a")
    hash.write_bytes(&mut second, b"bc")
    assert_ne(first.finish(), second.finish())
    return 0
}

Save as framed_hash.dodo and run dodo run framed_hash.dodo. These particular inputs have different hashes. Framing prevents ambiguous field encodings; it does not eliminate collisions in the finite hash output.

Use policies in collections

Map policies provide hash(&self, key: &K) -> u64 and equal(&self, a: &K, b: &K) -> bool. Equal keys must hash equally; equality must be an equivalence relation. Policy state and key equality/hash behavior must remain stable while keys are stored. I32 {} and U64 {} use FNV and ordinary integer equality. SipI32.new(k0, k1) and SipU64.new(k0, k1) provide the same encodings with SipHash. Colliding unequal keys are allowed and handled by collections.

For UTF-8 keys, hash.Str {} handles &str and hash.Text {} handles text.Text; both compare bytes lexicographically and hash contents with FNV. hash.SipStr.new(k0, k1) supplies caller-keyed hashing for &str keys. text_shared.Key {} provides comparison, equality, and FNV hashing for owned text_shared.String keys. See the string-keyed map example and collection lifetime restrictions.

Hashing a string means hashing its UTF-8 bytes, not its address. These policies do not normalize Unicode or ignore case. Prepare a normalized representation yourself if the application’s equality rules require one, and use that same representation for both equality and hashing. See the API reference for state constructors, policy methods, and key-source declarations.

Attribution and validation

FNV follows Fowler/Noll/Vo’s algorithm and the IETF FNV specification. SipHash follows Jean-Philippe Aumasson and Daniel J. Bernstein’s SipHash specification; the fixture contains all 64 official 64-bit reference vectors, interpreted as little-endian integers. CRC-32 follows RFC 1952, and Adler-32 follows RFC 1950. These are direct Dodo implementations of the algorithms, without C or libc dependencies.

tests/stdlib/hash_checks.dodo checks published vectors, independent Python zlib checksum results, every split position in a 513-byte input, zero-length updates, repeated finalization, reset, seeds, integer/framed encodings, and caller-key-source success/failure without fallback or retries. tests/hash_library.rs runs it at -O0 and -O3 and emits WebAssembly and Cortex-M0 objects. See examples/hash.dodo for a complete program.

Type to search all documentation.

Keyboard shortcuts

Search documentation
Ctrl K or /
Move through results
↑ ↓
Open selected result
Enter
Close a dialog
Esc
Show these shortcuts
?