use sha2::{Digest, Sha256};
#[cfg(feature = "std")]
use alloc::string::String;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TypeHash([u8; 32]);
impl TypeHash {
pub const fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn to_u64s(&self) -> (u64, u64, u64, u64) {
let b = &self.0;
(
u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]),
u64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]]),
u64::from_le_bytes([b[16], b[17], b[18], b[19], b[20], b[21], b[22], b[23]]),
u64::from_le_bytes([b[24], b[25], b[26], b[27], b[28], b[29], b[30], b[31]]),
)
}
pub fn from_u64s(a: u64, b: u64, c: u64, d: u64) -> Self {
let mut bytes = [0u8; 32];
bytes[0..8].copy_from_slice(&a.to_le_bytes());
bytes[8..16].copy_from_slice(&b.to_le_bytes());
bytes[16..24].copy_from_slice(&c.to_le_bytes());
bytes[24..32].copy_from_slice(&d.to_le_bytes());
Self(bytes)
}
#[cfg(feature = "std")]
pub fn to_hex(&self) -> String {
use core::fmt::Write;
let mut s = String::with_capacity(self.0.len() * 2);
for b in &self.0 {
let _ = write!(s, "{:02x}", b);
}
s
}
}
pub const HASH_BOOL: TypeHash = TypeHash::from_bytes([
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_U8: TypeHash = TypeHash::from_bytes([
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_U16: TypeHash = TypeHash::from_bytes([
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_U32: TypeHash = TypeHash::from_bytes([
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_U64: TypeHash = TypeHash::from_bytes([
0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_S8: TypeHash = TypeHash::from_bytes([
0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_S16: TypeHash = TypeHash::from_bytes([
0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_S32: TypeHash = TypeHash::from_bytes([
0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_S64: TypeHash = TypeHash::from_bytes([
0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_F32: TypeHash = TypeHash::from_bytes([
0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_F64: TypeHash = TypeHash::from_bytes([
0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_CHAR: TypeHash = TypeHash::from_bytes([
0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_STRING: TypeHash = TypeHash::from_bytes([
0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub const HASH_FLAGS: TypeHash = TypeHash::from_bytes([
0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
pub struct TypeHasher {
hasher: Sha256,
}
impl TypeHasher {
pub fn new() -> Self {
Self {
hasher: Sha256::new(),
}
}
pub fn tag(mut self, tag: u8) -> Self {
self.hasher.update([tag]);
self
}
pub fn string(mut self, s: &str) -> Self {
self.hasher.update((s.len() as u32).to_le_bytes());
self.hasher.update(s.as_bytes());
self
}
pub fn child(mut self, hash: &TypeHash) -> Self {
self.hasher.update(hash.as_bytes());
self
}
pub fn count(mut self, n: usize) -> Self {
self.hasher.update((n as u32).to_le_bytes());
self
}
pub fn finish(self) -> TypeHash {
let result = self.hasher.finalize();
let mut bytes = [0u8; 32];
bytes.copy_from_slice(&result);
TypeHash(bytes)
}
}
impl Default for TypeHasher {
fn default() -> Self {
Self::new()
}
}
const TAG_LIST: u8 = 0x10;
const TAG_OPTION: u8 = 0x11;
const TAG_RESULT: u8 = 0x12;
const TAG_TUPLE: u8 = 0x13;
const TAG_RECORD: u8 = 0x14;
const TAG_VARIANT: u8 = 0x15;
const TAG_FUNCTION: u8 = 0x16;
const TAG_INTERFACE: u8 = 0x17;
pub fn hash_list(element: &TypeHash) -> TypeHash {
TypeHasher::new().tag(TAG_LIST).child(element).finish()
}
pub fn hash_option(inner: &TypeHash) -> TypeHash {
TypeHasher::new().tag(TAG_OPTION).child(inner).finish()
}
pub fn hash_result(ok: &TypeHash, err: &TypeHash) -> TypeHash {
TypeHasher::new()
.tag(TAG_RESULT)
.child(ok)
.child(err)
.finish()
}
pub fn hash_tuple(elements: &[TypeHash]) -> TypeHash {
let mut hasher = TypeHasher::new().tag(TAG_TUPLE).count(elements.len());
for elem in elements {
hasher = hasher.child(elem);
}
hasher.finish()
}
pub fn hash_record(fields: &[(&str, TypeHash)]) -> TypeHash {
let mut hasher = TypeHasher::new().tag(TAG_RECORD).count(fields.len());
for (name, type_hash) in fields {
hasher = hasher.string(name).child(type_hash);
}
hasher.finish()
}
pub fn hash_variant(cases: &[(&str, Option<TypeHash>)]) -> TypeHash {
let mut hasher = TypeHasher::new().tag(TAG_VARIANT).count(cases.len());
for (name, payload) in cases {
hasher = hasher.string(name);
if let Some(type_hash) = payload {
hasher = hasher.tag(1).child(type_hash);
} else {
hasher = hasher.tag(0);
}
}
hasher.finish()
}
pub fn hash_function(params: &[TypeHash], results: &[TypeHash]) -> TypeHash {
let mut hasher = TypeHasher::new().tag(TAG_FUNCTION).count(params.len());
for param in params {
hasher = hasher.child(param);
}
hasher = hasher.count(results.len());
for result in results {
hasher = hasher.child(result);
}
hasher.finish()
}
pub struct Binding<'a> {
pub name: &'a str,
pub hash: TypeHash,
}
pub fn hash_interface(
name: &str,
type_bindings: &[Binding<'_>],
func_bindings: &[Binding<'_>],
) -> TypeHash {
let mut hasher = TypeHasher::new()
.tag(TAG_INTERFACE)
.string(name)
.count(type_bindings.len());
for binding in type_bindings {
hasher = hasher.string(binding.name).child(&binding.hash);
}
hasher = hasher.count(func_bindings.len());
for binding in func_bindings {
hasher = hasher.string(binding.name).child(&binding.hash);
}
hasher.finish()
}
pub const HASH_SELF_REF: TypeHash = TypeHash::from_bytes([
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_primitive_hashes_are_unique() {
let primitives = [
HASH_BOOL,
HASH_U8,
HASH_U16,
HASH_U32,
HASH_U64,
HASH_S8,
HASH_S16,
HASH_S32,
HASH_S64,
HASH_F32,
HASH_F64,
HASH_CHAR,
HASH_STRING,
HASH_FLAGS,
];
for (i, a) in primitives.iter().enumerate() {
for (j, b) in primitives.iter().enumerate() {
if i != j {
assert_ne!(a, b, "primitive hashes must be unique");
}
}
}
}
#[test]
fn test_list_hash() {
let list_s32 = hash_list(&HASH_S32);
let list_s64 = hash_list(&HASH_S64);
assert_ne!(list_s32, list_s64);
assert_ne!(list_s32, HASH_S32); }
#[test]
fn test_record_hash_is_structural() {
let point = hash_record(&[("x", HASH_S32), ("y", HASH_S32)]);
let vec2 = hash_record(&[("x", HASH_S32), ("y", HASH_S32)]);
assert_eq!(point, vec2, "same structure = same hash");
}
#[test]
fn test_record_hash_includes_field_names() {
let xy = hash_record(&[("x", HASH_S32), ("y", HASH_S32)]);
let ab = hash_record(&[("a", HASH_S32), ("b", HASH_S32)]);
assert_ne!(xy, ab, "different field names = different hash");
}
#[test]
fn test_tuple_vs_record() {
let tuple = hash_tuple(&[HASH_S32, HASH_S32]);
let record = hash_record(&[("x", HASH_S32), ("y", HASH_S32)]);
assert_ne!(tuple, record, "tuple != record");
}
#[test]
fn test_function_hash() {
let add = hash_function(&[HASH_S32, HASH_S32], &[HASH_S32]);
let sub = hash_function(&[HASH_S32, HASH_S32], &[HASH_S32]);
assert_eq!(add, sub);
}
#[test]
fn test_interface_includes_names() {
let iface_a = hash_interface(
"math",
&[],
&[Binding {
name: "add",
hash: hash_function(&[HASH_S32], &[HASH_S32]),
}],
);
let iface_b = hash_interface(
"math",
&[],
&[Binding {
name: "inc",
hash: hash_function(&[HASH_S32], &[HASH_S32]),
}],
);
assert_ne!(iface_a, iface_b);
}
#[test]
fn test_u64_roundtrip() {
let hash = hash_list(&HASH_S32);
let (a, b, c, d) = hash.to_u64s();
let back = TypeHash::from_u64s(a, b, c, d);
assert_eq!(hash, back);
}
}