Skip to content

Commit

Permalink
handle gracefully when x is not on curve
Browse files Browse the repository at this point in the history
  • Loading branch information
enitrat committed Nov 4, 2024
1 parent b1a3afd commit fd41567
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 6 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ cairo/tests/ef_tests/test_data

# Ignore proptest regression files
**/proptest-regressions/*

# Ide settings
.vscode/
1 change: 0 additions & 1 deletion .python-version

This file was deleted.

6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
9 changes: 8 additions & 1 deletion cairo/src/precompiles/ec_recover.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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))
);
Expand Down
50 changes: 49 additions & 1 deletion cairo/src/utils/signature.cairo
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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);
}
}
30 changes: 30 additions & 0 deletions cairo/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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')}")
2 changes: 0 additions & 2 deletions uv.lock

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

0 comments on commit fd41567

Please sign in to comment.