Skip to main content

sva_formula/
content_hash.rs

1// Concern: the SipHash-1-3-128 every content address in this workspace is built from, one key per domain | Non-concern: what any one address keys | IO: (domain, words) -> Hash
2
3use crate::hash::Hash;
4
5#[derive(Clone, Copy, PartialEq, Eq, Debug)]
6pub enum HashDomain {
7    WrittenClosedForm,
8    ClosedFormInFrequency,
9    ReadTime,
10    NodeIdentity,
11    CacheAddress,
12    GainBoundKey,
13    ProfileName,
14}
15
16impl HashDomain {
17    fn key(self) -> (u64, u64) {
18        (DOMAIN_KEY, self as u64)
19    }
20}
21
22const DOMAIN_KEY: u64 = 0x7264_6461_2061_7673;
23
24/// SipHash-1-3-128 over whole words: taken as a random function, `n` inputs in one domain collide
25/// with odds at most `n(n-1)/2^129`, against accident only, as its keys are public.
26#[derive(Clone)]
27pub struct ContentHasher {
28    v: [u64; 4],
29    words: u64,
30}
31
32impl ContentHasher {
33    pub fn new(domain: HashDomain) -> ContentHasher {
34        let (k0, k1) = domain.key();
35        ContentHasher {
36            v: [
37                k0 ^ 0x736f_6d65_7073_6575,
38                k1 ^ 0x646f_7261_6e64_6f6d ^ 0xee,
39                k0 ^ 0x6c79_6765_6e65_7261,
40                k1 ^ 0x7465_6462_7974_6573,
41            ],
42            words: 0,
43        }
44    }
45
46    pub fn word(&mut self, word: u64) {
47        self.compress(word);
48        self.words += 1;
49    }
50
51    pub fn hash(&mut self, held: Hash) {
52        self.word(held.0);
53        self.word(held.1);
54    }
55
56    pub fn text(&mut self, text: &str) {
57        self.word(text.len() as u64);
58        for chunk in text.as_bytes().chunks(8) {
59            let mut bytes = [0; 8];
60            bytes[..chunk.len()].copy_from_slice(chunk);
61            self.word(u64::from_le_bytes(bytes));
62        }
63    }
64
65    pub fn finish(mut self) -> Hash {
66        let length = (self.words.wrapping_mul(8) & 0xff) << 56;
67        self.compress(length);
68        self.v[2] ^= 0xee;
69        self.rounds(3);
70        let low = self.v.iter().fold(0, |held, v| held ^ v);
71        self.v[1] ^= 0xdd;
72        self.rounds(3);
73        let high = self.v.iter().fold(0, |held, v| held ^ v);
74        Hash(low, high)
75    }
76
77    fn compress(&mut self, m: u64) {
78        self.v[3] ^= m;
79        self.rounds(1);
80        self.v[0] ^= m;
81    }
82
83    fn rounds(&mut self, n: usize) {
84        let [v0, v1, v2, v3] = &mut self.v;
85        for _ in 0..n {
86            *v0 = v0.wrapping_add(*v1);
87            *v1 = v1.rotate_left(13) ^ *v0;
88            *v0 = v0.rotate_left(32);
89            *v2 = v2.wrapping_add(*v3);
90            *v3 = v3.rotate_left(16) ^ *v2;
91            *v0 = v0.wrapping_add(*v3);
92            *v3 = v3.rotate_left(21) ^ *v0;
93            *v2 = v2.wrapping_add(*v1);
94            *v1 = v1.rotate_left(17) ^ *v2;
95            *v2 = v2.rotate_left(32);
96        }
97    }
98}