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