A Python port of the Invisible Internet Project (I2P)
at main 103 lines 3.1 kB view raw
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