|
10 | 10 | from typing import cast
|
11 | 11 |
|
12 | 12 | import pytest
|
| 13 | +from py_ecc.bn128 import G1, G2, multiply |
13 | 14 |
|
| 15 | +from ethereum_test_base_types.base_types import Bytes |
14 | 16 | from ethereum_test_forks import Fork
|
15 | 17 | from ethereum_test_tools import (
|
16 | 18 | Address,
|
@@ -292,30 +294,6 @@ def test_worst_modexp(
|
292 | 294 | ],
|
293 | 295 | id="bn128_mul",
|
294 | 296 | ),
|
295 |
| - pytest.param( |
296 |
| - 0x08, |
297 |
| - [ |
298 |
| - # TODO: the following are only two inputs, but this can be extended |
299 |
| - # to more inputs to amortize costs as much as possible. Additionally, |
300 |
| - # there might be worse pairings that can be used. |
301 |
| - # |
302 |
| - # First pairing |
303 |
| - "1C76476F4DEF4BB94541D57EBBA1193381FFA7AA76ADA664DD31C16024C43F59", |
304 |
| - "3034DD2920F673E204FEE2811C678745FC819B55D3E9D294E45C9B03A76AEF41", |
305 |
| - "209DD15EBFF5D46C4BD888E51A93CF99A7329636C63514396B4A452003A35BF7", |
306 |
| - "04BF11CA01483BFA8B34B43561848D28905960114C8AC04049AF4B6315A41678", |
307 |
| - "2BB8324AF6CFC93537A2AD1A445CFD0CA2A71ACD7AC41FADBF933C2A51BE344D", |
308 |
| - "120A2A4CF30C1BF9845F20C6FE39E07EA2CCE61F0C9BB048165FE5E4DE877550", |
309 |
| - # Second pairing |
310 |
| - "111E129F1CF1097710D41C4AC70FCDFA5BA2023C6FF1CBEAC322DE49D1B6DF7C", |
311 |
| - "103188585E2364128FE25C70558F1560F4F9350BAF3959E603CC91486E110936", |
312 |
| - "198E9393920D483A7260BFB731FB5D25F1AA493335A9E71297E485B7AEF312C2", |
313 |
| - "1800DEEF121F1E76426A00665E5C4479674322D4F75EDADD46DEBD5CD992F6ED", |
314 |
| - "090689D0585FF075EC9E99AD690C3395BC4B313370B38EF355ACDADCD122975B", |
315 |
| - "12C85EA5DB8C6DEB4AAB71808DCB408FE3D1E7690C43D37B4CE6CC0166FA7DAA", |
316 |
| - ], |
317 |
| - id="bn128_pairing", |
318 |
| - ), |
319 | 297 | pytest.param(
|
320 | 298 | Blake2bSpec.BLAKE2_PRECOMPILE_ADDRESS,
|
321 | 299 | [
|
@@ -938,3 +916,101 @@ def test_worst_mod(
|
938 | 916 | post={},
|
939 | 917 | blocks=[Block(txs=[tx])],
|
940 | 918 | )
|
| 919 | + |
| 920 | + |
| 921 | +@pytest.mark.valid_from("Cancun") |
| 922 | +def test_worst_bn128_pairings( |
| 923 | + blockchain_test: BlockchainTestFiller, |
| 924 | + pre: Alloc, |
| 925 | + fork: Fork, |
| 926 | +): |
| 927 | + """Test running a block with as many BN128 pairings as possible.""" |
| 928 | + env = Environment() |
| 929 | + |
| 930 | + base_cost = 45_000 |
| 931 | + pairing_cost = 34_000 |
| 932 | + size_per_pairing = 192 |
| 933 | + |
| 934 | + gsc = fork.gas_costs() |
| 935 | + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() |
| 936 | + mem_exp_gas_calculator = fork.memory_expansion_gas_calculator() |
| 937 | + |
| 938 | + # This is a theoretical maximum number of pairings that can be done in a block. |
| 939 | + # It is only used for an upper bound for calculating the optimal number of pairings below. |
| 940 | + maximum_number_of_pairings = (env.gas_limit - base_cost) // pairing_cost |
| 941 | + print(f"max_pairings: {maximum_number_of_pairings}") |
| 942 | + |
| 943 | + # Discover the optimal number of pairings balancing two dimensions: |
| 944 | + # 1. Amortize the precompile base cost as much as possible. |
| 945 | + # 2. The cost of the memory expansion. |
| 946 | + max_pairings = 0 |
| 947 | + optimal_per_call_num_pairings = 0 |
| 948 | + for i in range(1, maximum_number_of_pairings + 1): |
| 949 | + # We'll pass all pairing arguments via calldata. |
| 950 | + available_gas_after_intrinsic = env.gas_limit - intrinsic_gas_calculator( |
| 951 | + calldata=[0xFF] * size_per_pairing * i # 0xFF is to indicate non-zero bytes. |
| 952 | + ) |
| 953 | + available_gas_after_expansion = max( |
| 954 | + 0, |
| 955 | + available_gas_after_intrinsic - mem_exp_gas_calculator(new_bytes=i * size_per_pairing), |
| 956 | + ) |
| 957 | + |
| 958 | + # This is ignoring "glue" opcodes, but helps to have a rough idea of the right |
| 959 | + # cutting point. |
| 960 | + approx_gas_cost_per_call = gsc.G_WARM_ACCOUNT_ACCESS + base_cost + i * pairing_cost |
| 961 | + |
| 962 | + num_precompile_calls = available_gas_after_expansion // approx_gas_cost_per_call |
| 963 | + num_pairings_done = num_precompile_calls * i # Each precompile call does i pairings. |
| 964 | + |
| 965 | + if num_pairings_done > max_pairings: |
| 966 | + max_pairings = num_pairings_done |
| 967 | + optimal_per_call_num_pairings = i |
| 968 | + |
| 969 | + print(f"{max_pairings=}, {optimal_per_call_num_pairings=}") |
| 970 | + |
| 971 | + calldata = Op.CALLDATACOPY(size=Op.CALLDATASIZE) |
| 972 | + attack_block = Op.POP(Op.STATICCALL(Op.GAS, 0x08, 0, Op.CALLDATASIZE, 0, 0)) |
| 973 | + code = code_loop_precompile_call(calldata, attack_block) |
| 974 | + |
| 975 | + code_address = pre.deploy_contract(code=code) |
| 976 | + |
| 977 | + tx = Transaction( |
| 978 | + to=code_address, |
| 979 | + gas_limit=env.gas_limit, |
| 980 | + data=_generate_bn128_pairs(optimal_per_call_num_pairings, 42), |
| 981 | + sender=pre.fund_eoa(), |
| 982 | + ) |
| 983 | + |
| 984 | + blockchain_test( |
| 985 | + env=env, |
| 986 | + pre=pre, |
| 987 | + post={}, |
| 988 | + blocks=[Block(txs=[tx])], |
| 989 | + ) |
| 990 | + |
| 991 | + |
| 992 | +def _generate_bn128_pairs(n: int, seed: int = 0): |
| 993 | + random.seed(seed) |
| 994 | + calldata = Bytes() |
| 995 | + |
| 996 | + for _ in range(n): |
| 997 | + priv_key_g1 = random.randint(1, 2**32 - 1) |
| 998 | + priv_key_g2 = random.randint(1, 2**32 - 1) |
| 999 | + |
| 1000 | + point_x_affine = multiply(G1, priv_key_g1) |
| 1001 | + point_y_affine = multiply(G2, priv_key_g2) |
| 1002 | + |
| 1003 | + g1_x_bytes = point_x_affine[0].n.to_bytes(32, "big") |
| 1004 | + g1_y_bytes = point_x_affine[1].n.to_bytes(32, "big") |
| 1005 | + g1_serialized = g1_x_bytes + g1_y_bytes |
| 1006 | + |
| 1007 | + g2_x_c1_bytes = point_y_affine[0].coeffs[1].n.to_bytes(32, "big") |
| 1008 | + g2_x_c0_bytes = point_y_affine[0].coeffs[0].n.to_bytes(32, "big") |
| 1009 | + g2_y_c1_bytes = point_y_affine[1].coeffs[1].n.to_bytes(32, "big") |
| 1010 | + g2_y_c0_bytes = point_y_affine[1].coeffs[0].n.to_bytes(32, "big") |
| 1011 | + g2_serialized = g2_x_c1_bytes + g2_x_c0_bytes + g2_y_c1_bytes + g2_y_c0_bytes |
| 1012 | + |
| 1013 | + pair_calldata = g1_serialized + g2_serialized |
| 1014 | + calldata += pair_calldata |
| 1015 | + |
| 1016 | + return calldata |
0 commit comments