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
24pub const HASH_BYTES: usize = 32;
26pub const MAX_BASE58_LEN: usize = 44;
28
29#[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 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 #[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 hash_base58_str.replace_range(..1, "I");
200 assert_eq!(
201 hash_base58_str.parse::<Hash>(),
202 Err(ParseHashError::Invalid)
203 );
204 }
205}