Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow generating random value independent of std. #62

Merged
merged 8 commits into from
Nov 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,14 @@ jobs:
toolchain: nightly
override: true
components: clippy
- name: check specialize
- name: check nightly
uses: actions-rs/cargo@v1
with:
command: check
args: --features specialize
- name: test specialize
- name: test nightly
uses: actions-rs/cargo@v1
with:
command: test
args: --features specialize
- name: check serde
uses: actions-rs/cargo@v1
with:
Expand Down Expand Up @@ -97,7 +95,7 @@ jobs:
command: build
args: --target i686-unknown-linux-gnu
x86_64-unknown-linux-gnu:
name: Linux x86_64 - specialize
name: Linux x86_64 - nightly
runs-on: ubuntu-latest
env:
RUSTFLAGS: -C target-feature=+aes
Expand All @@ -112,7 +110,7 @@ jobs:
with:
use-cross: true
command: build
args: --target x86_64-unknown-linux-gnu --features specialize
args: --target x86_64-unknown-linux-gnu
thumbv6m:
name: thumbv6m
runs-on: ubuntu-latest
Expand All @@ -128,3 +126,18 @@ jobs:
use-cross: true
command: check
args: --target thumbv6m-none-eabi --no-default-features
wasm32-unknown-unknown:
name: wasm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
override: true
- uses: actions-rs/cargo@v1
with:
use-cross: true
command: check
args: --target wasm32-unknown-unknown
31 changes: 19 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ahash"
version = "0.5.8"
version = "0.6.0"
authors = ["Tom Kaitchuck <Tom.Kaitchuck@gmail.com>"]
license = "MIT OR Apache-2.0"
description = "A non-cryptographic hash function using AES-NI for high performance"
Expand All @@ -10,6 +10,7 @@ keywords = ["hash", "hasher", "hashmap", "aes", "no-std"]
categories = ["algorithms", "data-structures", "no-std"]
edition = "2018"
readme = "README.md"
build = "./build.rs"

[lib]
name = "ahash"
Expand All @@ -22,16 +23,15 @@ doc = true
[features]
default = ["std"]

# Enabling this will enable `AHashMap` and `AHashSet`, and runtime key generation.
std = ["getrandom", "lazy_static"]
# Enabling this will enable `AHashMap` and `AHashSet`.
std = []

# This is an alternitive to std which does compile time key generation so that the standard library is not required.
# This implies the produced binary will not be identical. If both this and `std` are disabled constant keys are used.
# This is an alternitive to runtime key generation which does compile time key generation if getrandom is not available.
# (If getrandom is available this does nothing.)
# If this is on (and getrandom is off) it implies the produced binary will not be identical.
# If this is disabled and gerrandom is unavailable constant keys are used.
compile-time-rng = ["const-random"]

# Enables specilization (requires nightly)
specialize = []

[[bench]]
name = "ahash"
path = "tests/bench.rs"
Expand Down Expand Up @@ -60,10 +60,17 @@ lto = 'fat'
debug-assertions = false
codegen-units = 1

[dependencies]
lazy_static = { version = "1.4.0", optional = true }
getrandom = { version = "0.2.0", optional = true }
const-random = { version = "0.1.6", optional = true }
[build-dependencies]
version_check = "0.9"

[target.'cfg(any(target_os = "linux", target_os = "android", target_os = "windows", target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "solaris", target_os = "illumos", target_os = "fuchsia", target_os = "redox", target_os = "cloudabi", target_os = "haiku", target_os = "vxworks", target_os = "emscripten", target_os = "wasi"))'.dependencies]
lazy_static = { version = "1.4.0" }
getrandom = { version = "0.2.0" }
const-random = { version = "0.1.12", optional = true }
serde = { version = "1.0.117", optional = true }

[target.'cfg(not(any(target_os = "linux", target_os = "android", target_os = "windows", target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "solaris", target_os = "illumos", target_os = "fuchsia", target_os = "redox", target_os = "cloudabi", target_os = "haiku", target_os = "vxworks", target_os = "emscripten", target_os = "wasi")))'.dependencies]
const-random = { version = "0.1.12", optional = true }
serde = { version = "1.0.117", optional = true }

[dev-dependencies]
Expand Down
6 changes: 6 additions & 0 deletions FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ There are efforts to build a secure hash function that uses AES-NI for accelerat

## How is aHash so fast

AHash uses a number of tricks.

One trick is taking advantage of specialization. If aHash is compiled on nightly it will take
advantage of specialized hash implementations for strings, slices, and primitives.

Another is taking advantage of hardware instructions.
When it is available aHash uses AES rounds using the AES-NI instruction. AES-NI is very fast (on an intel i7-6700 it
is as fast as a 64 bit multiplication.) and handles 16 bytes of input at a time, while being a very strong permutation.

Expand Down
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,20 @@ map.insert(56, 78);

## Flags

The aHash package has three flags:
* `std`: This enables features which require the standard library. (On by default) This includes generating random keys and providing the utility classes `AHashMap` and `AHashSet`.
* `compile-time-rng`: As an alternative to `std` when it is not available, this generates Random numbers for keys at compile time. This allows for DOS resistance even if there is no random number generator available at runtime (assuming the compiled binary is not public).
Note that this has the effect of making the output of the build non-deterministic.
* `specialize`: This uses the specialization feature to provide optimized algorithms for primitive types. (This requires nightly)

**NOTE:** If neither `std` or `compile-time-rng` aHash will fall back on using the numeric value of memory addresses as a source of randomness.
This is somewhat strong if ALSR is turned on (it is by default) but for some embedded platforms where this is not available,
this will result in weak keys. As a result, it is strongly recommended to use `std` when it is available and `compile-time-rng` when developing for an embedded platform where `std` is not available.
(If both are enabled `std` will take precedence and `compile-time-rng` will have no effect.)

The aHash package has the following flags:
* `std`: This enables features which require the standard library. (On by default) This includes providing the utility classes `AHashMap` and `AHashSet`.
* `compile-time-rng`: Whenever possible aHash will seed hashers with random numbers using the [getrandom](https://github.com/rust-random/getrandom) crate.
This is possible for OS targets which provide a source of randomness. (see the [full list](https://docs.rs/getrandom/0.2.0/getrandom/#supported-targets).)
For OS targets without access to a random number generator, `compile-time-rng` provides an alternative.
If `getrandom` is unavailable and `compile-time-rng` is enabled, aHash will generate random numbers at compile time and embed them in the binary.
This allows for DOS resistance even if there is no random number generator available at runtime (assuming the compiled binary is not public).
This makes the binary non-deterministic, unless `getrandom` is available for the target in which case the flag does nothing.
(If non-determinism is a problem see [constrandom's documentation](https://github.com/tkaitchuck/constrandom#deterministic-builds))

**NOTE:** If `getrandom` is unavailable and `compile-time-rng` is disabled aHash will fall back on using the numeric
value of memory addresses as a source of randomness. This is somewhat strong if ALSR is turned on (it is by default)
but for embedded platforms this will result in weak keys. As a result, it is recommended to use `compile-time-rng` anytime
random numbers will not be available at runtime.

## Comparison with other hashers

Expand Down
32 changes: 32 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#![deny(warnings)]

use std::env;

fn main() {
println!("cargo:rerun-if-changed=build.rs");
if let Some(channel) = version_check::Channel::read() {
if channel.supports_features() {
println!("cargo:rustc-cfg=feature=\"specialize\"");
}
}
let os = env::var("CARGO_CFG_TARGET_OS").expect("CARGO_CFG_TARGET_OS was not set");
if os.eq_ignore_ascii_case("linux") ||
os.eq_ignore_ascii_case("android") ||
os.eq_ignore_ascii_case("windows") ||
os.eq_ignore_ascii_case("macos") ||
os.eq_ignore_ascii_case("ios") ||
os.eq_ignore_ascii_case("freebsd") ||
os.eq_ignore_ascii_case("openbsd") ||
os.eq_ignore_ascii_case("dragonfly") ||
os.eq_ignore_ascii_case("solaris") ||
os.eq_ignore_ascii_case("illumos") ||
os.eq_ignore_ascii_case("fuchsia") ||
os.eq_ignore_ascii_case("redox") ||
os.eq_ignore_ascii_case("cloudabi") ||
os.eq_ignore_ascii_case("haiku") ||
os.eq_ignore_ascii_case("vxworks") ||
os.eq_ignore_ascii_case("emscripten") ||
os.eq_ignore_ascii_case("wasi") {
println!("cargo:rustc-cfg=feature=\"runtime-rng\"");
}
}
4 changes: 2 additions & 2 deletions smhasher/ahash-cbindings/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ahash-cbindings"
version = "0.1.1"
version = "0.1.2"
authors = ["Tom Kaitchuck <Tom.Kaitchuck@gmail.com>"]
edition = "2018"
description = "C bindings for aHash so that it can be invoked by SMHasher to verify quality."
Expand All @@ -17,4 +17,4 @@ lto = 'fat'
debug-assertions = false

[dependencies]
ahash = { path = "../../", default-features = false, features = ["specialize"] }
ahash = { path = "../../", default-features = false }
42 changes: 30 additions & 12 deletions src/random_state.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
#[cfg(feature = "std")]
#[cfg(all(feature = "runtime-rng", not(all(feature = "compile-time-rng", test))))]
use crate::convert::Convert;
use crate::{AHasher};
#[cfg(all(not(feature = "std"), feature = "compile-time-rng"))]
#[cfg(all(feature = "compile-time-rng", any(not(feature = "runtime-rng"), test)))]
use const_random::const_random;
use core::fmt;
use core::hash::BuildHasher;
use core::hash::Hasher;
#[cfg(feature = "std")]
#[cfg(all(feature = "runtime-rng", not(all(feature = "compile-time-rng", test))))]
use lazy_static::*;
use core::sync::atomic::{AtomicUsize, Ordering};

#[cfg(feature = "std")]
#[cfg(all(feature = "runtime-rng", not(all(feature = "compile-time-rng", test))))]
lazy_static! {
static ref SEEDS: [[u64; 4]; 2] = {
let mut result: [u8; 64] = [0; 64];
Expand All @@ -28,7 +28,7 @@ pub(crate) const PI: [u64; 4] = [
0x082e_fa98_ec4e_6c89,
];

#[cfg(all(not(feature = "std"), not(feature = "compile-time-rng")))]
#[cfg(all(not(feature = "runtime-rng"), not(feature = "compile-time-rng")))]
const PI2: [u64; 4] = [
0x4528_21e6_38d0_1377,
0xbe54_66cf_34e9_0c6c,
Expand All @@ -38,11 +38,11 @@ const PI2: [u64; 4] = [

#[inline]
pub(crate) fn seeds() -> [u64; 4] {
#[cfg(feature = "std")]
#[cfg(all(feature = "runtime-rng", not(all(feature = "compile-time-rng", test))))]
{ SEEDS[1] }
#[cfg(all(not(feature = "std"), feature = "compile-time-rng"))]
#[cfg(all(feature = "compile-time-rng", any(not(feature = "runtime-rng"), test)))]
{ [const_random!(u64), const_random!(u64), const_random!(u64), const_random!(u64)] }
#[cfg(all(not(feature = "std"), not(feature = "compile-time-rng")))]
#[cfg(all(not(feature = "runtime-rng"), not(feature = "compile-time-rng")))]
{ PI }
}

Expand Down Expand Up @@ -72,19 +72,19 @@ impl RandomState {
/// Use randomly generated keys
#[inline]
pub fn new() -> RandomState {
#[cfg(feature = "std")]
#[cfg(all(feature = "runtime-rng", not(all(feature = "compile-time-rng", test))))]
{
let seeds = *SEEDS;
RandomState::from_keys(seeds[0], seeds[1])
}
#[cfg(all(not(feature = "std"), feature = "compile-time-rng"))]
#[cfg(all(feature = "compile-time-rng", any(not(feature = "runtime-rng"), test)))]
{
RandomState::from_keys(
[const_random!(u64), const_random!(u64), const_random!(u64), const_random!(u64)],
[const_random!(u64), const_random!(u64), const_random!(u64), const_random!(u64)],
)
}
#[cfg(all(not(feature = "std"), not(feature = "compile-time-rng")))]
#[cfg(all(not(feature = "runtime-rng"), not(feature = "compile-time-rng")))]
{
RandomState::from_keys(PI, PI2)
}
Expand Down Expand Up @@ -113,7 +113,7 @@ impl RandomState {
COUNTER.store(new, Ordering::Relaxed);
hasher.write_usize(new);
}
#[cfg(all(not(feature = "std"), not(feature = "compile-time-rng")))]
#[cfg(all(not(feature = "runtime-rng"), not(feature = "compile-time-rng")))]
hasher.write_usize(&PI as *const _ as usize);
let mix = |k: u64| {
let mut h = hasher.clone();
Expand Down Expand Up @@ -193,6 +193,24 @@ mod test {
assert_ne!(a.build_hasher().finish(), b.build_hasher().finish());
}

#[cfg(all(feature = "runtime-rng", not(all(feature = "compile-time-rng", test))))]
#[test]
fn test_not_pi() {
assert_ne!(PI, seeds());
}

#[cfg(all(feature = "compile-time-rng", any(not(feature = "runtime-rng"), test)))]
#[test]
fn test_not_pi_const() {
assert_ne!(PI, seeds());
}

#[cfg(all(not(feature = "runtime-rng"), not(feature = "compile-time-rng")))]
#[test]
fn test_pi() {
assert_eq!(PI, seeds());
}

#[test]
fn test_with_seeds_const() {
const _CONST_RANDOM_STATE: RandomState = RandomState::with_seeds(17, 19, 21, 23);
Expand Down