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
28pub const HASH_BYTES: usize = 32;
30pub const MAX_BASE58_LEN: usize = 44;
32
33#[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 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 #[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 hash_base58_str.replace_range(..1, "I");
205 assert_eq!(
206 hash_base58_str.parse::<Hash>(),
207 Err(ParseHashError::Invalid)
208 );
209 }
210}