Skip to content
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

feat: enable ec_add / ec_mul #1398

Merged
merged 15 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ endif

.PHONY: build test coverage clean

# 154615699 corresponds to release v0.1.7 of Kakarot SSJ.
KKRT_SSJ_RELEASE_ID = 154615699
# 173874367 corresponds to release v0.1.13 of Kakarot SSJ.
KKRT_SSJ_RELEASE_ID = 176384150
enitrat marked this conversation as resolved.
Show resolved Hide resolved
# Kakarot SSJ artifacts for precompiles.
KKRT_SSJ_BUILD_ARTIFACT_URL = $(shell curl -L https://api.github.com/repos/kkrt-labs/kakarot-ssj/releases/${KKRT_SSJ_RELEASE_ID} | jq -r '.assets[0].browser_download_url')
KATANA_VERSION = v1.0.0-alpha.11
KATANA_VERSION = v1.0.0-alpha.12
ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))

BUILD_DIR = build
Expand All @@ -35,7 +35,7 @@ $(SSJ_ZIP):
fetch-ef-tests:
poetry run python ./kakarot_scripts/ef_tests/fetch.py

setup: fetch-ssj-artifacts
setup:
poetry install

test: deploy
Expand Down
11 changes: 7 additions & 4 deletions kakarot_scripts/utils/starknet.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ def get_deployments():

@cache
def get_artifact(contract_name):
# Cairo 0 artifacts
ClementWalter marked this conversation as resolved.
Show resolved Hide resolved
artifacts = list(BUILD_DIR.glob(f"*{contract_name}*.json"))
if artifacts:
return Artifact(sierra=None, casm=artifacts[0])

# Cairo 1 artifacts
artifacts = list(CAIRO_DIR.glob(f"**/*{contract_name}.*.json")) or list(
BUILD_DIR_SSJ.glob(f"**/*{contract_name}.*.json")
)
Expand All @@ -255,10 +261,7 @@ def get_artifact(contract_name):
)
return Artifact(sierra=sierra, casm=casm)

artifacts = list(BUILD_DIR.glob(f"**/*{contract_name}*.json"))
if not artifacts:
raise FileNotFoundError(f"No artifact found for {contract_name}")
return Artifact(sierra=None, casm=artifacts[0])
raise FileNotFoundError(f"No artifact found for {contract_name}")


@cache
Expand Down
2 changes: 1 addition & 1 deletion poetry.lock

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

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ packages = [{ include = "kakarot_scripts" }, { include = "tests" }]

[tool.poetry.dependencies]
python = ">=3.10,<3.11"
starknet-py = "^0.23.0"
cairo-lang = "0.13.1"
python-dotenv = "^0.21.0"
async-lru = "^2.0.4"
toml = "^0.10.2"
scikit-learn = "^1.5.1"
seaborn = "^0.13.2"
boto3 = "^1.35.12"
starknet-py = "0.23.0"

[tool.poetry.group.dev.dependencies]
pytest = "^8.1.1"
Expand Down
64 changes: 64 additions & 0 deletions solidity_contracts/src/EvmPrecompiles/EvmPrecompiles.sol
ClementWalter marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @title EVM Precompiles Integration
/// @notice Contract for integration testing of EVM precompiles.
/// @dev Implements functions for ECADD and ECMUL precompiles.
contract EvmPrecompiles {
/// @dev Address of the ECADD precompile
address private constant ECADD_PRECOMPILE = address(0x06);
/// @dev Address of the ECMUL precompile
address private constant ECMUL_PRECOMPILE = address(0x07);

/// @dev Gas cost for ECADD call is 150
uint256 private constant ECADD_GAS = 1000000;
/// @dev Gas cost for ECMUL call is 6000
uint256 private constant ECMUL_GAS = 1000000;
enitrat marked this conversation as resolved.
Show resolved Hide resolved

/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor() {}
enitrat marked this conversation as resolved.
Show resolved Hide resolved

/*//////////////////////////////////////////////////////////////
FUNCTIONS FOR PRECOMPILES
//////////////////////////////////////////////////////////////*/
/// @notice Performs elliptic curve addition
/// @param x1 X coordinate of the first point
/// @param y1 Y coordinate of the first point
/// @param x2 X coordinate of the second point
/// @param y2 Y coordinate of the second point
/// @return success True if the operation was successful, false otherwise
/// @return x X coordinate of the result point
/// @return y Y coordinate of the result point
function ecAdd(uint256 x1, uint256 y1, uint256 x2, uint256 y2)
public
enitrat marked this conversation as resolved.
Show resolved Hide resolved
view
returns (bool success, uint256 x, uint256 y)
{
bytes memory input = abi.encodePacked(x1, y1, x2, y2);
(bool success, bytes memory result) = ECADD_PRECOMPILE.staticcall{gas: ECADD_GAS}(input);
if (!success) {
ClementWalter marked this conversation as resolved.
Show resolved Hide resolved
return (false, 0, 0);
}
(x, y) = abi.decode(result, (uint256, uint256));
return (true, x, y);
}

/// @notice Performs elliptic curve scalar multiplication
/// @param x1 X coordinate of the point
/// @param y1 Y coordinate of the point
/// @param s Scalar for multiplication
/// @return success True if the operation was successful, false otherwise
/// @return x X coordinate of the result point
/// @return y Y coordinate of the result point
function ecMul(uint256 x1, uint256 y1, uint256 s) public view returns (bool success, uint256 x, uint256 y) {
enitrat marked this conversation as resolved.
Show resolved Hide resolved
bytes memory input = abi.encodePacked(x1, y1, s);
(bool success, bytes memory result) = ECMUL_PRECOMPILE.staticcall{gas: ECMUL_GAS}(input);
if (!success) {
return (false, 0, 0);
}
(x, y) = abi.decode(result, (uint256, uint256));
return (true, x, y);
}
}
4 changes: 2 additions & 2 deletions src/kakarot/precompiles/precompiles.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ namespace Precompiles {
ret;
call not_implemented_precompile; // 0x5
ret;
call not_implemented_precompile; // 0x6
call external_precompile; // 0x6
ret;
call not_implemented_precompile; // 0x7
call external_precompile; // 0x7
ret;
call not_implemented_precompile; // 0x8
ret;
Expand Down
134 changes: 134 additions & 0 deletions tests/end_to_end/EvmPrecompiles/test_evm_precompiles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import pytest
import pytest_asyncio
from ethereum.base_types import U256, Uint
from ethereum.cancun.vm.exceptions import OutOfGasError
from ethereum.crypto.alt_bn128 import ALT_BN128_PRIME, BNF, BNP
from hypothesis import given, settings
from hypothesis.strategies import integers

from kakarot_scripts.utils.kakarot import deploy


@pytest_asyncio.fixture(scope="package")
async def evm_precompiles(owner):
return await deploy(
"EvmPrecompiles",
"EvmPrecompiles",
caller_eoa=owner.starknet_contract,
enitrat marked this conversation as resolved.
Show resolved Hide resolved
)


def ref_alt_bn128_add(x0, y0, x1, y1):
enitrat marked this conversation as resolved.
Show resolved Hide resolved
x0_value = U256.from_signed(x0)
y0_value = U256.from_signed(y0)
x1_value = U256.from_signed(x1)
y1_value = U256.from_signed(y1)

for i in (x0_value, y0_value, x1_value, y1_value):
if i >= ALT_BN128_PRIME:
raise OutOfGasError

try:
p0 = BNP(BNF(x0_value), BNF(y0_value))
p1 = BNP(BNF(x1_value), BNF(y1_value))
except ValueError:
raise OutOfGasError from None
enitrat marked this conversation as resolved.
Show resolved Hide resolved

p = p0 + p1

x_bytes = p.x.to_be_bytes32()
y_bytes = p.y.to_be_bytes32()

x = Uint(int.from_bytes(x_bytes, "big"))
y = Uint(int.from_bytes(y_bytes, "big"))

return [x, y]


def ref_alt_bn128_mul(x0, y0, s):
x0_value = U256.from_signed(x0)
y0_value = U256.from_signed(y0)
U256.from_signed(s)

for i in (x0_value, y0_value):
if i >= ALT_BN128_PRIME:
raise OutOfGasError

try:
p0 = BNP(BNF(x0_value), BNF(y0_value))
except ValueError:
raise OutOfGasError from None

p = p0.mul_by(s)

x_bytes = p.x.to_be_bytes32()
y_bytes = p.y.to_be_bytes32()

x = Uint(int.from_bytes(x_bytes, "big"))
y = Uint(int.from_bytes(y_bytes, "big"))

return [x, y]


@pytest.mark.asyncio(scope="package")
@pytest.mark.EvmPrecompiles
# @pytest.mark.xfail(reason="Katana doesn't support new builtins")
enitrat marked this conversation as resolved.
Show resolved Hide resolved
class TestEvmPrecompiles:
class TestEcAdd:
@given(
x0=integers(min_value=0, max_value=ALT_BN128_PRIME - 1),
y0=integers(min_value=0, max_value=ALT_BN128_PRIME - 1),
x1=integers(min_value=0, max_value=ALT_BN128_PRIME - 1),
y1=integers(min_value=0, max_value=ALT_BN128_PRIME - 1),
)
@settings(max_examples=10)
async def test_should_return_ec_add_with_coordinates(
self, evm_precompiles, x0, y0, x1, y1
):
try:
expected = ref_alt_bn128_add(x0, y0, x1, y1)
result = await evm_precompiles.ecAdd(x0, y0, x1, y1)
assert result[0] is True
assert result[1:] == expected
except OutOfGasError:
result = await evm_precompiles.ecAdd(x0, y0, x1, y1)
assert result[0] is False

async def test_should_return_ec_add_point_at_infinity(self, evm_precompiles):
try:
expected = ref_alt_bn128_add(0, 0, 0, 0)
result = await evm_precompiles.ecAdd(0, 0, 0, 0)
assert result[0] is True
assert result[1:] == expected
except OutOfGasError:
result = await evm_precompiles.ecAdd(0, 0, 0, 0)
assert result[0] is False

class TestEcMul:
@given(
x0=integers(min_value=0, max_value=ALT_BN128_PRIME - 1),
y0=integers(min_value=0, max_value=ALT_BN128_PRIME - 1),
s=integers(min_value=0, max_value=2**256 - 1),
)
@settings(max_examples=10)
async def test_should_return_ec_mul_with_coordinates(
self, evm_precompiles, x0, y0, s
):
try:
expected = ref_alt_bn128_mul(x0, y0, s)
result = await evm_precompiles.ecMul(x0, y0, s)
assert result[0] is True
assert result[1:] == expected
except OutOfGasError:
result = await evm_precompiles.ecMul(x0, y0, s)
assert result[0] is False

async def test_should_return_ec_mul_point_at_infinity(self, evm_precompiles):
try:
expected = ref_alt_bn128_mul(0, 0, 0)
result = await evm_precompiles.ecMul(0, 0, 0)
assert result[0] is True
assert result[1:] == expected
except OutOfGasError:
result = await evm_precompiles.ecMul(0, 0, 0)
assert result[0] is False
69 changes: 0 additions & 69 deletions tests/src/kakarot/precompiles/test_ec_add.cairo

This file was deleted.

11 changes: 0 additions & 11 deletions tests/src/kakarot/precompiles/test_ec_add.py

This file was deleted.

8 changes: 0 additions & 8 deletions tests/src/kakarot/precompiles/test_ec_mul.py

This file was deleted.

Loading