From fd415674849a8e611fbaeb99902b7b0a6b30eaad Mon Sep 17 00:00:00 2001 From: enitrat Date: Mon, 4 Nov 2024 16:26:19 +0700 Subject: [PATCH] handle gracefully when x is not on curve --- .gitignore | 3 ++ .python-version | 1 - .vscode/settings.json | 6 +++- cairo/src/precompiles/ec_recover.cairo | 9 ++++- cairo/src/utils/signature.cairo | 50 +++++++++++++++++++++++++- cairo/tests/conftest.py | 30 ++++++++++++++++ uv.lock | 2 -- 7 files changed, 95 insertions(+), 6 deletions(-) delete mode 100644 .python-version diff --git a/.gitignore b/.gitignore index 14580aac7..af4bfed5a 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ cairo/tests/ef_tests/test_data # Ignore proptest regression files **/proptest-regressions/* + +# Ide settings +.vscode/ diff --git a/.python-version b/.python-version deleted file mode 100644 index 9919bf8c9..000000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.10.13 diff --git a/.vscode/settings.json b/.vscode/settings.json index 09ac58def..cbabc0314 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,5 +12,9 @@ "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "jupyter.notebookFileRoot": "${workspaceFolder}/cairo", - "githubPullRequests.remotes": ["kakarot", "upstream"] + "githubPullRequests.remotes": [ + "kakarot", + "upstream" + ], + "cairols.sourceDir": "cairo" } diff --git a/cairo/src/precompiles/ec_recover.cairo b/cairo/src/precompiles/ec_recover.cairo index 09ab0bbdc..4a5d5b402 100644 --- a/cairo/src/precompiles/ec_recover.cairo +++ b/cairo/src/precompiles/ec_recover.cairo @@ -14,6 +14,7 @@ from src.errors import Errors from src.utils.uint256 import uint256_eq from src.utils.utils import Helpers from src.utils.array import slice +from src.utils.signature import Signature from src.utils.maths import unsigned_div_rem // @title EcRecover Precompile related functions. @@ -78,7 +79,13 @@ namespace PrecompileEcRecover { let (r_bigint) = uint256_to_bigint(r); let (s_bigint) = uint256_to_bigint(s); - let (public_key_point) = recover_public_key(msg_hash_bigint, r_bigint, s_bigint, v - 27); + let (public_key_point, success) = Signature.try_recover_public_key( + msg_hash_bigint, r_bigint, s_bigint, v - 27 + ); + if (success == 0) { + let (output) = alloc(); + return (0, output, GAS_COST_EC_RECOVER, 0); + } let (is_public_key_invalid) = EcRecoverHelpers.ec_point_equal( public_key_point, EcPoint(BigInt3(0, 0, 0), BigInt3(0, 0, 0)) ); diff --git a/cairo/src/utils/signature.cairo b/cairo/src/utils/signature.cairo index a409e207a..4b9436883 100644 --- a/cairo/src/utils/signature.cairo +++ b/cairo/src/utils/signature.cairo @@ -1,7 +1,15 @@ from starkware.cairo.common.cairo_builtins import BitwiseBuiltin from starkware.cairo.common.cairo_secp.bigint3 import BigInt3, UnreducedBigInt3 -from starkware.cairo.common.cairo_secp.signature import validate_signature_entry +from starkware.cairo.common.cairo_secp.ec_point import EcPoint +from starkware.cairo.common.cairo_secp.signature import ( + validate_signature_entry, + try_get_point_from_x, + get_generator_point, + div_mod_n, +) +from starkware.cairo.common.cairo_secp.ec import ec_add, ec_mul, ec_negate from starkware.cairo.common.uint256 import Uint256 +from starkware.cairo.common.alloc import alloc from starkware.cairo.common.cairo_secp.bigint import uint256_to_bigint from src.interfaces.interfaces import ICairo1Helpers @@ -32,4 +40,44 @@ namespace Signature { } return (); } + + // Similar to `recover_public_key`, but handles the case where 'x' does not correspond to a point on the + // curve gracefully. + // Receives a signature and the signed message hash. + // Returns the public key associated with the signer, represented as a point on the curve, and `true` if valid. + // Returns the point (0, 0) and `false` otherwise. + // Note: + // Some places use the values 27 and 28 instead of 0 and 1 for v. + // In that case, a subtraction by 27 returns a v that can be used by this function. + // Prover assumptions: + // * r is the x coordinate of some nonzero point on the curve. + // * All the limbs of s and msg_hash are in the range (-2 ** 210.99, 2 ** 210.99). + // * All the limbs of r are in the range (-2 ** 124.99, 2 ** 124.99). + func try_recover_public_key{range_check_ptr}( + msg_hash: BigInt3, r: BigInt3, s: BigInt3, v: felt + ) -> (public_key_point: EcPoint, success: felt) { + alloc_locals; + let (local r_point: EcPoint*) = alloc(); + let (is_on_curve) = try_get_point_from_x(x=r, v=v, result=r_point); + if (is_on_curve == 0) { + return (public_key_point=EcPoint(x=BigInt3(0, 0, 0), y=BigInt3(0, 0, 0)), success=0); + } + let (generator_point: EcPoint) = get_generator_point(); + // The result is given by + // -(msg_hash / r) * gen + (s / r) * r_point + // where the division by r is modulo N. + + let (u1: BigInt3) = div_mod_n(msg_hash, r); + let (u2: BigInt3) = div_mod_n(s, r); + + let (point1) = ec_mul(generator_point, u1); + // We prefer negating the point over negating the scalar because negating mod SECP_P is + // computationally easier than mod N. + let (minus_point1) = ec_negate(point1); + + let (point2) = ec_mul([r_point], u2); + + let (public_key_point) = ec_add(minus_point1, point2); + return (public_key_point=public_key_point, success=1); + } } diff --git a/cairo/tests/conftest.py b/cairo/tests/conftest.py index d5919e964..eac1787e3 100644 --- a/cairo/tests/conftest.py +++ b/cairo/tests/conftest.py @@ -1,9 +1,11 @@ import logging +import os import shutil from pathlib import Path import pytest from dotenv import load_dotenv +from hypothesis import Phase, Verbosity, settings from tests.utils.coverage import report_runs from tests.utils.reporting import dump_coverage @@ -35,3 +37,31 @@ async def coverage(worker_id): output_dir.mkdir(exist_ok=True, parents=True) shutil.rmtree(output_dir, ignore_errors=True) dump_coverage(output_dir, files) + + +settings.register_profile( + "nightly", + deadline=None, + max_examples=1500, + phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target], +) +settings.register_profile( + "ci", + deadline=None, + max_examples=100, + phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target], +) +settings.register_profile( + "dev", + deadline=None, + max_examples=10, + phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target], +) +settings.register_profile( + "debug", + max_examples=10, + verbosity=Verbosity.verbose, + phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target], +) +settings.load_profile(os.getenv("HYPOTHESIS_PROFILE", "default")) +logger.info(f"Using Hypothesis profile: {os.getenv('HYPOTHESIS_PROFILE', 'default')}") diff --git a/uv.lock b/uv.lock index 9464e1130..43d24eef6 100644 --- a/uv.lock +++ b/uv.lock @@ -1988,8 +1988,6 @@ version = "6.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/18/c7/8c6872f7372eb6a6b2e4708b88419fb46b857f7a2e1892966b851cc79fc9/psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2", size = 508067 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/66/78c9c3020f573c58101dc43a44f6855d01bbbd747e24da2f0c4491200ea3/psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35", size = 249766 }, - { url = "https://files.pythonhosted.org/packages/e1/3f/2403aa9558bea4d3854b0e5e567bc3dd8e9fbc1fc4453c0aa9aafeb75467/psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1", size = 253024 }, { url = "https://files.pythonhosted.org/packages/0b/37/f8da2fbd29690b3557cca414c1949f92162981920699cd62095a984983bf/psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0", size = 250961 }, { url = "https://files.pythonhosted.org/packages/35/56/72f86175e81c656a01c4401cd3b1c923f891b31fbcebe98985894176d7c9/psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0", size = 287478 }, { url = "https://files.pythonhosted.org/packages/19/74/f59e7e0d392bc1070e9a70e2f9190d652487ac115bb16e2eff6b22ad1d24/psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd", size = 290455 },