Skip to main content

provekit_whir/hash/
mod.rs

1mod blake3_engine;
2mod copy_engine;
3mod digest_engine;
4mod hash_counter;
5
6use core::fmt;
7use std::{
8    borrow::Cow,
9    sync::{Arc, LazyLock},
10};
11
12use const_oid::ObjectIdentifier;
13use serde::{Deserialize, Serialize};
14use static_assertions::{assert_impl_all, assert_obj_safe};
15use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
16
17pub use self::{
18    blake3_engine::{Blake3, BLAKE3},
19    copy_engine::{Copy, COPY},
20    digest_engine::{DigestEngine, Keccak, Sha2, Sha3, KECCAK, SHA2, SHA3},
21    hash_counter::HASH_COUNTER,
22};
23use crate::{
24    engines::{self, EngineId, Engines},
25    transcript::{Encoding, NargDeserialize, ProverMessage, VerificationError, VerificationResult},
26};
27
28pub static ENGINES: LazyLock<Engines<dyn HashEngine>> = LazyLock::new(|| {
29    let engines = Engines::<dyn HashEngine>::new();
30    engines.register(Arc::new(Copy::new()));
31    engines.register(Arc::new(Sha2::new()));
32    engines.register(Arc::new(Keccak::new()));
33    engines.register(Arc::new(Sha3::new()));
34    engines.register(Arc::new(Blake3::detect()));
35    engines
36});
37
38#[derive(
39    Clone,
40    Copy,
41    PartialEq,
42    Eq,
43    PartialOrd,
44    Ord,
45    Hash,
46    Default,
47    Serialize,
48    Deserialize,
49    KnownLayout,
50    Immutable,
51    Unaligned,
52    FromBytes,
53    IntoBytes,
54)]
55#[repr(transparent)]
56pub struct Hash(pub [u8; 32]);
57
58pub trait HashEngine: Send + Sync {
59    fn name(&self) -> Cow<'_, str>;
60
61    fn oid(&self) -> Option<ObjectIdentifier> {
62        None
63    }
64
65    /// Check if the engine supports hashing messages of the given size.
66    fn supports_size(&self, size: usize) -> bool;
67
68    /// The number of messages that should be hashed together to achieve maximally
69    /// utilize single thread parallelism.
70    ///
71    /// The caller should attermpt to call `hash_many` with multiples of this size for
72    /// optimal performance, e.g. when dividing work over threads.
73    ///
74    /// Regardless, all batch sizes must be supported.
75    fn preferred_batch_size(&self) -> usize {
76        1
77    }
78
79    /// Hash many messages of size `size`.
80    ///
81    /// Input contains `output.len()` messages concatenated together.
82    ///
83    /// Note: Implementation should be single-threaded. Parallelization should
84    /// be taken care of by the caller.
85    fn hash_many(&self, size: usize, input: &[u8], output: &mut [Hash]);
86}
87
88impl<E: HashEngine + ?Sized> engines::Engine for E {
89    fn engine_id(&self) -> EngineId {
90        use digest::Digest;
91        use sha3::Sha3_256;
92
93        let mut hasher = Sha3_256::new();
94        hasher.update(b"whir::hash");
95        if let Some(oid) = self.oid() {
96            hasher.update(oid.as_bytes());
97        } else {
98            hasher.update(self.name().as_bytes());
99        }
100        let hash: [u8; 32] = hasher.finalize().into();
101        hash.into()
102    }
103}
104
105assert_obj_safe!(HashEngine);
106
107impl fmt::Debug for Hash {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        for byte in self.0 {
110            write!(f, "{byte:02x}")?;
111        }
112        Ok(())
113    }
114}
115
116impl Encoding<[u8]> for Hash {
117    fn encode(&self) -> impl AsRef<[u8]> {
118        self.as_bytes()
119    }
120}
121
122impl NargDeserialize for Hash {
123    fn deserialize_from_narg(buf: &mut &[u8]) -> VerificationResult<Self> {
124        let (hash, tail) = Self::read_from_prefix(buf).map_err(|_| VerificationError)?;
125        *buf = tail;
126        Ok(hash)
127    }
128}
129
130assert_impl_all!(Hash: ProverMessage);
131
132#[cfg(test)]
133pub(crate) mod tests {
134    use proptest::{sample::select, strategy::Strategy};
135
136    use super::*;
137    use crate::{
138        engines::EngineId,
139        hash::{BLAKE3, KECCAK},
140    };
141
142    const HASHES: [EngineId; 5] = [COPY, SHA2, SHA3, KECCAK, BLAKE3];
143
144    pub fn hash_for_size(size: usize) -> impl Strategy<Value = EngineId> {
145        let suitable = HASHES
146            .iter()
147            .copied()
148            .filter(|h| ENGINES.retrieve(*h).is_some_and(|h| h.supports_size(size)))
149            .collect::<Vec<_>>();
150        select(suitable)
151    }
152}