mod hashers;
mod pow_hashers;
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Debug, Display, Formatter};
use std::hash::{Hash as StdHash, Hasher as StdHasher};
use std::str::{self, FromStr};
use wasm_bindgen::prelude::*;
use workflow_wasm::abi::ref_from_abi;
use workflow_wasm::jsvalue::JsValueTrait;
pub const HASH_SIZE: usize = 32;
pub use hashers::*;
#[derive(Eq, Clone, Copy, Default, PartialOrd, Ord, BorshSerialize, BorshDeserialize, BorshSchema)]
#[wasm_bindgen]
pub struct Hash([u8; HASH_SIZE]);
impl Hash {
#[inline(always)]
pub const fn from_bytes(bytes: [u8; HASH_SIZE]) -> Self {
Hash(bytes)
}
#[inline(always)]
pub const fn as_bytes(self) -> [u8; 32] {
self.0
}
#[inline(always)]
pub fn from_slice(bytes: &[u8]) -> Self {
Self(<[u8; HASH_SIZE]>::try_from(bytes).expect("Slice must have the length of Hash"))
}
#[inline(always)]
pub fn to_le_u64(self) -> [u64; 4] {
let mut out = [0u64; 4];
out.iter_mut().zip(self.iter_le_u64()).for_each(|(out, word)| *out = word);
out
}
#[inline(always)]
pub fn iter_le_u64(&self) -> impl ExactSizeIterator<Item = u64> + '_ {
self.0.chunks_exact(8).map(|chunk| u64::from_le_bytes(chunk.try_into().unwrap()))
}
#[inline(always)]
pub fn from_le_u64(arr: [u64; 4]) -> Self {
let mut ret = [0; HASH_SIZE];
ret.chunks_exact_mut(8).zip(arr.iter()).for_each(|(bytes, word)| bytes.copy_from_slice(&word.to_le_bytes()));
Self(ret)
}
#[inline(always)]
pub fn from_u64_word(word: u64) -> Self {
Self::from_le_u64([0, 0, 0, word])
}
}
impl StdHash for Hash {
#[inline(always)]
fn hash<H: StdHasher>(&self, state: &mut H) {
self.iter_le_u64().for_each(|x| x.hash(state));
}
}
impl PartialEq for Hash {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Display for Hash {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut hex = [0u8; HASH_SIZE * 2];
faster_hex::hex_encode(&self.0, &mut hex).expect("The output is exactly twice the size of the input");
f.write_str(str::from_utf8(&hex).expect("hex is always valid UTF-8"))
}
}
impl Debug for Hash {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self, f)
}
}
impl FromStr for Hash {
type Err = faster_hex::Error;
#[inline]
fn from_str(hash_str: &str) -> Result<Self, Self::Err> {
let mut bytes = [0u8; HASH_SIZE];
faster_hex::hex_decode(hash_str.as_bytes(), &mut bytes)?;
Ok(Hash(bytes))
}
}
impl From<u64> for Hash {
#[inline(always)]
fn from(word: u64) -> Self {
Self::from_u64_word(word)
}
}
impl AsRef<[u8]> for Hash {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Serialize for Hash {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for Hash {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = <std::string::String as Deserialize>::deserialize(deserializer)?;
FromStr::from_str(&s).map_err(serde::de::Error::custom)
}
}
#[wasm_bindgen]
impl Hash {
#[wasm_bindgen(constructor)]
pub fn constructor(hex_str: &str) -> Self {
Hash::from_str(hex_str).expect("invalid hash value")
}
#[wasm_bindgen(js_name = toString)]
pub fn to_str(&self) -> String {
self.to_string()
}
}
type TryFromError = workflow_wasm::error::Error;
impl TryFrom<JsValue> for Hash {
type Error = workflow_wasm::error::Error;
fn try_from(js_value: JsValue) -> Result<Self, Self::Error> {
let hash = if js_value.is_string() || js_value.is_array() {
let bytes = js_value.try_as_vec_u8()?;
Hash(
<[u8; HASH_SIZE]>::try_from(bytes)
.map_err(|_| TryFromError::WrongSize("Slice must have the length of Hash".into()))?,
)
} else if js_value.is_object() {
ref_from_abi!(Hash, &js_value).map_err(|_| TryFromError::WrongType("supplied object must be a `Hash`".to_string()))?
} else {
return Err(TryFromError::WrongType("supplied object must be a `Hash`".to_string()));
};
Ok(hash)
}
}
impl Hash {
pub fn try_vec_from_array(array: js_sys::Array) -> Result<Vec<Hash>, workflow_wasm::error::Error> {
let mut list = vec![];
for item in array.iter() {
list.push(item.try_into()?);
}
Ok(list)
}
}
pub const ZERO_HASH: Hash = Hash([0; HASH_SIZE]);
#[cfg(test)]
mod tests {
use super::Hash;
use std::str::FromStr;
#[test]
fn test_hash_basics() {
let hash_str = "8e40af02265360d59f4ecf9ae9ebf8f00a3118408f5a9cdcbcc9c0f93642f3af";
let hash = Hash::from_str(hash_str).unwrap();
assert_eq!(hash_str, hash.to_string());
let hash2 = Hash::from_str(hash_str).unwrap();
assert_eq!(hash, hash2);
let hash3 = Hash::from_str("8e40af02265360d59f4ecf9ae9ebf8f00a3118408f5a9cdcbcc9c0f93642f3ab").unwrap();
assert_ne!(hash2, hash3);
let odd_str = "8e40af02265360d59f4ecf9ae9ebf8f00a3118408f5a9cdcbcc9c0f93642f3a";
let short_str = "8e40af02265360d59f4ecf9ae9ebf8f00a3118408f5a9cdcbcc9c0f93642f3";
assert!(matches!(dbg!(Hash::from_str(odd_str)), Err(faster_hex::Error::InvalidLength(len)) if len == 64));
assert!(matches!(dbg!(Hash::from_str(short_str)), Err(faster_hex::Error::InvalidLength(len)) if len == 64));
}
}