Skip to content

zkevm: add remaining precompiles #1581

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
May 21, 2025
Merged
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
190 changes: 176 additions & 14 deletions tests/zkevm/test_worst_compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import math
import random
from typing import cast

import pytest

Expand All @@ -19,17 +20,21 @@
Bytecode,
Environment,
Transaction,
While,
)
from ethereum_test_tools.code.generators import While
from ethereum_test_tools.vm.opcode import Opcodes as Op
from ethereum_test_vm import Opcode
from ethereum_test_vm.opcode import Opcode
from tests.cancun.eip4844_blobs.spec import Spec as BlobsSpec
from tests.istanbul.eip152_blake2.common import Blake2bInput
from tests.istanbul.eip152_blake2.spec import Spec as Blake2bSpec
from tests.prague.eip2537_bls_12_381_precompiles import spec as bls12381_spec
from tests.prague.eip2537_bls_12_381_precompiles.spec import BytesConcatenation

REFERENCE_SPEC_GIT_PATH = "TODO"
REFERENCE_SPEC_VERSION = "TODO"

MAX_CODE_SIZE = 24 * 1024
KECCAK_RATE = 136
ECRECOVER_GAS_COST = 3_000


@pytest.mark.valid_from("Cancun")
Expand Down Expand Up @@ -116,6 +121,7 @@ def test_worst_keccak(
pytest.param(0x04, 15, 3, 1, id="IDENTITY"),
],
)
@pytest.mark.slow()
def test_worst_precompile_only_data_input(
blockchain_test: BlockchainTestFiller,
pre: Alloc,
Expand Down Expand Up @@ -237,23 +243,179 @@ def test_worst_modexp(


@pytest.mark.valid_from("Cancun")
def test_worst_ecrecover(
@pytest.mark.parametrize(
"precompile_address,parameters",
[
pytest.param(
0x01,
[
# The inputs below are a valid signature, thus ECRECOVER call won't
# be short-circuited by validations and do actual work.
"38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E",
"000000000000000000000000000000000000000000000000000000000000001B",
"38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E",
"789D1DD423D25F0772D2748D60F7E4B81BB14D086EBA8E8E8EFB6DCFF8A4AE02",
],
id="ecrecover",
),
pytest.param(
0x06,
[
"18B18ACFB4C2C30276DB5411368E7185B311DD124691610C5D3B74034E093DC9",
"063C909C4720840CB5134CB9F59FA749755796819658D32EFC0D288198F37266",
"07C2B7F58A84BD6145F00C9C2BC0BB1A187F20FF2C92963A88019E7C6A014EED",
"06614E20C147E940F2D70DA3F74C9A17DF361706A4485C742BD6788478FA17D7",
],
id="bn128_add",
),
pytest.param(
0x07,
[
"1A87B0584CE92F4593D161480614F2989035225609F08058CCFA3D0F940FEBE3",
"1A2F3C951F6DADCC7EE9007DFF81504B0FCD6D7CF59996EFDC33D92BF7F9F8F6",
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
],
id="bn128_mul",
),
pytest.param(
0x08,
[
# TODO: the following are only two inputs, but this can be extended
# to more inputs to amortize costs as much as possible. Additionally,
# there might be worse pairings that can be used.
#
# First pairing
"1C76476F4DEF4BB94541D57EBBA1193381FFA7AA76ADA664DD31C16024C43F59",
"3034DD2920F673E204FEE2811C678745FC819B55D3E9D294E45C9B03A76AEF41",
"209DD15EBFF5D46C4BD888E51A93CF99A7329636C63514396B4A452003A35BF7",
"04BF11CA01483BFA8B34B43561848D28905960114C8AC04049AF4B6315A41678",
"2BB8324AF6CFC93537A2AD1A445CFD0CA2A71ACD7AC41FADBF933C2A51BE344D",
"120A2A4CF30C1BF9845F20C6FE39E07EA2CCE61F0C9BB048165FE5E4DE877550",
# Second pairing
"111E129F1CF1097710D41C4AC70FCDFA5BA2023C6FF1CBEAC322DE49D1B6DF7C",
"103188585E2364128FE25C70558F1560F4F9350BAF3959E603CC91486E110936",
"198E9393920D483A7260BFB731FB5D25F1AA493335A9E71297E485B7AEF312C2",
"1800DEEF121F1E76426A00665E5C4479674322D4F75EDADD46DEBD5CD992F6ED",
"090689D0585FF075EC9E99AD690C3395BC4B313370B38EF355ACDADCD122975B",
"12C85EA5DB8C6DEB4AAB71808DCB408FE3D1E7690C43D37B4CE6CC0166FA7DAA",
],
id="bn128_pairing",
),
pytest.param(
Blake2bSpec.BLAKE2_PRECOMPILE_ADDRESS,
[
Blake2bInput(rounds=0xFFFF, f=True).create_blake2b_tx_data(),
],
id="blake2f",
),
pytest.param(
BlobsSpec.POINT_EVALUATION_PRECOMPILE_ADDRESS,
[
"01E798154708FE7789429634053CBF9F99B619F9F084048927333FCE637F549B",
"564C0A11A0F704F4FC3E8ACFE0F8245F0AD1347B378FBF96E206DA11A5D36306",
"24D25032E67A7E6A4910DF5834B8FE70E6BCFEEAC0352434196BDF4B2485D5A1",
"8F59A8D2A1A625A17F3FEA0FE5EB8C896DB3764F3185481BC22F91B4AAFFCCA25F26936857BC3A7C2539EA8EC3A952B7",
"873033E038326E87ED3E1276FD140253FA08E9FC25FB2D9A98527FC22A2C9612FBEAFDAD446CBC7BCDBDCD780AF2C16A",
],
id="point_evaluation",
),
pytest.param(
bls12381_spec.Spec.G1ADD,
[
bls12381_spec.Spec.G1,
bls12381_spec.Spec.P1,
],
id="bls12_g1add",
),
pytest.param(
bls12381_spec.Spec.G1MSM,
[
(bls12381_spec.Spec.P1 + bls12381_spec.Scalar(bls12381_spec.Spec.Q))
* (len(bls12381_spec.Spec.G1MSM_DISCOUNT_TABLE) - 1),
],
id="bls12_g1msm",
),
pytest.param(
bls12381_spec.Spec.G2ADD,
[
bls12381_spec.Spec.G2,
bls12381_spec.Spec.P2,
],
id="bls12_g2add",
),
pytest.param(
bls12381_spec.Spec.G2MSM,
[
# TODO: the //2 is required due to a limitation of the max contract size limit.
# In a further iteration we can insert the inputs as calldata or storage and avoid
# having to do PUSHes which has this limtiation. This also applies to G1MSM.
(bls12381_spec.Spec.P2 + bls12381_spec.Scalar(bls12381_spec.Spec.Q))
* (len(bls12381_spec.Spec.G2MSM_DISCOUNT_TABLE) // 2),
],
id="bls12_g2msm",
),
pytest.param(
bls12381_spec.Spec.PAIRING,
[
bls12381_spec.Spec.G1,
bls12381_spec.Spec.G2,
],
id="bls12_pairing_check",
),
pytest.param(
bls12381_spec.Spec.MAP_FP_TO_G1,
[
bls12381_spec.FP(bls12381_spec.Spec.P - 1),
],
id="bls12_fp_to_g1",
),
pytest.param(
bls12381_spec.Spec.MAP_FP2_TO_G2,
[
bls12381_spec.FP2((bls12381_spec.Spec.P - 1, bls12381_spec.Spec.P - 1)),
],
id="bls12_fp_to_g2",
),
],
)
@pytest.mark.slow()
def test_worst_precompile_fixed_cost(
blockchain_test: BlockchainTestFiller,
pre: Alloc,
fork: Fork,
precompile_address: Address,
parameters: list[str] | list[BytesConcatenation] | list[bytes],
):
"""Test running a block with as many ECRECOVER calls as possible."""
"""Test running a block filled with a precompile with fixed cost."""
env = Environment()

# Calldata
calldata = (
Op.MSTORE(0 * 32, 0x38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E)
+ Op.MSTORE(1 * 32, 27)
+ Op.MSTORE(2 * 32, 0x38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E)
+ Op.MSTORE(3 * 32, 0x789D1DD423D25F0772D2748D60F7E4B81BB14D086EBA8E8E8EFB6DCFF8A4AE02)
)
concatenated_bytes: bytes
if all(isinstance(p, str) for p in parameters):
parameters_str = cast(list[str], parameters)
concatenated_hex_string = "".join(parameters_str)
concatenated_bytes = bytes.fromhex(concatenated_hex_string)
elif all(isinstance(p, (bytes, BytesConcatenation)) for p in parameters):
parameters_bytes_list = [
bytes(p) for p in cast(list[BytesConcatenation | bytes], parameters)
]
concatenated_bytes = b"".join(parameters_bytes_list)
else:
raise TypeError(
"parameters must be a list of strings (hex) "
"or a list of byte-like objects (bytes or BytesConcatenation)."
)

attack_block = Op.POP(Op.STATICCALL(ECRECOVER_GAS_COST, 0x1, 0, 32 * 4, 0, 0))
padding_length = (32 - (len(concatenated_bytes) % 32)) % 32
input_bytes = concatenated_bytes + b"\x00" * padding_length

calldata = Bytecode()
for i in range(0, len(input_bytes), 32):
chunk = input_bytes[i : i + 32]
value_to_store = int.from_bytes(chunk, "big")
calldata += Op.MSTORE(i, value_to_store)

attack_block = Op.POP(
Op.STATICCALL(Op.GAS, precompile_address, 0, len(concatenated_bytes), 0, 0)
)
code = code_loop_precompile_call(calldata, attack_block)
code_address = pre.deploy_contract(code=bytes(code))

Expand Down
Loading