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 {
17 serde_big_array::BigArray,
18 serde_derive::{Deserialize, Serialize},
19};
20#[cfg(feature = "borsh")]
21extern crate alloc;
22#[cfg(feature = "borsh")]
23use alloc::string::ToString;
24#[cfg(feature = "decode")]
25pub use solana_hash::ParseHashError;
26#[cfg(feature = "wincode")]
27use wincode::{SchemaRead, SchemaWrite};
28
29pub const HASH_BYTES: usize = 64;
31pub const MAX_BASE58_LEN: usize = 88;
33
34#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
36#[cfg_attr(
37 feature = "borsh",
38 derive(BorshSerialize, BorshDeserialize),
39 borsh(crate = "borsh")
40)]
41#[cfg_attr(feature = "borsh", derive(BorshSchema))]
42#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
43#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
44#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
45#[cfg_attr(feature = "copy", derive(Copy))]
46#[cfg_attr(not(feature = "decode"), derive(Debug))]
47#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
48#[repr(transparent)]
49pub struct Hash512(
50 #[cfg_attr(feature = "serde", serde(with = "BigArray"))] pub(crate) [u8; HASH_BYTES],
51);
52
53impl Default for Hash512 {
54 fn default() -> Self {
55 Self([0u8; HASH_BYTES])
56 }
57}
58
59impl From<[u8; HASH_BYTES]> for Hash512 {
60 fn from(from: [u8; HASH_BYTES]) -> Self {
61 Self(from)
62 }
63}
64
65impl AsRef<[u8]> for Hash512 {
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: &Hash512) -> fmt::Result {
73 let mut out = [0u8; MAX_BASE58_LEN];
74 let len = five8::encode_64(&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 Hash512 {
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 Hash512 {
89 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90 write_as_base58(f, self)
91 }
92}
93
94#[cfg(feature = "decode")]
95impl FromStr for Hash512 {
96 type Err = ParseHashError;
97
98 fn from_str(s: &str) -> Result<Self, Self::Err> {
99 use five8::DecodeError;
100 if s.len() > MAX_BASE58_LEN {
101 return Err(ParseHashError::WrongSize);
102 }
103 let mut bytes = [0; HASH_BYTES];
104 five8::decode_64(s, &mut bytes).map_err(|e| match e {
105 DecodeError::InvalidChar(_) => ParseHashError::Invalid,
106 DecodeError::TooLong
107 | DecodeError::TooShort
108 | DecodeError::LargestTermTooHigh
109 | DecodeError::OutputTooLong => ParseHashError::WrongSize,
110 })?;
111 Ok(Self::from(bytes))
112 }
113}
114
115impl Hash512 {
116 pub const fn new_from_array(hash_array: [u8; HASH_BYTES]) -> Self {
117 Self(hash_array)
118 }
119
120 #[cfg(feature = "atomic")]
122 pub fn new_unique() -> Self {
123 use solana_atomic_u64::AtomicU64;
124 static I: AtomicU64 = AtomicU64::new(1);
125
126 let mut b = [0u8; HASH_BYTES];
127 let i = I.fetch_add(1);
128 b[0..8].copy_from_slice(&i.to_le_bytes());
129 Self::new_from_array(b)
130 }
131
132 pub const fn to_bytes(&self) -> [u8; HASH_BYTES] {
133 self.0
134 }
135
136 pub const fn as_bytes(&self) -> &[u8; HASH_BYTES] {
137 &self.0
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_new_unique() {
147 assert_ne!(Hash512::new_unique(), Hash512::new_unique());
148 }
149
150 #[test]
151 fn test_hash512_fromstr() {
152 let hash = Hash512::new_from_array([1; HASH_BYTES]);
153
154 let mut hash_base58_str = bs58::encode(hash).into_string();
155
156 assert_eq!(hash_base58_str.parse::<Hash512>(), Ok(hash));
157
158 hash_base58_str.push_str(&bs58::encode(hash.as_ref()).into_string());
159 assert_eq!(
160 hash_base58_str.parse::<Hash512>(),
161 Err(ParseHashError::WrongSize)
162 );
163
164 hash_base58_str.truncate(hash_base58_str.len() / 2);
165 assert_eq!(hash_base58_str.parse::<Hash512>(), Ok(hash));
166
167 hash_base58_str.truncate(hash_base58_str.len() / 2);
168 assert_eq!(
169 hash_base58_str.parse::<Hash512>(),
170 Err(ParseHashError::WrongSize)
171 );
172
173 let input_too_big = bs58::encode(&[0xffu8; HASH_BYTES + 1]).into_string();
174 assert!(input_too_big.len() > MAX_BASE58_LEN);
175 assert_eq!(
176 input_too_big.parse::<Hash512>(),
177 Err(ParseHashError::WrongSize)
178 );
179
180 let mut hash_base58_str = bs58::encode(hash.as_ref()).into_string();
181 assert_eq!(hash_base58_str.parse::<Hash512>(), Ok(hash));
182
183 hash_base58_str.replace_range(..1, "I");
185 assert_eq!(
186 hash_base58_str.parse::<Hash512>(),
187 Err(ParseHashError::Invalid)
188 );
189 }
190}