A Python port of the Invisible Internet Project (I2P)
1"""Hash data structures — fixed-length byte wrappers.
2
3Ported from net.i2p.data.Hash, net.i2p.crypto.SHA1Hash, Hash384, Hash512.
4"""
5
6import hashlib
7
8
9class Hash:
10 """32-byte SHA-256 hash container."""
11
12 HASH_LENGTH = 32
13
14 __slots__ = ("_data", "_cached_hash")
15
16 def __init__(self, data: bytes | None = None) -> None:
17 if data is not None:
18 if len(data) != self.HASH_LENGTH:
19 raise ValueError(f"Hash data must be {self.HASH_LENGTH} bytes, got {len(data)}")
20 self._data = bytes(data)
21 else:
22 self._data = b"\x00" * self.HASH_LENGTH
23 self._cached_hash = int.from_bytes(self._data[:4], "big")
24
25 @classmethod
26 def create(cls, data: bytes, offset: int = 0) -> "Hash":
27 return cls(data[offset : offset + cls.HASH_LENGTH])
28
29 @property
30 def data(self) -> bytes:
31 return self._data
32
33 def __eq__(self, other: object) -> bool:
34 if isinstance(other, Hash):
35 return self._data == other._data
36 return NotImplemented
37
38 def __hash__(self) -> int:
39 return self._cached_hash
40
41 def __repr__(self) -> str:
42 return f"Hash({self._data.hex()})"
43
44 def __bytes__(self) -> bytes:
45 return self._data
46
47 FAKE_HASH: "Hash"
48
49
50Hash.FAKE_HASH = Hash(b"\x00" * Hash.HASH_LENGTH)
51
52
53class SHA1Hash:
54 """20-byte SHA-1 hash container."""
55
56 HASH_LENGTH = 20
57
58 __slots__ = ("_data", "_cached_hash")
59
60 def __init__(self, data: bytes | None = None) -> None:
61 if data is not None:
62 if len(data) != self.HASH_LENGTH:
63 raise ValueError(f"SHA1Hash data must be {self.HASH_LENGTH} bytes, got {len(data)}")
64 self._data = bytes(data)
65 else:
66 self._data = b"\x00" * self.HASH_LENGTH
67 self._cached_hash = int.from_bytes(self._data[:4], "big")
68
69 @property
70 def data(self) -> bytes:
71 return self._data
72
73 def __eq__(self, other: object) -> bool:
74 if isinstance(other, SHA1Hash):
75 return self._data == other._data
76 return NotImplemented
77
78 def __hash__(self) -> int:
79 return self._cached_hash
80
81 def __repr__(self) -> str:
82 return f"SHA1Hash({self._data.hex()})"
83
84 def __bytes__(self) -> bytes:
85 return self._data
86
87
88class Hash384:
89 """48-byte hash container."""
90
91 HASH_LENGTH = 48
92
93 __slots__ = ("_data",)
94
95 def __init__(self, data: bytes | None = None) -> None:
96 if data is not None:
97 if len(data) != self.HASH_LENGTH:
98 raise ValueError(f"Hash384 data must be {self.HASH_LENGTH} bytes, got {len(data)}")
99 self._data = bytes(data)
100 else:
101 self._data = b"\x00" * self.HASH_LENGTH
102
103 @property
104 def data(self) -> bytes:
105 return self._data
106
107 def __eq__(self, other: object) -> bool:
108 if isinstance(other, Hash384):
109 return self._data == other._data
110 return NotImplemented
111
112 def __hash__(self) -> int:
113 return int.from_bytes(self._data[:4], "big")
114
115 def __bytes__(self) -> bytes:
116 return self._data
117
118
119class Hash512:
120 """64-byte hash container."""
121
122 HASH_LENGTH = 64
123
124 __slots__ = ("_data",)
125
126 def __init__(self, data: bytes | None = None) -> None:
127 if data is not None:
128 if len(data) != self.HASH_LENGTH:
129 raise ValueError(f"Hash512 data must be {self.HASH_LENGTH} bytes, got {len(data)}")
130 self._data = bytes(data)
131 else:
132 self._data = b"\x00" * self.HASH_LENGTH
133
134 @property
135 def data(self) -> bytes:
136 return self._data
137
138 def __eq__(self, other: object) -> bool:
139 if isinstance(other, Hash512):
140 return self._data == other._data
141 return NotImplemented
142
143 def __hash__(self) -> int:
144 return int.from_bytes(self._data[:4], "big")
145
146 def __bytes__(self) -> bytes:
147 return self._data