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