Skip to content

Commit

Permalink
feat: use recover_eth_address Cairo1 Helper (#1114)
Browse files Browse the repository at this point in the history
<!--- Please provide a general summary of your changes in the title
above -->

<!-- Give an estimate of the time you spent on this PR in terms of work
days.
Did you spend 0.5 days on this PR or rather 2 days?  -->

Time spent on this PR: 0.2d

## Pull request type

<!-- Please try to limit your pull request to one type,
submit multiple pull requests if needed. -->

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?

<!-- Please describe the current behavior that you are modifying,
or link to a relevant issue. -->

Resolves #1111 Closes #1095

## What is the new behavior?

<!-- Please describe the behavior or changes that are being added by
this PR. -->

-
-
-

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg"
height="34" align="absmiddle"
alt="Reviewable"/>](https://reviewable.io/reviews/kkrt-labs/kakarot/1114)
<!-- Reviewable:end -->
  • Loading branch information
enitrat authored Apr 22, 2024
1 parent fd7ddb2 commit c177e26
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 36 deletions.
7 changes: 4 additions & 3 deletions src/kakarot/accounts/library.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ();
}
Expand Down
4 changes: 2 additions & 2 deletions src/kakarot/interfaces/interfaces.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
}
}
23 changes: 9 additions & 14 deletions src/kakarot/precompiles/ec_recover.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
35 changes: 35 additions & 0 deletions src/utils/signature.cairo
Original file line number Diff line number Diff line change
@@ -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 ();
}
}
42 changes: 25 additions & 17 deletions tests/utils/syscall_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit c177e26

Please sign in to comment.