binius_hash/vision_4/
compression.rs

1// Copyright 2025 Irreducible Inc.
2// Copyright 2026 The Binius Developers
3
4use binius_field::BinaryField128bGhash as Ghash;
5use binius_utils::{DeserializeBytes, SerializeBytes};
6use digest::Output;
7
8use super::{constants::M, digest::VisionHasherDigest, permutation::permutation};
9use crate::{CompressionFunction, PseudoCompressionFunction};
10
11/// Vision pseudo-compression function for 2-to-1 compression.
12///
13/// Uses the standard collision-resistant construction: `h(x) = Trunc(p(x) ⊕ x)`
14/// where `p` is the Vision permutation. The inputs are combined, passed through
15/// the permutation, then the original input state is added back before truncating
16/// to the output size. This is the
17/// [Matyas–Meyer–Oseas](en.wikipedia.org/wiki/One-way_compression_function#Matyas–Meyer–Oseas) (or
18/// MMO) compression function construction.
19#[derive(Clone, Debug, Default)]
20pub struct VisionCompression;
21
22impl PseudoCompressionFunction<Output<VisionHasherDigest>, 2> for VisionCompression {
23	fn compress(&self, input: [Output<VisionHasherDigest>; 2]) -> Output<VisionHasherDigest> {
24		// Step 1: Deserialize each 32-byte input into 2 Ghash elements
25		let mut state: [Ghash; M] = [
26			Ghash::deserialize(&input[0][0..16]).expect("16 bytes fits in Ghash"),
27			Ghash::deserialize(&input[0][16..32]).expect("16 bytes fits in Ghash"),
28			Ghash::deserialize(&input[1][0..16]).expect("16 bytes fits in Ghash"),
29			Ghash::deserialize(&input[1][16..32]).expect("16 bytes fits in Ghash"),
30		];
31
32		// Step 2: Copy original first 2 state elements
33		let original_first = state[0];
34		let original_second = state[1];
35
36		// Step 3: Apply Vision permutation
37		permutation(&mut state);
38
39		// Step 4: Add original first 2 elements to permuted result
40		state[0] += original_first;
41		state[1] += original_second;
42
43		// Step 5: Serialize first 2 elements (left half) back to 32 bytes
44		let mut output = Output::<VisionHasherDigest>::default();
45		let (left, right) = output.as_mut_slice().split_at_mut(16);
46		state[0].serialize(left).expect("fits in 16 bytes");
47		state[1].serialize(right).expect("fits in 16 bytes");
48		// Note: state[2] and state[3] are discarded (right half truncated)
49
50		output
51	}
52}
53
54impl CompressionFunction<Output<VisionHasherDigest>, 2> for VisionCompression {}