hyperloglog_rs/
sip.rs

1//! Implementation of SipHash from [Jean-Philippe Aumasson](https://www.aumasson.jp/) and Daniel J. Bernstein.
2//!
3//! SipHash is a general-purpose hashing function: it runs at a good
4//! speed (competitive with Spooky and City) and permits strong _keyed_
5//! hashing. This lets you key your hash tables from a strong RNG, such as
6//! [`rand::os::OsRng`](https://docs.rs/rand/latest/rand/rngs/struct.OsRng.html).
7//!
8//! Although the SipHash algorithm is considered to be generally strong,
9//! it is not intended for cryptographic purposes. As such, all
10//! cryptographic uses of this implementation are _strongly discouraged
11//!
12//! # Reference
13//! - <https://www.aumasson.jp/siphash/siphash.pdf>
14//! - <https://131002.net/siphash/>
15//! - <https://github.com/floodyberry/supercop/blob/master/crypto_auth/siphash24/sse41/siphash.c>
16//! - <https://github.com/google/highwayhash/blob/master/highwayhash/sip_hash.h>
17//! - <https://github.com/rust-lang/rust/blob/master/library/core/src/hash/sip.rs>
18//!
19//! # Reimplementation reasons
20//! - The rust implementation is not const generic and is deprecated and has no simd optimzations
21//! - The [most popular rust library](https://github.com/jedisct1/rust-siphash/tree/master)
22//!  is just a port of rust implementation and has the same problems minus the deprecation
23
24/// Loads an integer of the desired type from a byte stream, in LE order. Uses
25/// `copy_nonoverlapping` to let the compiler generate the most efficient way
26/// to load it from a possibly unaligned address.
27///
28/// Unsafe because: unchecked indexing at `i..i+size_of(int_ty)`
29macro_rules! load_int_le {
30    ($buf:expr, $i:expr, $int_ty:ident) => {{
31        debug_assert!($i + core::mem::size_of::<$int_ty>() <= $buf.len());
32        let mut data = 0 as $int_ty;
33        core::ptr::copy_nonoverlapping(
34            $buf.as_ptr().add($i),
35            &mut data as *mut _ as *mut u8,
36            core::mem::size_of::<$int_ty>(),
37        );
38        data.to_le()
39    }};
40}
41
42/// Loads a u64 using up to 7 bytes of a byte slice. It looks clumsy but the
43/// `copy_nonoverlapping` calls that occur (via `load_int_le!`) all have fixed
44/// sizes and avoid calling `memcpy`, which is good for speed.
45///
46/// Unsafe because: unchecked indexing at start..start+len
47#[inline]
48unsafe fn u8to64_le(buf: &[u8], start: usize, len: usize) -> u64 {
49    debug_assert!(len < 8);
50    let mut i = 0; // current byte index (from LSB) in the output u64
51    let mut out = 0;
52    if i + 3 < len {
53        out = load_int_le!(buf, start + i, u32) as u64;
54        i += 4;
55    }
56    if i + 1 < len {
57        out |= (load_int_le!(buf, start + i, u16) as u64) << (i * 8);
58        i += 2
59    }
60    if i < len {
61        out |= (*buf.get_unchecked(start + i) as u64) << (i * 8);
62        i += 1;
63    }
64    debug_assert_eq!(i, len);
65    out
66}
67
68/// Porting of 64-bit SipHash from https://github.com/veorq/SipHash
69#[derive(Debug, Clone, Copy)]
70#[repr(align(16), C)] //alignement and C repr so the compiler can use simd instructions
71pub struct Sip64Scalar<const C: usize, const D: usize> {
72    // interleave the v values so that the compiler can optimize with simd
73    v0: u64,
74    v2: u64,
75    v1: u64,
76    v3: u64,
77    k0: u64,
78    k1: u64,
79    /// precedent message
80    m: u64,
81    /// how many bytes we've processed
82    length: usize,
83    /// buffer of unprocessed bytes in little endian order
84    tail: u64,
85    /// how many bytes in tail are valid
86    ntail: usize,
87}
88
89impl<const C: usize, const D: usize> core::default::Default for Sip64Scalar<C, D> {
90    #[inline]
91    fn default() -> Self {
92        Self::new()
93    }
94}
95
96impl<const C: usize, const D: usize> Sip64Scalar<C, D> {
97    #[inline]
98    pub fn new() -> Self {
99        // same default key as rust implementation
100        Self::new_with_key([0; 16])
101    }
102
103    #[inline(always)]
104    pub fn new_with_key(key: [u8; 16]) -> Self {
105        Self::new_with_key_and_state(
106            key,
107            [
108                0x736f6d6570736575,
109                0x646f72616e646f6d,
110                0x6c7967656e657261,
111                0x7465646279746573,
112            ],
113        )
114    }
115
116    #[inline(always)]
117    pub fn new_with_key_and_state(key: [u8; 16], state: [u64; 4]) -> Self {
118        let mut res = Self {
119            v0: state[0],
120            v1: state[1],
121            v2: state[2],
122            v3: state[3],
123            k0: u64::from_le_bytes(key[0..8].try_into().unwrap()),
124            k1: u64::from_le_bytes(key[8..16].try_into().unwrap()),
125            m: 0,
126            length: 0,
127            tail: 0,
128            ntail: 0,
129        };
130
131        res.v3 ^= res.k1;
132        res.v2 ^= res.k0;
133        res.v1 ^= res.k1;
134        res.v0 ^= res.k0;
135
136        res
137    }
138
139    #[inline(always)]
140    fn round(&mut self) {
141        self.v0 = self.v0.wrapping_add(self.v1);
142        self.v2 = self.v2.wrapping_add(self.v3);
143
144        self.v1 = self.v1.rotate_left(13);
145        self.v3 = self.v3.rotate_left(16);
146
147        self.v1 ^= self.v0;
148        self.v3 ^= self.v2;
149
150        self.v0 = self.v0.rotate_left(32);
151
152        self.v0 = self.v0.wrapping_add(self.v3);
153        self.v2 = self.v2.wrapping_add(self.v1);
154
155        self.v1 = self.v1.rotate_left(17);
156        self.v3 = self.v3.rotate_left(21);
157
158        self.v3 ^= self.v0;
159        self.v1 ^= self.v2;
160
161        self.v2 = self.v2.rotate_left(32);
162    }
163
164    // A specialized write function for values with size <= 8.
165    //
166    // The hashing of multi-byte integers depends on endianness. E.g.:
167    // - little-endian: `write_u32(0xDDCCBBAA)` == `write([0xAA, 0xBB, 0xCC, 0xDD])`
168    // - big-endian:    `write_u32(0xDDCCBBAA)` == `write([0xDD, 0xCC, 0xBB, 0xAA])`
169    //
170    // This function does the right thing for little-endian hardware. On
171    // big-endian hardware `x` must be byte-swapped first to give the right
172    // behaviour. After any byte-swapping, the input must be zero-extended to
173    // 64-bits. The caller is responsible for the byte-swapping and
174    // zero-extension.
175    #[inline]
176    pub fn short_write<T>(&mut self, _x: T, x: u64) {
177        let size = core::mem::size_of::<T>();
178        self.length += size;
179
180        // The original number must be zero-extended, not sign-extended.
181        debug_assert!(if size < 8 { x >> (8 * size) == 0 } else { true });
182
183        // The number of bytes needed to fill `self.tail`.
184        let needed = 8 - self.ntail;
185
186        self.tail |= x << (8 * self.ntail);
187        if size < needed {
188            self.ntail += size;
189            return;
190        }
191
192        // `self.tail` is full, process it.
193        self.v3 ^= self.tail;
194        for _ in 0..C {
195            self.round();
196        }
197        self.v0 ^= self.tail;
198
199        self.ntail = size - needed;
200        self.tail = if needed < 8 { x >> (8 * needed) } else { 0 };
201    }
202
203    pub fn finish_tuple(&self) -> (u64, u64) {
204        let mut state = *self;
205
206        let b: u64 = ((self.length as u64 & 0xff) << 56) | self.tail;
207
208        state.v3 ^= b;
209        for _ in 0..C {
210            state.round();
211        }
212        state.v0 ^= b;
213
214        state.v2 ^= 0xff;
215        for _ in 0..D {
216            state.round();
217        }
218
219        (state.v0 ^ state.v2, state.v1 ^ state.v3)
220    }
221}
222
223impl<const C: usize, const D: usize> core::hash::Hasher for Sip64Scalar<C, D> {
224    #[inline]
225    fn write(&mut self, msg: &[u8]) {
226        let length = msg.len();
227        self.length += length;
228
229        let mut needed = 0;
230
231        if self.ntail != 0 {
232            needed = 8 - self.ntail;
233            self.tail |=
234                unsafe { u8to64_le(msg, 0, core::cmp::min(length, needed)) } << (8 * self.ntail);
235            if length < needed {
236                self.ntail += length;
237                return;
238            } else {
239                self.v3 ^= self.tail;
240                for _ in 0..C {
241                    self.round();
242                }
243                self.v0 ^= self.tail;
244                self.ntail = 0;
245            }
246        }
247
248        // Buffered tail is now flushed, process new input.
249        let len = length - needed;
250        let left = len & 0x7;
251
252        let mut i = needed;
253        while i < len - left {
254            let mi = unsafe { load_int_le!(msg, i, u64) };
255
256            self.v3 ^= mi;
257            for _ in 0..C {
258                self.round();
259            }
260            self.v0 ^= mi;
261
262            i += 8;
263        }
264
265        self.tail = unsafe { u8to64_le(msg, i, left) };
266        self.ntail = left;
267    }
268
269    #[inline]
270    fn finish(&self) -> u64 {
271        let (first, second) = self.finish_tuple();
272        first ^ second
273    }
274
275    #[inline]
276    fn write_usize(&mut self, i: usize) {
277        self.short_write(i, i.to_le() as u64);
278    }
279
280    #[inline]
281    fn write_u8(&mut self, i: u8) {
282        self.short_write(i, i as u64);
283    }
284
285    #[inline]
286    fn write_u32(&mut self, i: u32) {
287        self.short_write(i, i.to_le() as u64);
288    }
289
290    #[inline]
291    fn write_u64(&mut self, i: u64) {
292        self.short_write(i, i.to_le());
293    }
294}
295
296/// Porting of 128-bit SipHash from https://github.com/veorq/SipHash
297#[derive(Debug, Clone, Copy)]
298#[repr(transparent)]
299pub struct Sip128Scalar<const C: usize, const D: usize>(Sip64Scalar<C, D>);
300
301impl<const C: usize, const D: usize> core::default::Default for Sip128Scalar<C, D> {
302    #[inline]
303    fn default() -> Self {
304        Self::new()
305    }
306}
307
308impl<const C: usize, const D: usize> Sip128Scalar<C, D> {
309    #[inline]
310    pub fn new() -> Self {
311        Self(<Sip64Scalar<C, D>>::new())
312    }
313}
314
315impl<const C: usize, const D: usize> core::hash::Hasher for Sip128Scalar<C, D> {
316    #[inline(always)]
317    fn write(&mut self, msg: &[u8]) {
318        self.0.write(msg)
319    }
320
321    #[inline]
322    fn finish(&self) -> u64 {
323        let (high, low) = self.finish_tuple();
324
325        high ^ low
326    }
327
328    #[inline]
329    fn write_usize(&mut self, i: usize) {
330        self.0.write_usize(i);
331    }
332
333    #[inline]
334    fn write_u8(&mut self, i: u8) {
335        self.0.write_u8(i);
336    }
337
338    #[inline]
339    fn write_u32(&mut self, i: u32) {
340        self.0.write_u32(i);
341    }
342
343    #[inline]
344    fn write_u64(&mut self, i: u64) {
345        self.0.write_u64(i);
346    }
347}
348
349impl<const C: usize, const D: usize> Sip128Scalar<C, D> {
350    #[inline]
351    pub fn finish_tuple(&self) -> (u64, u64) {
352        let mut state = *self;
353
354        let b: u64 = ((self.0.length as u64 & 0xff) << 56) | self.0.tail;
355
356        state.0.v3 ^= b;
357        for _ in 0..C {
358            state.0.round();
359        }
360        state.0.v0 ^= b;
361
362        state.0.v2 ^= 0xff;
363        for _ in 0..D {
364            state.0.round();
365        }
366
367        let low = state.0.v0 ^ state.0.v1 ^ state.0.v2 ^ state.0.v3;
368
369        state.0.v1 ^= 0xdd;
370        for _ in 0..D {
371            state.0.round();
372        }
373
374        let high = state.0.v0 ^ state.0.v1 ^ state.0.v2 ^ state.0.v3;
375
376        (high, low)
377    }
378}
379
380#[cfg(test)]
381#[cfg(feature = "std")]
382mod test {
383    use core::hash::Hasher;
384
385    use super::*;
386    #[test]
387    fn test_siphasher() {
388        let data = (0..255_u8).collect::<Vec<_>>();
389        for i in 0..16 {
390            #[allow(deprecated)]
391            let mut sip =
392                core::hash::SipHasher::new_with_keys(0x0706050403020100, 0x0f0e0d0c0b0a0908);
393            sip.write(&data[..i]);
394            let truth = sip.finish();
395
396            let mut sip = <Sip64Scalar<2, 4>>::new_with_key([
397                0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
398            ]);
399            sip.write(&data[..i]);
400            assert_eq!(sip.finish(), truth);
401        }
402    }
403    #[test]
404    fn test_default() {
405        let data = (0..255_u8).collect::<Vec<_>>();
406        for i in 0..16 {
407            #[allow(deprecated)]
408            let mut sip = std::collections::hash_map::DefaultHasher::new();
409            sip.write(&data[..i]);
410            let truth = sip.finish();
411
412            let mut sip = <Sip64Scalar<1, 3>>::new();
413            sip.write(&data[..i]);
414            assert_eq!(sip.finish(), truth);
415        }
416    }
417}