Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions multihash/funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,42 @@ def copy(self) -> "ShakeHash":
return c


def _create_keccak_hash(digest_bits: int):
"""Create a hashlib-compatible Keccak wrapper for the given digest bit length."""
try:
from Crypto.Hash import keccak as _keccak_mod
except ImportError:
return None

class KeccakHash:
name = f"keccak-{digest_bits}"
digest_size = digest_bits // 8
block_size = 200 - 2 * (digest_bits // 8)

def __init__(self) -> None:
self._digest_bits = digest_bits
self._data = bytearray()
self._hasher = _keccak_mod.new(digest_bits=digest_bits)

def update(self, data: bytes) -> None:
self._data.extend(data)
self._hasher.update(data)

def digest(self) -> bytes:
return self._hasher.digest()

def hexdigest(self) -> str:
return self._hasher.hexdigest()

def copy(self) -> "KeccakHash":
c = KeccakHash()
if self._data:
c.update(bytes(self._data))
return c

return KeccakHash


class Blake3Hash:
"""hashlib-compatible wrapper for Blake3 using official blake3 library.

Expand Down Expand Up @@ -577,6 +613,11 @@ class FuncReg(metaclass=_FuncRegMeta):
(Func.sha2_512_224, "sha512_224", getattr(hashlib, "sha512_224", None)),
(Func.sha2_512_256, "sha512_256", getattr(hashlib, "sha512_256", None)),
# Blake3 registered via register_variable_size in reset()
# Legacy Keccak (distinct from NIST SHA3) — requires pycryptodome
(Func.keccak_224, "keccak-224", _create_keccak_hash(224)),
(Func.keccak_256, "keccak-256", _create_keccak_hash(256)),
(Func.keccak_384, "keccak-384", _create_keccak_hash(384)),
(Func.keccak_512, "keccak-512", _create_keccak_hash(512)),
# Murmur3 variants (using official mmh3 library)
(Func.murmur3_128, "murmur3-128", Murmur3_128Hash),
(Func.murmur3_32, "murmur3-32", Murmur3_32Hash),
Expand Down
1 change: 1 addition & 0 deletions newsfragments/48.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Register legacy Keccak-224/256/384/512 hash implementations (optional ``pycryptodome`` dependency), distinct from NIST SHA-3.
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,16 @@ Homepage = "https://github.com/multiformats/multihash"
Download = "https://github.com/multiformats/multihash/tarball/3.0.0"

[project.optional-dependencies]
keccak = [
"pycryptodome>=3.0,<4.0",
]
dev = [
"Sphinx>=5.0.0",
"build>=0.9.0",
"bump-my-version>=1.2.0",
"mypy",
"pre-commit",
"pycryptodome>=3.0,<4.0",
"pytest",
"pytest-cov",
"pytest-runner",
Expand Down
20 changes: 20 additions & 0 deletions tests/test_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,26 @@ def test_blake3_variable_lengths(self):
with pytest.raises(HashComputationError):
digest(data, Func.blake3, length=129)

def test_keccak_available(self):
"""Legacy Keccak hashes register when pycryptodome is installed."""
pytest.importorskip("Crypto.Hash.keccak")
from Crypto.Hash import keccak

mh = digest(b"hello", Func.keccak_256)
assert mh.code == Func.keccak_256
assert len(mh.digest) == 32
expected = keccak.new(digest_bits=256)
expected.update(b"hello")
assert mh.digest == expected.digest()

for func, bits in (
(Func.keccak_224, 224),
(Func.keccak_384, 384),
(Func.keccak_512, 512),
):
mh_v = digest(b"hello", func)
assert len(mh_v.digest) == bits // 8

def test_murmur3_128_available(self):
"""Test that MurmurHash3-128 is available and works."""
assert hasattr(Func, "murmur3_128")
Expand Down
43 changes: 42 additions & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading