Skip to main content

binius_hash/
serialization.rs

1// Copyright 2024-2025 Irreducible Inc.
2
3use std::{borrow::Borrow, cmp::min};
4
5use binius_utils::{SerializationError, SerializeBytes};
6use bytes::{BufMut, buf::UninitSlice};
7use digest::{
8	Digest, Output,
9	block_api::{Block, BlockSizeUser},
10};
11
12/// Adapter that wraps [`Digest`] references and exposes the [`BufMut`] interface.
13///
14/// This adapter is useful so that structs that implement [`SerializeBytes`] can be serialized
15/// directly to a hasher.
16#[derive(Debug)]
17pub struct HashBuffer<'a, D: Digest + BlockSizeUser> {
18	digest: &'a mut D,
19	block: Block<D>,
20	/// Invariant: `index` is always strictly less than `D::block_size()`.
21	index: usize,
22}
23
24impl<'a, D: Digest + BlockSizeUser> HashBuffer<'a, D> {
25	pub fn new(digest: &'a mut D) -> Self {
26		Self {
27			digest,
28			block: <Block<D>>::default(),
29			index: 0,
30		}
31	}
32
33	fn flush(&mut self) {
34		self.digest.update(&self.block.as_slice()[..self.index]);
35		self.index = 0;
36	}
37}
38
39// The buffer trait is unsafe to implement, so this is the crate's one exception to its ban.
40// The obligation is that the advertised capacity is really writable.
41// That holds because the write cursor never passes the end of the block it is filling.
42#[allow(unsafe_code)]
43unsafe impl<D: Digest + BlockSizeUser> BufMut for HashBuffer<'_, D> {
44	fn remaining_mut(&self) -> usize {
45		usize::MAX
46	}
47
48	unsafe fn advance_mut(&mut self, mut cnt: usize) {
49		while cnt > 0 {
50			let remaining = min(<D as BlockSizeUser>::block_size() - self.index, cnt);
51			cnt -= remaining;
52			self.index += remaining;
53			if self.index == <D as BlockSizeUser>::block_size() {
54				self.flush();
55			}
56		}
57	}
58
59	fn chunk_mut(&mut self) -> &mut UninitSlice {
60		let buffer = &mut self.block[self.index..];
61		buffer.into()
62	}
63}
64
65impl<D: Digest + BlockSizeUser> Drop for HashBuffer<'_, D> {
66	fn drop(&mut self) {
67		self.flush();
68	}
69}
70
71/// Hashes a sequence of serializable items.
72pub fn hash_serialize<T, D>(
73	items: impl IntoIterator<Item = impl Borrow<T>>,
74) -> Result<Output<D>, SerializationError>
75where
76	T: SerializeBytes,
77	D: Digest + BlockSizeUser,
78{
79	let mut hasher = D::new();
80	{
81		let mut buffer = HashBuffer::new(&mut hasher);
82		for item in items {
83			item.borrow().serialize(&mut buffer)?;
84		}
85	}
86	Ok(hasher.finalize())
87}
88
89#[cfg(test)]
90mod tests {
91	use super::*;
92	use crate::StdDigest;
93
94	#[test]
95	fn test_hash_buffer_updates() {
96		let message =
97			b"yo, listen up, here's the story about a little guy that lives in a blue world";
98		assert!(message.len() > 64);
99		assert!(message.len() < 128);
100
101		let expected_digest = StdDigest::digest(message);
102
103		let mut hasher = StdDigest::new();
104		{
105			let mut buffer = HashBuffer::new(&mut hasher);
106			buffer.put_slice(message);
107		}
108		assert_eq!(hasher.finalize(), expected_digest);
109	}
110}