|
| 1 | +// Utility hashing module copied from `solana_program::program::hash`, since we |
| 2 | +// can't import solana_program for compile time hashing for some reason. |
| 3 | + |
| 4 | +use serde::{Deserialize, Serialize}; |
| 5 | +use sha2::{Digest, Sha256}; |
| 6 | +use std::{convert::TryFrom, fmt, mem, str::FromStr}; |
| 7 | +use thiserror::Error; |
| 8 | + |
| 9 | +pub const HASH_BYTES: usize = 32; |
| 10 | +#[derive(Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] |
| 11 | +#[repr(transparent)] |
| 12 | +pub struct Hash(pub [u8; HASH_BYTES]); |
| 13 | + |
| 14 | +#[derive(Clone, Default)] |
| 15 | +pub struct Hasher { |
| 16 | + hasher: Sha256, |
| 17 | +} |
| 18 | + |
| 19 | +impl Hasher { |
| 20 | + pub fn hash(&mut self, val: &[u8]) { |
| 21 | + self.hasher.update(val); |
| 22 | + } |
| 23 | + pub fn hashv(&mut self, vals: &[&[u8]]) { |
| 24 | + for val in vals { |
| 25 | + self.hash(val); |
| 26 | + } |
| 27 | + } |
| 28 | + pub fn result(self) -> Hash { |
| 29 | + // At the time of this writing, the sha2 library is stuck on an old version |
| 30 | + // of generic_array (0.9.0). Decouple ourselves with a clone to our version. |
| 31 | + Hash(<[u8; HASH_BYTES]>::try_from(self.hasher.finalize().as_slice()).unwrap()) |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +impl AsRef<[u8]> for Hash { |
| 36 | + fn as_ref(&self) -> &[u8] { |
| 37 | + &self.0[..] |
| 38 | + } |
| 39 | +} |
| 40 | + |
| 41 | +impl fmt::Debug for Hash { |
| 42 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 43 | + write!(f, "{}", bs58::encode(self.0).into_string()) |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +impl fmt::Display for Hash { |
| 48 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 49 | + write!(f, "{}", bs58::encode(self.0).into_string()) |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +#[derive(Debug, Clone, PartialEq, Eq, Error)] |
| 54 | +pub enum ParseHashError { |
| 55 | + #[error("string decoded to wrong size for hash")] |
| 56 | + WrongSize, |
| 57 | + #[error("failed to decoded string to hash")] |
| 58 | + Invalid, |
| 59 | +} |
| 60 | + |
| 61 | +impl FromStr for Hash { |
| 62 | + type Err = ParseHashError; |
| 63 | + |
| 64 | + fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 65 | + let bytes = bs58::decode(s) |
| 66 | + .into_vec() |
| 67 | + .map_err(|_| ParseHashError::Invalid)?; |
| 68 | + if bytes.len() != mem::size_of::<Hash>() { |
| 69 | + Err(ParseHashError::WrongSize) |
| 70 | + } else { |
| 71 | + Ok(Hash::new(&bytes)) |
| 72 | + } |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +impl Hash { |
| 77 | + pub fn new(hash_slice: &[u8]) -> Self { |
| 78 | + Hash(<[u8; HASH_BYTES]>::try_from(hash_slice).unwrap()) |
| 79 | + } |
| 80 | + |
| 81 | + pub const fn new_from_array(hash_array: [u8; HASH_BYTES]) -> Self { |
| 82 | + Self(hash_array) |
| 83 | + } |
| 84 | + |
| 85 | + /// unique Hash for tests and benchmarks. |
| 86 | + pub fn new_unique() -> Self { |
| 87 | + use std::sync::atomic::{AtomicU64, Ordering}; |
| 88 | + static I: AtomicU64 = AtomicU64::new(1); |
| 89 | + |
| 90 | + let mut b = [0u8; HASH_BYTES]; |
| 91 | + let i = I.fetch_add(1, Ordering::Relaxed); |
| 92 | + b[0..8].copy_from_slice(&i.to_le_bytes()); |
| 93 | + Self::new(&b) |
| 94 | + } |
| 95 | + |
| 96 | + pub fn to_bytes(self) -> [u8; HASH_BYTES] { |
| 97 | + self.0 |
| 98 | + } |
| 99 | +} |
| 100 | + |
| 101 | +/// Return a Sha256 hash for the given data. |
| 102 | +pub fn hashv(vals: &[&[u8]]) -> Hash { |
| 103 | + // Perform the calculation inline, calling this from within a program is |
| 104 | + // not supported |
| 105 | + #[cfg(not(target_arch = "bpf"))] |
| 106 | + { |
| 107 | + let mut hasher = Hasher::default(); |
| 108 | + hasher.hashv(vals); |
| 109 | + hasher.result() |
| 110 | + } |
| 111 | + // Call via a system call to perform the calculation |
| 112 | + #[cfg(target_arch = "bpf")] |
| 113 | + { |
| 114 | + extern "C" { |
| 115 | + fn sol_sha256(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64; |
| 116 | + }; |
| 117 | + let mut hash_result = [0; HASH_BYTES]; |
| 118 | + unsafe { |
| 119 | + sol_sha256( |
| 120 | + vals as *const _ as *const u8, |
| 121 | + vals.len() as u64, |
| 122 | + &mut hash_result as *mut _ as *mut u8, |
| 123 | + ); |
| 124 | + } |
| 125 | + Hash::new_from_array(hash_result) |
| 126 | + } |
| 127 | +} |
| 128 | + |
| 129 | +/// Return a Sha256 hash for the given data. |
| 130 | +pub fn hash(val: &[u8]) -> Hash { |
| 131 | + hashv(&[val]) |
| 132 | +} |
| 133 | + |
| 134 | +/// Return the hash of the given hash extended with the given value. |
| 135 | +pub fn extend_and_hash(id: &Hash, val: &[u8]) -> Hash { |
| 136 | + let mut hash_data = id.as_ref().to_vec(); |
| 137 | + hash_data.extend_from_slice(val); |
| 138 | + hash(&hash_data) |
| 139 | +} |
0 commit comments