From c177e26c72b7142044de8731fb30e18fde4f6cb3 Mon Sep 17 00:00:00 2001 From: Mathieu <60658558+enitrat@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:21:05 +0200 Subject: [PATCH] feat: use recover_eth_address Cairo1 Helper (#1114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Time spent on this PR: 0.2d ## Pull request type Please check the type of change your PR introduces: - [ ] Bugfix - [ ] Feature - [ ] Code style update (formatting, renaming) - [ ] Refactoring (no functional changes, no api changes) - [ ] Build related changes - [ ] Documentation content changes - [ ] Other (please describe): ## What is the current behavior? Resolves #1111 Closes #1095 ## What is the new behavior? - - - - - - This change is [Reviewable](https://reviewable.io/reviews/kkrt-labs/kakarot/1114) --- src/kakarot/accounts/library.cairo | 7 ++-- src/kakarot/interfaces/interfaces.cairo | 4 +-- src/kakarot/precompiles/ec_recover.cairo | 23 +++++-------- src/utils/signature.cairo | 35 ++++++++++++++++++++ tests/utils/syscall_handler.py | 42 ++++++++++++++---------- 5 files changed, 75 insertions(+), 36 deletions(-) create mode 100644 src/utils/signature.cairo diff --git a/src/kakarot/accounts/library.cairo b/src/kakarot/accounts/library.cairo index e0c5fde48..b087029d3 100644 --- a/src/kakarot/accounts/library.cairo +++ b/src/kakarot/accounts/library.cairo @@ -32,6 +32,7 @@ from kakarot.constants import Constants from utils.eth_transaction import EthTransaction from utils.uint256 import uint256_add from utils.bytes import bytes_to_bytes8_little_endian +from utils.signature import Signature // @dev: should always be zero for EOAs @storage_var @@ -657,13 +658,13 @@ namespace Internals { last_input_num_bytes=last_word_num_bytes, ); - ICairo1Helpers.library_call_verify_eth_signature( - class_hash=helpers_class, + Signature.verify_eth_signature_uint256( msg_hash=msg_hash, r=r, s=s, - y_parity=y_parity, + v=y_parity, eth_address=address, + helpers_class=helpers_class, ); return (); } diff --git a/src/kakarot/interfaces/interfaces.cairo b/src/kakarot/interfaces/interfaces.cairo index b51030e7c..824fd090d 100644 --- a/src/kakarot/interfaces/interfaces.cairo +++ b/src/kakarot/interfaces/interfaces.cairo @@ -173,8 +173,8 @@ namespace ICairo1Helpers { ) -> (hash: Uint256) { } - func verify_eth_signature( - msg_hash: Uint256, r: Uint256, s: Uint256, y_parity: felt, eth_address: felt + func recover_eth_address(msg_hash: Uint256, r: Uint256, s: Uint256, y_parity: felt) -> ( + success: felt, address: felt ) { } } diff --git a/src/kakarot/precompiles/ec_recover.cairo b/src/kakarot/precompiles/ec_recover.cairo index 7c796bedc..6984d6a2e 100644 --- a/src/kakarot/precompiles/ec_recover.cairo +++ b/src/kakarot/precompiles/ec_recover.cairo @@ -58,28 +58,23 @@ namespace PrecompileEcRecover { return (0, output, GAS_COST_EC_RECOVER, 0); } - // v - 27, see recover_public_key comment - let hash = Helpers.bytes32_to_bigint(input_padded); - let r = Helpers.bytes32_to_bigint(input_padded + 32 * 2); - let s = Helpers.bytes32_to_bigint(input_padded + 32 * 3); + let msg_hash = Helpers.bytes_to_uint256(32, input_padded); + let r = Helpers.bytes_to_uint256(32, input_padded + 32 * 2); + let s = Helpers.bytes_to_uint256(32, input_padded + 32 * 3); - let (public_key_point) = recover_public_key(hash, r, s, v - 27); - let (is_public_key_invalid) = EcRecoverHelpers.ec_point_equal( - public_key_point, EcPoint(BigInt3(0, 0, 0), BigInt3(0, 0, 0)) + // v - 27, see recover_public_key comment + let (helpers_class) = Kakarot_cairo1_helpers_class_hash.read(); + let (success, recovered_address) = ICairo1Helpers.library_call_recover_eth_address( + class_hash=helpers_class, msg_hash=msg_hash, r=r, s=s, y_parity=v - 27 ); - if (is_public_key_invalid != FALSE) { + if (success == 0) { let (output) = alloc(); return (0, output, GAS_COST_EC_RECOVER, 0); } - let (helpers_class) = Kakarot_cairo1_helpers_class_hash.read(); - let (public_address) = EcRecoverHelpers.public_key_point_to_eth_address( - public_key_point, helpers_class - ); - let (output) = alloc(); - Helpers.split_word(public_address, 32, output); + Helpers.split_word(recovered_address, 32, output); return (32, output, GAS_COST_EC_RECOVER, 0); } diff --git a/src/utils/signature.cairo b/src/utils/signature.cairo new file mode 100644 index 000000000..44992fdae --- /dev/null +++ b/src/utils/signature.cairo @@ -0,0 +1,35 @@ +%lang starknet + +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.uint256 import Uint256 +from kakarot.interfaces.interfaces import ICairo1Helpers +from starkware.cairo.common.cairo_secp.bigint import uint256_to_bigint + +namespace Signature { + // A version of verify_eth_signature, with that msg_hash, r and s as Uint256 and + // using the Cairo1 helpers class. + func verify_eth_signature_uint256{ + syscall_ptr: felt*, range_check_ptr, bitwise_ptr: BitwiseBuiltin* + }(msg_hash: Uint256, r: Uint256, s: Uint256, v: felt, eth_address: felt, helpers_class: felt) { + alloc_locals; + let (msg_hash_bigint: BigInt3) = uint256_to_bigint(msg_hash); + let (r_bigint: BigInt3) = uint256_to_bigint(r); + let (s_bigint: BigInt3) = uint256_to_bigint(s); + + with_attr error_message("Signature out of range.") { + validate_signature_entry(r_bigint); + validate_signature_entry(s_bigint); + } + + with_attr error_message("Invalid signature.") { + let (success, recovered_address) = ICairo1Helpers.library_call_recover_eth_address( + class_hash=helpers_class, msg_hash=msg_hash, r=r, s=s, y_parity=v + ); + assert success = 1; + assert eth_address = recovered_address; + } + return (); + } +} diff --git a/tests/utils/syscall_handler.py b/tests/utils/syscall_handler.py index 97802fb07..400365aea 100644 --- a/tests/utils/syscall_handler.py +++ b/tests/utils/syscall_handler.py @@ -7,7 +7,7 @@ from eth_utils import keccak from ethereum.base_types import U256 -from ethereum.crypto.elliptic_curve import secp256k1_recover +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import keccak256 from starkware.starknet.public.abi import ( get_selector_from_name, @@ -18,21 +18,6 @@ from tests.utils.uint256 import int_to_uint256, uint256_to_int -def cairo_verify_eth_signature(class_hash, calldata): - """ - Convert the input calldata to Cairo's `verify_eth_signature` into a signature, a message hash and an address. - """ - msg_hash = b"".join([num.to_bytes(16, "big") for num in reversed(calldata[0:2])]) - r = U256(uint256_to_int(calldata[2], calldata[3])) - s = U256(uint256_to_int(calldata[4], calldata[5])) - y_parity = U256(calldata[6]) - address = calldata[7] - public_key = secp256k1_recover(r, s, y_parity, msg_hash) - recovered_address = int.from_bytes(keccak256(public_key)[12:32], "big") - assert address == recovered_address - return [] - - def cairo_keccak(class_hash, calldata): return int_to_uint256( int.from_bytes( @@ -47,6 +32,29 @@ def cairo_keccak(class_hash, calldata): ) +def cairo_recover_eth_address(class_hash, calldata): + """ + Convert the input calldata to Cairo's `recover_eth_address` into a signature and a message hash. + """ + msg_hash = b"".join([num.to_bytes(16, "big") for num in reversed(calldata[0:2])]) + r = U256(uint256_to_int(calldata[2], calldata[3])) + s = U256(uint256_to_int(calldata[4], calldata[5])) + y_parity = U256(calldata[6]) + + if 0 >= r or r >= SECP256K1N: + return [0, 0] + if 0 >= s or s >= SECP256K1N: + return [0, 0] + try: + public_key = secp256k1_recover(r, s, y_parity, msg_hash) + except Exception: + return [0, 0] # return [is_some: 0, address: 0] + return [ + 1, + int.from_bytes(keccak256(public_key)[12:32], "big"), + ] # return [is_some: 1, address: int] + + def parse_state(state): """ Parse a serialized state as a dict of string, mainly converting hex strings to @@ -136,7 +144,7 @@ class SyscallHandler: # We need to reconstruct the raw bytes from the Cairo-style keccak calldata. patches = { get_selector_from_name("keccak"): cairo_keccak, - get_selector_from_name("verify_eth_signature"): cairo_verify_eth_signature, + get_selector_from_name("recover_eth_address"): cairo_recover_eth_address, } def get_contract_address(self, segments, syscall_ptr):