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