hashx/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
#![doc = include_str!("../README.md")]
// @@ begin lint list maintained by maint/add_warning @@
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
#![warn(missing_docs)]
#![warn(noop_method_call)]
#![warn(unreachable_pub)]
#![warn(clippy::all)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::cargo_common_metadata)]
#![deny(clippy::cast_lossless)]
#![deny(clippy::checked_conversions)]
#![warn(clippy::cognitive_complexity)]
#![deny(clippy::debug_assert_with_mut_call)]
#![deny(clippy::exhaustive_enums)]
#![deny(clippy::exhaustive_structs)]
#![deny(clippy::expl_impl_clone_on_copy)]
#![deny(clippy::fallible_impl_from)]
#![deny(clippy::implicit_clone)]
#![deny(clippy::large_stack_arrays)]
#![warn(clippy::manual_ok_or)]
#![deny(clippy::missing_docs_in_private_items)]
#![warn(clippy::needless_borrow)]
#![warn(clippy::needless_pass_by_value)]
#![warn(clippy::option_option)]
#![deny(clippy::print_stderr)]
#![deny(clippy::print_stdout)]
#![warn(clippy::rc_buffer)]
#![deny(clippy::ref_option_ref)]
#![warn(clippy::semicolon_if_nothing_returned)]
#![warn(clippy::trait_duplication_in_bounds)]
#![deny(clippy::unchecked_duration_subtraction)]
#![deny(clippy::unnecessary_wraps)]
#![warn(clippy::unseparated_literal_suffix)]
#![deny(clippy::unwrap_used)]
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
mod compiler;
mod constraints;
mod err;
mod generator;
mod program;
mod rand;
mod register;
mod scheduler;
mod siphash;
use crate::compiler::{Architecture, Executable};
use crate::program::Program;
use rand_core::RngCore;
pub use crate::err::{CompilerError, Error};
pub use crate::rand::SipRand;
pub use crate::siphash::SipState;
/// Option for selecting a HashX runtime
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum RuntimeOption {
/// Choose the interpreted runtime, without trying the compiler at all.
InterpretOnly,
/// Choose the compiled runtime only, and fail if it experiences any errors.
CompileOnly,
/// Always try the compiler first but fall back to the interpreter on error.
/// (This is the default)
#[default]
TryCompile,
}
/// Effective HashX runtime for a constructed program
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum Runtime {
/// The interpreted runtime is active.
Interpret,
/// The compiled runtime is active.
Compiled,
}
/// Pre-built hash program that can be rapidly computed with different inputs
///
/// The program and initial state representation are not specified in this
/// public interface, but [`std::fmt::Debug`] can describe program internals.
#[derive(Debug)]
pub struct HashX {
/// Keys used to generate an initial register state from the hash input
///
/// Half of the key material generated from seed bytes go into the random
/// program generator, and the other half are saved here for use in each
/// hash invocation.
register_key: SipState,
/// A prepared randomly generated hash program
///
/// In compiled runtimes this will be executable code, and in the
/// interpreter it's a list of instructions. There is no stable API for
/// program information, but the Debug trait will list programs in either
/// format.
program: RuntimeProgram,
}
/// Combination of [`Runtime`] and the actual program info used by that runtime
///
/// All variants of [`RuntimeProgram`] use some kind of inner heap allocation
/// to store the program data.
#[derive(Debug)]
enum RuntimeProgram {
/// Select the interpreted runtime, and hold a Program for it to run.
Interpret(Program),
/// Select the compiled runtime, and hold an executable code page.
Compiled(Executable),
}
impl HashX {
/// The maximum available output size for [`Self::hash_to_bytes()`]
pub const FULL_SIZE: usize = 32;
/// Generate a new hash function with the supplied seed.
pub fn new(seed: &[u8]) -> Result<Self, Error> {
HashXBuilder::new().build(seed)
}
/// Check which actual program runtime is in effect.
///
/// By default we try to generate code at runtime to accelerate the hash
/// function, but we fall back to an interpreter if this fails. The compiler
/// can be disabled entirely using [`RuntimeOption::InterpretOnly`] and
/// [`HashXBuilder`].
pub fn runtime(&self) -> Runtime {
match &self.program {
RuntimeProgram::Interpret(_) => Runtime::Interpret,
RuntimeProgram::Compiled(_) => Runtime::Compiled,
}
}
/// Calculate the first 64-bit word of the hash, without converting to bytes.
pub fn hash_to_u64(&self, input: u64) -> u64 {
self.hash_to_regs(input).digest(self.register_key)[0]
}
/// Calculate the hash function at its full output width, returning a fixed
/// size byte array.
pub fn hash_to_bytes(&self, input: u64) -> [u8; Self::FULL_SIZE] {
let words = self.hash_to_regs(input).digest(self.register_key);
let mut bytes = [0_u8; Self::FULL_SIZE];
for word in 0..words.len() {
bytes[word * 8..(word + 1) * 8].copy_from_slice(&words[word].to_le_bytes());
}
bytes
}
/// Common setup for hashes with any output format
#[inline(always)]
fn hash_to_regs(&self, input: u64) -> register::RegisterFile {
let mut regs = register::RegisterFile::new(self.register_key, input);
match &self.program {
RuntimeProgram::Interpret(program) => program.interpret(&mut regs),
RuntimeProgram::Compiled(executable) => executable.invoke(&mut regs),
}
regs
}
}
/// Builder for creating [`HashX`] instances with custom settings
#[derive(Default, Debug, Clone, Eq, PartialEq)]
pub struct HashXBuilder {
/// Current runtime() setting for this builder
runtime: RuntimeOption,
}
impl HashXBuilder {
/// Create a new [`HashXBuilder`] with default settings.
///
/// Immediately calling [`Self::build()`] would be equivalent to using
/// [`HashX::new()`].
pub fn new() -> Self {
Default::default()
}
/// Select a new [`RuntimeOption`].
pub fn runtime(&mut self, runtime: RuntimeOption) -> &mut Self {
self.runtime = runtime;
self
}
/// Build a [`HashX`] instance with a seed and the selected options.
pub fn build(&self, seed: &[u8]) -> Result<HashX, Error> {
let (key0, key1) = SipState::pair_from_seed(seed);
let mut rng = SipRand::new(key0);
self.build_from_rng(&mut rng, key1)
}
/// Build a [`HashX`] instance from an arbitrary [`RngCore`] and
/// a [`SipState`] key used for initializing the register file.
pub fn build_from_rng<R: RngCore>(
&self,
rng: &mut R,
register_key: SipState,
) -> Result<HashX, Error> {
let program = Program::generate(rng)?;
self.build_from_program(program, register_key)
}
/// Build a [`HashX`] instance from an already-generated [`Program`] and
/// [`SipState`] key.
///
/// The program is either stored as-is or compiled, depending on the current
/// [`RuntimeOption`]. Requires a program as well as a [`SipState`] to be
/// used for initializing the register file.
fn build_from_program(&self, program: Program, register_key: SipState) -> Result<HashX, Error> {
Ok(HashX {
register_key,
program: match self.runtime {
RuntimeOption::InterpretOnly => RuntimeProgram::Interpret(program),
RuntimeOption::CompileOnly => {
RuntimeProgram::Compiled(Architecture::compile((&program).into())?)
}
RuntimeOption::TryCompile => match Architecture::compile((&program).into()) {
Ok(exec) => RuntimeProgram::Compiled(exec),
Err(_) => RuntimeProgram::Interpret(program),
},
},
})
}
}