diff --git a/eth_utils/crypto.py b/eth_utils/crypto.py index 52aa0e38..ffaec027 100644 --- a/eth_utils/crypto.py +++ b/eth_utils/crypto.py @@ -12,4 +12,13 @@ def keccak( hexstr: str | None = None, text: str | None = None, ) -> bytes: + if isinstance(primitive, bytes) and hexstr is None and text is None: + return bytes(keccak_256(primitive)) + elif ( + isinstance(primitive, (bytearray, memoryview)) + and hexstr is None + and text is None + ): + return bytes(keccak_256(bytes(primitive))) + return bytes(keccak_256(to_bytes(primitive, hexstr, text))) diff --git a/newsfragments/95.performance.rst b/newsfragments/95.performance.rst new file mode 100644 index 00000000..4988205d --- /dev/null +++ b/newsfragments/95.performance.rst @@ -0,0 +1,3 @@ +Reduced ``keccak()`` overhead for raw ``bytes``-like inputs by bypassing +the generic conversion path when no ``hexstr`` or ``text`` argument is +provided. diff --git a/tests/core/crypto-utils/test_crypto.py b/tests/core/crypto-utils/test_crypto.py new file mode 100644 index 00000000..4e820670 --- /dev/null +++ b/tests/core/crypto-utils/test_crypto.py @@ -0,0 +1,38 @@ +import pytest + +from eth_hash.auto import ( + keccak as raw_keccak, +) + +import eth_utils.crypto as crypto_utils + + +@pytest.mark.parametrize( + "kwargs,expected_payload", + ( + ({"primitive": b"\x12\x34"}, b"\x12\x34"), + ({"primitive": bytearray(b"\x12\x34")}, b"\x12\x34"), + ({"primitive": memoryview(b"\x12\x34")}, b"\x12\x34"), + ({"hexstr": "0x1234"}, b"\x12\x34"), + ({"text": "ethereum"}, b"ethereum"), + ), +) +def test_keccak_matches_backend_for_supported_inputs(kwargs, expected_payload): + assert crypto_utils.keccak(**kwargs) == bytes(raw_keccak(expected_payload)) + + +def test_keccak_bytes_input_skips_to_bytes_conversion(monkeypatch): + def fail_if_called(*args, **kwargs): + raise AssertionError("to_bytes should not be called for raw bytes input") + + monkeypatch.setattr(crypto_utils, "to_bytes", fail_if_called) + + payload = b"\xde\xad\xbe\xef" + assert crypto_utils.keccak(payload) == bytes(raw_keccak(payload)) + + +def test_keccak_still_validates_multiple_input_sources(): + with pytest.raises( + TypeError, match="Exactly one of the passed values can be specified" + ): + crypto_utils.keccak(b"\x12\x34", hexstr="0x1234")