1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// Copyright 2023-2024 Ulvetanna Inc.

use std::marker::PhantomData;

/// Trait representing the simplest and most straight forward use case of a hash function
///
/// This is a highly simplified trait that just treats the entire interface of a hash as a
/// simple function that takes in a slice of inputs and gives you back an output.
pub trait HashDigest<T> {
	/// The hash function output type.
	type Digest;
	fn hash(data: impl AsRef<[T]>) -> Self::Digest;
}

/// Wrapper over structs that implement [`Hasher`] to provide default implementation for [`HashDigest`]
pub struct HasherDigest<T, H: Hasher<T>> {
	_t_marker: PhantomData<T>,
	_h_marker: PhantomData<H>,
}

impl<T, H: Hasher<T>> HashDigest<T> for HasherDigest<T, H> {
	type Digest = <H as Hasher<T>>::Digest;

	fn hash(data: impl AsRef<[T]>) -> Self::Digest {
		hasher_hash::<T, H>(data)
	}
}

/// Wrapper over structs that implement [`FixedLenHasher`] to provide default implementation for [`HashDigest`]
pub struct FixedLenHasherDigest<T, H: FixedLenHasher<T>> {
	_t_marker: PhantomData<T>,
	_h_marker: PhantomData<H>,
}

impl<T, H: FixedLenHasher<T>> HashDigest<T> for FixedLenHasherDigest<T, H> {
	type Digest = <H as FixedLenHasher<T>>::Digest;

	fn hash(data: impl AsRef<[T]>) -> Self::Digest {
		fixed_len_hash::<T, H>(data)
	}
}

/// Trait representing cryptographic hash functions which is generic over the input type.
///
/// This interface is largely based on RustCrypto's Digest trait, except that instead of
/// requiring byte strings as input and byte arrays as output, this is generic over the input
/// values and has a less constrained output digest type.
pub trait Hasher<T> {
	/// The hash function output type.
	type Digest;

	fn new() -> Self;
	fn update(&mut self, data: impl AsRef<[T]>);
	fn chain_update(self, data: impl AsRef<[T]>) -> Self;
	fn finalize(self) -> Self::Digest;
	fn finalize_into(self, out: &mut Self::Digest);

	fn finalize_reset(&mut self) -> Self::Digest;
	fn finalize_into_reset(&mut self, out: &mut Self::Digest);
	fn reset(&mut self);
}

#[derive(Debug, thiserror::Error)]
pub enum HashError {
	#[error("Not enough data to finalize hash (expected {committed} elements, hashed {hashed} elements)")]
	NotEnoughData { committed: u64, hashed: u64 },
	#[error("Too much data to hash (expected {committed} elements, received {received} elements)")]
	TooMuchData { committed: u64, received: u64 },
}

/// Trait representing a family of fixed-length cryptographic hash functions which is generic over
/// the input type.
///
/// A fixed-length hash has the property that the amount of data in the preimage is fixed, and so
/// must be specified up front when constructing the hasher. The `[FixedLenHasher::finalize]` will
/// fail if the amount of data the hasher is updated with does not match the specified length. A
/// family of fixed-length hash functions retains the property that is it impossible to find
/// collisions between any two inputs, even those differing in length.
///
/// This interface is otherwise similar to the [`Hasher`] trait.
pub trait FixedLenHasher<T>
where
	Self: Sized,
{
	/// The hash function output type.
	type Digest;

	/// Constructor.
	///
	/// `msg_len` is the total number of `T` elements to be hashed
	fn new(msg_len: u64) -> Self;
	fn update(&mut self, data: impl AsRef<[T]>);
	fn chain_update(self, data: impl AsRef<[T]>) -> Self;
	fn finalize(self) -> Result<Self::Digest, HashError>;

	/// Resets with length initialized to the current context
	fn reset(&mut self);
}

pub fn hasher_hash<T, H: Hasher<T>>(data: impl AsRef<[T]>) -> H::Digest {
	H::new().chain_update(data).finalize()
}

pub fn fixed_len_hash<T, H: FixedLenHasher<T>>(data: impl AsRef<[T]>) -> H::Digest {
	H::new(data.as_ref().len() as u64)
		.chain_update(data)
		.finalize()
		.unwrap()
}