Skip to content

prudent-rs/hash-injector

hash-injector

What, why, how

Summary

hash-injector is a Rust library that gives you control to provide/reuse hash values. It allows you to share hashes between equivalent instances of selected different types. That is not possible by Rust's core::hash out of the box.

This works even for instances of type(s) that are 1:1 related/respective to instances of the other (value-bearing, "primary") type, but they do not carry any value matchable with instances of that other type. It is especially suitable for sequential/ordered/generated/unique assigned secondary/tertiary/quaternary... indexes.

Problem

When a Rust type implements (or derives) core::hash::Hash trait, it visits its value-forming fields (recursively by default) and collects their primitive values or their byte representation.

The actual hash value also depends on the initial state (ideally randomness-based state) of core::hash::Hasher as created by core::hash::BuildHasher. The same BuildHasher instance creates Hasher instances with the same initial state, hence the hashes are reproducible. However, different BuildHasher instances should create Hasher instances with different initial state. So Hashers created by different BuildHashers should generate different hashes (for the same objects), hence being Hash DoS-resistant.

That gives all hashable types (with a derived or well-written Hash implementation) protection against Hash DoS attacks by default (if the BuildHasher/Hasher is Hash DoS-resistant, for example like SipHash 1-3 used by default by std::collections::HashMap).

However, Rust does NOT allow a type that implements Hash to provide/override its hash value directly. Instead, the hash value is calculated by core::Hash::Hasher::finish() method, which is NOT called by the object being hashed, but by the code that requested the hash (for example, HashMap). While that protects user types from mistakes, and it is secure by default, it does limit some special cases.

How to use - overview

How? You choose one type (value-bearing, "primary" type, not a secondary index) whose hash is collected in the usual way (by #[derive(core::hash::Hash)] or by standard implementation of core::hash::Hash). Its instances are usually stored as hashed keys. When storing them, capture their hashes and store them as a part of the the secondary index instances.

The secondary index types implement core::hash::Hash with a simple function call that signals/injects the stored hash to a co-operating core::hash::Hasher wrapper. At the same time, the Hasher generates hashes for the value-bearing type and any other types as per usual, still honoring Rust's cryptographic security.

You can combine this crate with any standard or custom core::hash::BuildHasher (for example, std::collections::HashMap's default BuildHasher: std::hash::RandomState).

Solution

hash-injector provides a core::hash::BuildHasher adapter, with its core::hash::Hasher adapter. The Hasher adapter has two modes of behavior:

  • standard (and default): When hashing ordinary types (core/std/3rd party), this Hasher sends the data (collected by Hash trait) to the underlying Hasher, and it returns the hash generated by it, so all behaves as per usual. It is Hash DoS-resistant (if the underlying BuildHasher/Hasher is Hash DoS-proof).

  • specialized: The adapter allows custom types, selected by you, to inject the hash - rather than have the hash calculated. If the injected hash is Hash DoS-proof (like ones generated by standard HashMap), then this use is also Hash DoS-resistant.

Use cases

especially when one type implements core::borrow::Borrow

Compatibility

Backwards-compatible

If you use this BuildHasher adapter with ordinary types (core/std/3rd party), the returned hash value is the one calculated by the underlying BuildHasher.

The only exception is extreme and beyond any ordinary types: Hashing an array or slice of ZST (zero-sized type) of size core::primitive::usize::MAX or core::primitive::usize::MAX-1 (or potentially a few more values near usize::MAX). This can happen only for ZST's, because such array/slice lengths are not available for non-zero-sized types (since those are limited by core::primitive::isize::MAX instead).

Since ZST's do not carry any data, it is fair to assume that they do not write anything in their Hash::hash(...) - like hash(...) for (). Well behaving ZST's could write some constants, but the hash would still have the same zero entropy/quality. They could write some thread-specific (Thread-local-based) constant values (if the ZST is !core::marker::Send), and the current Hash implementation for slices does honor that - but then all their instances (in the same thread) are equal anyway, so the entropy/quality (within the thread that they are bound/limited to) is zero again.

Compatibility with underlying Hashers and BuildHashers

Compatible with any underlying Hashers and BuildHashers (whether they implement Rust feature hasher_prefixfree_extras or not).

Compatibility between different use cases

This crate does NOT use (compile time) features to choose between various flows. Instead, the consumer chooses the flow by providing a const generic parameter (of type Flags).

Forward compatibility

The API is open to more flows or configuration in the future. The initial functions to create const generic Flags parameters are new_flags_signal_first and new_flags_submit_first. If/once we implement flows or configuration, there will be similar new functions. The existing functions will take on defaults for any new configuration flags/options, and we may have more similar functions for other combinations, like new_flags_signal_first_xxx and new_flags_submit_first_xxx.

nightly

As of mid 2025, this crate needs nightly toolchain because of two Rust features:

Quality assurance

  • no unsafe code (see #![forbid(unsafe_code)] in hash-injector-lib/src/lib.rs)
  • asserts (compile time) feature (which asserts in both debug or release)
  • unit tests
  • integration tests in hash-injector-tests, run for all combination of compile time features

Zero cost help

About

Rust hash injector

Resources

License

Apache-2.0 and 2 other licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
BSD-2-Clause
LICENSE-BSD
MIT
LICENSE-MIT

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages