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