A Python port of the Invisible Internet Project (I2P)
1"""SipHash — SipHash-2-4 implementation.
2
3Ported from net.i2p.util.SipHash / net.i2p.crypto.SipHashInline.
4Fast non-cryptographic hash for hash tables.
5"""
6
7import os
8import struct
9
10
11def _rotl64(v: int, n: int) -> int:
12 return ((v << n) | (v >> (64 - n))) & 0xFFFFFFFFFFFFFFFF
13
14
15def _sip_round(v0: int, v1: int, v2: int, v3: int):
16 v0 = (v0 + v1) & 0xFFFFFFFFFFFFFFFF
17 v1 = _rotl64(v1, 13)
18 v1 ^= v0
19 v0 = _rotl64(v0, 32)
20 v2 = (v2 + v3) & 0xFFFFFFFFFFFFFFFF
21 v3 = _rotl64(v3, 16)
22 v3 ^= v2
23 v0 = (v0 + v3) & 0xFFFFFFFFFFFFFFFF
24 v3 = _rotl64(v3, 21)
25 v3 ^= v0
26 v2 = (v2 + v1) & 0xFFFFFFFFFFFFFFFF
27 v1 = _rotl64(v1, 17)
28 v1 ^= v2
29 v2 = _rotl64(v2, 32)
30 return v0, v1, v2, v3
31
32
33# Per-process random keys (like Java's per-JVM constants)
34_K0 = struct.unpack("<Q", os.urandom(8))[0]
35_K1 = struct.unpack("<Q", os.urandom(8))[0]
36
37
38class SipHash:
39 """SipHash-2-4 implementation."""
40
41 @staticmethod
42 def digest(data: bytes, offset: int = 0, length: int = -1) -> int:
43 """Compute SipHash-2-4 digest. Returns a 64-bit integer."""
44 if length < 0:
45 length = len(data) - offset
46 return SipHash._siphash24(_K0, _K1, data, offset, length)
47
48 @staticmethod
49 def hash_code(data: bytes | None) -> int:
50 """Compute a 32-bit hash code from SipHash-2-4."""
51 if data is None:
52 return 0
53 h = SipHash.digest(data)
54 return h & 0xFFFFFFFF
55
56 @staticmethod
57 def _siphash24(k0: int, k1: int, data: bytes, off: int, length: int) -> int:
58 v0 = k0 ^ 0x736F6D6570736575
59 v1 = k1 ^ 0x646F72616E646F6D
60 v2 = k0 ^ 0x6C7967656E657261
61 v3 = k1 ^ 0x7465646279746573
62
63 # Process 8-byte blocks
64 end = off + (length & ~7)
65 for i in range(off, end, 8):
66 m = struct.unpack_from("<Q", data, i)[0]
67 v3 ^= m
68 v0, v1, v2, v3 = _sip_round(v0, v1, v2, v3)
69 v0, v1, v2, v3 = _sip_round(v0, v1, v2, v3)
70 v0 ^= m
71
72 # Process remaining bytes
73 b = (length & 0xFF) << 56
74 remaining = length & 7
75 tail_off = off + length - remaining
76 if remaining >= 7:
77 b |= data[tail_off + 6] << 48
78 if remaining >= 6:
79 b |= data[tail_off + 5] << 40
80 if remaining >= 5:
81 b |= data[tail_off + 4] << 32
82 if remaining >= 4:
83 b |= data[tail_off + 3] << 24
84 if remaining >= 3:
85 b |= data[tail_off + 2] << 16
86 if remaining >= 2:
87 b |= data[tail_off + 1] << 8
88 if remaining >= 1:
89 b |= data[tail_off]
90
91 v3 ^= b
92 v0, v1, v2, v3 = _sip_round(v0, v1, v2, v3)
93 v0, v1, v2, v3 = _sip_round(v0, v1, v2, v3)
94 v0 ^= b
95
96 # Finalization
97 v2 ^= 0xFF
98 v0, v1, v2, v3 = _sip_round(v0, v1, v2, v3)
99 v0, v1, v2, v3 = _sip_round(v0, v1, v2, v3)
100 v0, v1, v2, v3 = _sip_round(v0, v1, v2, v3)
101 v0, v1, v2, v3 = _sip_round(v0, v1, v2, v3)
102
103 return (v0 ^ v1 ^ v2 ^ v3) & 0xFFFFFFFFFFFFFFFF