Hash v0.1.0
Installation
Add Hash to the [Dependencies] table of your package's Rux.toml:
[Dependencies]
Hash = { Namespace = "Rux", Version = "0.1.0" }
Standard packages are declared by hand: the registry does not serve them, so rux add Rux/Hash cannot add them.
Dependencies
Core0.1.0
Guide
What it provides
| Module | Covers |
|---|---|
Hasher | the Hasher contract, and the writers every caller of one needs |
Fnv | FNV-1a over 32 and 64 bits, incremental and one-shot |
Crc | CRC-32 and CRC-32C, their tables and their polynomials |
Adler | Adler-32, the checksum the zlib format ends with |
XxHash | xxHash64, seeded, streaming and one-shot |
SipHash | keyed SipHash-2-4, for tables whose keys someone else chooses |
Design
Every hash here is built the same way: make one, write bytes into it in order, and ask for the answer. That shape is what lets a value be hashed without first being turned into a contiguous buffer — a struct writes each of its fields in turn, and a collection each of its elements, with nothing copied anywhere.
Take a hasher by generic bound and mutable reference. func Digest<H: Hasher>(hasher: &var H) borrows the caller's own state and resolves at instantiation, so mutations are visible without copying. The interface exists to be the bound.
All hasher states are structural Copy values and own no external resources. Copying one intentionally snapshots the stream; later writes to either value are independent. Construct unseeded states with type calls such as Fnv1a64(), Crc32(), and XxHash64(). Raw pointers remain only on byte-range APIs because those ranges may be nullable when empty and support pointer arithmetic; callers must provide length readable bytes.
WriteUint32 and WriteUint64 use a fixed byte order rather than the machine's, so a value hashes the same on every target. That is what makes the hash of a struct mean the same thing everywhere, and it costs nothing on a little-endian machine.
Speed
XxHash64 is the one to reach for when there is a lot of data. Where FNV-1a takes a byte at a time, it takes thirty-two: four independent accumulators, so a wide machine has four chains to run at once rather than one dependency chain to wait on. The mixing is arithmetic rather than table lookup, which is what keeps it fast on data that does not fit in cache — there is no table to evict.
A seed is part of the algorithm rather than an afterthought: two hashers with different seeds give unrelated answers for the same input, which keeps accidental collisions from following data between tables. It is not enough to stop a deliberate one.
Streaming is exact. Any division of the same byte stream into writes gives the same answer, including one byte at a time and including a write that lands one byte short of a block boundary.
Checksums
Crc32 and Crc32c are cyclic redundancy checks, and what they buy over a sum is a guarantee rather than a hope: a 32-bit CRC detects every burst error up to 32 bits long, every single-bit and double-bit error, and every error of odd weight — none of which a general-purpose hash promises about anything.
Both are the reflected, table-driven forms every implementation uses, so a value computed here matches gzip, PNG and zip for Crc32, and iSCSI, ext4, Btrfs and SCTP for Crc32c. Prefer Crc32c for new work: it detects more of the double-bit errors that appear at storage-record lengths, and both supported architectures implement it in hardware. This implementation is the table rather than the instruction — correct everywhere, and about a byte per cycle.
A CRC detects accident, never intent. Given any message and any target value, producing a message with that CRC is arithmetic a beginner can do. They implement Hasher because feeding them bytes has the same shape, not because they substitute for one.
Keying
Every other hash here has a fixed, public structure, so anyone can compute inputs that collide. Feed a few thousand of those to a hash table and every one lands in the same bucket: lookups that should be constant become linear, and a service doing fine falls over on traffic that looks perfectly ordinary. SipHash24 is what stops that.
It stops it by being keyed. Without the key, finding two colliding inputs is as hard as breaking the function, and the key is a secret the program draws from Rux/Entropy at startup and never publishes. A key compiled into a program is a published key, and a published key is no key at all.
The price is speed — two rounds per eight bytes and four more at the end, against xxHash64's one round across four independent lanes. For a table's short keys that is small and worth paying. For hashing a large file it is not.
Sixty-four bits is enough to make collisions unfindable at hash-table scale and not enough to authenticate a message. Rux/Crypto has HMAC for that.
What this is not
None of these are cryptographic. They are fast, they are named, and they are documented — but an adversary who gets to choose the input can find collisions in any of them except SipHash, and even SipHash promises only that doing so is hard without the key. Anything that must resist an adversary belongs in Rux/Crypto, which is also where SHA-2, SHA-3, BLAKE3 and the explicitly named legacy MD5 and SHA-1 live.
A hash from this package is stable: FNV-1a here will agree with any other correct implementation, which is what makes it worth naming an algorithm rather than promising only "some hash". Core::Hashable makes no such promise — its answers are not stable across runs, versions or targets, and nothing that outlives the process should be keyed on one.
Index
Interfaces
| Name | Summary |
|---|---|
Hasher | A hash being computed, one write at a time. |
Types
| Name | Summary |
|---|---|
Adler32 | The Adler-32 of a message, computed a batch of bytes at a time. |
Crc32 | The CRC-32 of a message, computed a byte at a time. |
Crc32c | The CRC-32C of a message, computed a byte at a time. |
Fnv1a32 | FNV-1a over 32 bits, computed one byte at a time. |
Fnv1a64 | FNV-1a over 64 bits, computed one byte at a time. |
SipState | The four words SipHash mixes, and the round that mixes them. |
SipHash24 | SipHash-2-4 over a stream of bytes, keyed with 128 bits. |
XxHash64 | xxHash64 over a stream of bytes, in as many writes as the caller has. |
Functions
| Name | Summary |
|---|---|
Crc32Of | The CRC-32 of length bytes at bytes. |
Crc32cOf | The CRC-32C of length bytes at bytes. |
Fnv1a32Of | The 32-bit FNV-1a hash of length bytes at bytes. |
Fnv1a64Of | The 64-bit FNV-1a hash of length bytes at bytes. |
HashBytes<H> | Writes length bytes and returns the hash, for a caller with nothing else to add. |
WriteByte<H> | Writes one byte into hasher. |
WriteUint32<H> | Writes a 32-bit value into hasher, least significant byte first. |
WriteUint64<H> | Writes a 64-bit value into hasher, least significant byte first. |
SipWord | The eight bytes at bytes as a 64-bit word, least significant byte first. |
SipHash24Of | The SipHash-2-4 of length bytes at bytes, keyed with low and high. |
ReadLittleEndian64 | The eight bytes at bytes as a 64-bit value, least significant byte first. |
ReadLittleEndian32 | The four bytes at bytes as a 32-bit value, least significant byte first. |
XxRound | One accumulator round: mix lane into accumulator. |
XxMerge | Folds one finished accumulator into the running hash. |
XxAvalanche | The final mixing step, which spreads every input bit across the whole result. |
XxHash64SeededOf | The xxHash64 of length bytes at bytes, using seed. |
XxHash64Of | The xxHash64 of length bytes at bytes, seeded with zero. |
Constants
| Name | Summary |
|---|---|
Crc32Polynomial | The reflected IEEE 802.3 generator polynomial, which is what gzip, PNG and zip mean by "CRC-32". |
Crc32cPolynomial | The reflected Castagnoli generator polynomial, which is what iSCSI, ext4, Btrfs and SCTP mean by "CRC-32C". |
Crc32Initial | The value a CRC register starts at, and the value the finished register is complemented against. |
Crc32Table | One byte of division by Crc32Polynomial, precomputed for every possible byte. |
Crc32cTable | One byte of division by Crc32cPolynomial, precomputed for every possible byte. |
FnvOffsetBasis32 | The 32-bit FNV-1a offset basis, which is what an empty input hashes to. |
FnvPrime32 | The 32-bit FNV prime. |
FnvOffsetBasis64 | The 64-bit FNV-1a offset basis, which is what an empty input hashes to. |
FnvPrime64 | The 64-bit FNV prime. |
SipInit0 | The first initialization constant, mixed with the low half of the key. |
SipInit1 | The second initialization constant, mixed with the high half of the key. |
SipInit2 | The third initialization constant, mixed with the low half of the key. |
SipInit3 | The fourth initialization constant, mixed with the high half of the key. |
SipCompressionRounds | How many rounds each eight-byte word of input is mixed with: the 2 of SipHash-2-4. |
SipFinalizationRounds | How many rounds finish the hash after the last word: the 4 of SipHash-2-4. |
XxPrime1 | The first xxHash64 prime, which every round multiplies by. |
XxPrime2 | The second xxHash64 prime, which scales each input lane before it is rotated in. |
XxPrime3 | The third xxHash64 prime, used in the four-byte tail step and the avalanche. |
XxPrime4 | The fourth xxHash64 prime, added after each merge and after each eight-byte tail step. |
XxPrime5 | The fifth xxHash64 prime, which seeds an input too short to fill a block and mixes each trailing byte. |
XxBlockSize | How many bytes one xxHash64 round consumes: four lanes of eight. |