Skip to main content

solana_hash/
lib.rs

1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
4#[cfg(feature = "borsh")]
5use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
6#[cfg(any(feature = "rand", feature = "frozen-abi"))]
7extern crate std;
8#[cfg(feature = "bytemuck")]
9use bytemuck_derive::{Pod, Zeroable};
10#[cfg(feature = "decode")]
11use core::{
12    fmt,
13    str::{from_utf8_unchecked, FromStr},
14};
15#[cfg(feature = "serde")]
16use serde_derive::{Deserialize, Serialize};
17#[cfg(feature = "sanitize")]
18use solana_sanitize::Sanitize;
19#[cfg(feature = "borsh")]
20extern crate alloc;
21#[cfg(feature = "borsh")]
22use alloc::string::ToString;
23#[cfg(feature = "frozen-abi")]
24use solana_frozen_abi_macro::{AbiExample, StableAbi, StableAbiSample};
25#[cfg(feature = "wincode")]
26use wincode::{SchemaRead, SchemaWrite};
27#[cfg(feature = "rand")]
28mod hasher;
29
30#[cfg(all(feature = "rand", not(any(target_os = "solana", target_arch = "bpf"))))]
31pub use crate::hasher::{HashHasher, HashHasherBuilder};
32
33/// Size of a hash in bytes.
34pub const HASH_BYTES: usize = 32;
35/// Maximum string length of a base58 encoded hash.
36pub const MAX_BASE58_LEN: usize = 44;
37
38/// A hash; the 32-byte output of a hashing algorithm.
39///
40/// This struct is used most often in `solana-sdk` and related crates to contain
41/// a [SHA-256] hash, but may instead contain a [blake3] hash.
42///
43/// [SHA-256]: https://en.wikipedia.org/wiki/SHA-2
44/// [blake3]: https://github.com/BLAKE3-team/BLAKE3
45#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbiSample, StableAbi))]
46#[cfg_attr(
47    feature = "borsh",
48    derive(BorshSerialize, BorshDeserialize),
49    borsh(crate = "borsh")
50)]
51#[cfg_attr(feature = "borsh", derive(BorshSchema))]
52#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
53#[cfg_attr(feature = "serde", derive(Serialize, Deserialize,))]
54#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
55#[cfg_attr(feature = "copy", derive(Copy))]
56#[cfg_attr(not(feature = "decode"), derive(Debug))]
57#[derive(Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
58#[repr(transparent)]
59pub struct Hash(pub(crate) [u8; HASH_BYTES]);
60
61#[cfg(feature = "sanitize")]
62impl Sanitize for Hash {}
63
64impl From<[u8; HASH_BYTES]> for Hash {
65    fn from(from: [u8; 32]) -> Self {
66        Self(from)
67    }
68}
69
70impl AsRef<[u8]> for Hash {
71    fn as_ref(&self) -> &[u8] {
72        &self.0[..]
73    }
74}
75
76#[cfg(feature = "decode")]
77fn write_as_base58(f: &mut fmt::Formatter, h: &Hash) -> fmt::Result {
78    let mut out = [0u8; MAX_BASE58_LEN];
79    let len = five8::encode_32(&h.0, &mut out) as usize;
80    // any sequence of base58 chars is valid utf8
81    let as_str = unsafe { from_utf8_unchecked(&out[..len]) };
82    f.write_str(as_str)
83}
84
85#[cfg(feature = "decode")]
86impl fmt::Debug for Hash {
87    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88        write_as_base58(f, self)
89    }
90}
91
92#[cfg(feature = "decode")]
93impl fmt::Display for Hash {
94    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95        write_as_base58(f, self)
96    }
97}
98
99#[cfg(feature = "decode")]
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub enum ParseHashError {
102    WrongSize,
103    Invalid,
104}
105
106#[cfg(feature = "decode")]
107impl core::error::Error for ParseHashError {}
108
109#[cfg(feature = "decode")]
110impl fmt::Display for ParseHashError {
111    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112        match self {
113            ParseHashError::WrongSize => f.write_str("string decoded to wrong size for hash"),
114            ParseHashError::Invalid => f.write_str("failed to decoded string to hash"),
115        }
116    }
117}
118
119#[cfg(feature = "decode")]
120impl FromStr for Hash {
121    type Err = ParseHashError;
122
123    fn from_str(s: &str) -> Result<Self, Self::Err> {
124        use five8::DecodeError;
125        if s.len() > MAX_BASE58_LEN {
126            return Err(ParseHashError::WrongSize);
127        }
128        let mut bytes = [0; HASH_BYTES];
129        five8::decode_32(s, &mut bytes).map_err(|e| match e {
130            DecodeError::InvalidChar(_) => ParseHashError::Invalid,
131            DecodeError::TooLong
132            | DecodeError::TooShort
133            | DecodeError::LargestTermTooHigh
134            | DecodeError::OutputTooLong => ParseHashError::WrongSize,
135        })?;
136        Ok(Self::from(bytes))
137    }
138}
139
140/// Custom impl of Hash for Hash.
141///
142/// This allows us to skip hashing the length of the hash
143/// which is always the same anyway.
144impl core::hash::Hash for Hash {
145    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
146        state.write(self.as_bytes());
147    }
148}
149
150impl Hash {
151    pub const fn new_from_array(hash_array: [u8; HASH_BYTES]) -> Self {
152        Self(hash_array)
153    }
154
155    /// unique Hash for tests and benchmarks.
156    #[cfg(feature = "atomic")]
157    pub fn new_unique() -> Self {
158        use solana_atomic_u64::AtomicU64;
159        static I: AtomicU64 = AtomicU64::new(1);
160
161        let mut b = [0u8; HASH_BYTES];
162        let i = I.fetch_add(1);
163        b[0..8].copy_from_slice(&i.to_le_bytes());
164        Self::new_from_array(b)
165    }
166
167    pub const fn to_bytes(&self) -> [u8; HASH_BYTES] {
168        self.0
169    }
170
171    pub const fn as_bytes(&self) -> &[u8; HASH_BYTES] {
172        &self.0
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn test_new_unique() {
182        assert!(Hash::new_unique() != Hash::new_unique());
183    }
184
185    #[test]
186    fn test_hash_fromstr() {
187        let hash = Hash::new_from_array([1; 32]);
188
189        let mut hash_base58_str = bs58::encode(hash).into_string();
190
191        assert_eq!(hash_base58_str.parse::<Hash>(), Ok(hash));
192
193        hash_base58_str.push_str(&bs58::encode(hash.as_ref()).into_string());
194        assert_eq!(
195            hash_base58_str.parse::<Hash>(),
196            Err(ParseHashError::WrongSize)
197        );
198
199        hash_base58_str.truncate(hash_base58_str.len() / 2);
200        assert_eq!(hash_base58_str.parse::<Hash>(), Ok(hash));
201
202        hash_base58_str.truncate(hash_base58_str.len() / 2);
203        assert_eq!(
204            hash_base58_str.parse::<Hash>(),
205            Err(ParseHashError::WrongSize)
206        );
207
208        let input_too_big = bs58::encode(&[0xffu8; HASH_BYTES + 1]).into_string();
209        assert!(input_too_big.len() > MAX_BASE58_LEN);
210        assert_eq!(
211            input_too_big.parse::<Hash>(),
212            Err(ParseHashError::WrongSize)
213        );
214
215        let mut hash_base58_str = bs58::encode(hash.as_ref()).into_string();
216        assert_eq!(hash_base58_str.parse::<Hash>(), Ok(hash));
217
218        // throw some non-base58 stuff in there
219        hash_base58_str.replace_range(..1, "I");
220        assert_eq!(
221            hash_base58_str.parse::<Hash>(),
222            Err(ParseHashError::Invalid)
223        );
224    }
225}