Skip to main content

solana_hash_512/
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 {
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
29/// Size of a hash in bytes.
30pub const HASH_BYTES: usize = 64;
31/// Maximum string length of a base58 encoded 64-byte hash.
32pub const MAX_BASE58_LEN: usize = 88;
33
34/// A hash; the 64-byte output of the SHA-512 hashing algorithm.
35#[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    // 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 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    /// unique Hash512 for tests and benchmarks.
121    #[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        // throw some non-base58 stuff in there
184        hash_base58_str.replace_range(..1, "I");
185        assert_eq!(
186            hash_base58_str.parse::<Hash512>(),
187            Err(ParseHashError::Invalid)
188        );
189    }
190}