molframe_core/contract/policy/fingerprint.rs
1//! Telling one policy or input from another.
2//!
3//! The standard hasher is seeded afresh each process, which is right for hash
4//! tables and wrong here: a fingerprint that changes between runs identifies the
5//! run rather than the thing. This one does not.
6
7use std::fmt;
8use std::hash::Hasher;
9
10/// FNV-1a's specified 64-bit initial state.
11const FNV1A64_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
12/// FNV-1a's specified 64-bit prime multiplier.
13const FNV1A64_PRIME: u64 = 0x0000_0100_0000_01b3;
14
15/// A fast, stable 64-bit fingerprint of a policy, a record or a cache entry.
16///
17/// It is not collision resistant and must not be used to claim that two blocks of bytes are
18/// identical; [`ContentDigest`](crate::contract::ContentDigest) is SHA-256 and is what
19/// verification rests on.
20#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
21pub struct Fingerprint(u64);
22
23impl Fingerprint {
24 /// Wraps a finished hash.
25 pub(super) const fn from_hash(hash: u64) -> Self {
26 Self(hash)
27 }
28
29 /// Fingerprints a block of bytes.
30 #[must_use]
31 pub fn of(bytes: &[u8]) -> Self {
32 let mut hasher = Fnv1a::new();
33 hasher.write(bytes);
34 Self(hasher.finish())
35 }
36
37 /// The raw value.
38 #[must_use]
39 pub const fn get(self) -> u64 {
40 self.0
41 }
42}
43
44impl fmt::Display for Fingerprint {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 write!(f, "fnv1a64:{:016x}", self.0)
47 }
48}
49
50impl fmt::Debug for Fingerprint {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 write!(f, "{self}")
53 }
54}
55
56/// A hasher whose output does not depend on how the program was started.
57///
58/// The standard hasher is randomly seeded per process, which is right for
59/// hash tables and wrong here: a fingerprint that changes between runs cannot
60/// identify anything.
61pub(super) struct Fnv1a(u64);
62
63impl Fnv1a {
64 pub(super) const fn new() -> Self {
65 Self(FNV1A64_OFFSET_BASIS)
66 }
67}
68
69impl Hasher for Fnv1a {
70 fn finish(&self) -> u64 {
71 self.0
72 }
73
74 fn write(&mut self, bytes: &[u8]) {
75 for byte in bytes {
76 self.0 ^= u64::from(*byte);
77 // FNV-1a is defined modulo 2^64, so wrapping is part of the
78 // algorithm rather than an overflow fallback.
79 self.0 = self.0.wrapping_mul(FNV1A64_PRIME);
80 }
81 }
82}