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),
                },
            },
        })
    }
}