From 5f27087923a16fa015cc51bd95658eadaa4ceff7 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Mon, 8 Sep 2025 14:08:53 +0200 Subject: [PATCH 1/2] EVM/Node: expose a function to retrieve the signer of an authorization --- .../bin_node/lib_dev/encodings/transaction.ml | 44 +++++++++++++++---- .../lib_dev/encodings/transaction.mli | 4 ++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/etherlink/bin_node/lib_dev/encodings/transaction.ml b/etherlink/bin_node/lib_dev/encodings/transaction.ml index c6415e5340c2..138d50861ff8 100644 --- a/etherlink/bin_node/lib_dev/encodings/transaction.ml +++ b/etherlink/bin_node/lib_dev/encodings/transaction.ml @@ -541,19 +541,21 @@ let legacy_recovery_id ~chain_id ~v = let chain_id = Z.to_int chain_id in v - ((chain_id * 2) + 35) -let recovery_id : transaction -> (bytes, string) result = - fun transaction -> +let recovery_id ri = + if ri == 0 || ri == 1 then ( + let buffer = Bytes.create 1 in + Bytes.set_uint8 buffer 0 ri ; + Ok buffer) + else Error "Invalid recovery id" + +let recovery_id_from_tx transaction = let ri = match transaction.transaction_type with | Legacy -> legacy_recovery_id ~chain_id:transaction.chain_id ~v:transaction.v | Eip2930 | Eip1559 | Eip7702 -> Z.to_int transaction.v in - if ri == 0 || ri == 1 then ( - let buffer = Bytes.create 1 in - Bytes.set_uint8 buffer 0 ri ; - Ok buffer) - else Error "Invalid recovery id" + recovery_id ri let secp256k1n_2 = Z.of_string @@ -581,7 +583,7 @@ let uncompressed_signature ~r ~s recovery_id = let caller ({r; s; _} as transaction) = let open Result_syntax in let message = message transaction in - let* recovery_id = recovery_id transaction in + let* recovery_id = recovery_id_from_tx transaction in let* sig_ = uncompressed_signature ~r ~s recovery_id in let+ caller = Tezos_crypto.Signature.Secp256k1.recover sig_ message in Address (Hex (Hex.of_bytes caller |> Hex.show)) @@ -628,3 +630,29 @@ let to_transaction_object : r = Qty r; s = Qty s; } + +let auth_message {chain_id; address; nonce; _} = + let open Rlp in + let chain_id = encode_z chain_id in + let nonce = encode_z nonce in + let rlp = List [Value chain_id; Value address; Value nonce] in + encode rlp + +let authorization_hash authorization = + (* Magic number used to calculate an EIP7702 authority. *) + let authority_magic_byte = 5 in + let buffer = Buffer.create 128 in + Buffer.add_char buffer (Char.chr authority_magic_byte) ; + let rlp_authorization = auth_message authorization in + Buffer.add_bytes buffer rlp_authorization ; + let raw_bytes = Buffer.to_bytes buffer in + Tezos_crypto.Signature.Secp256k1.keccak256_digest raw_bytes + +let auth_signer ({r; s; y_parity; _} as authorization : authorization_list_item) + = + let open Result_syntax in + let hash = authorization_hash authorization in + let* recovery_id = recovery_id (Z.to_int y_parity) in + let* sig_ = uncompressed_signature ~r ~s recovery_id in + let+ auth_signer = Tezos_crypto.Signature.Secp256k1.recover sig_ hash in + Address (Hex (Hex.of_bytes auth_signer |> Hex.show)) diff --git a/etherlink/bin_node/lib_dev/encodings/transaction.mli b/etherlink/bin_node/lib_dev/encodings/transaction.mli index 5b8ef3b05581..f73133797f63 100644 --- a/etherlink/bin_node/lib_dev/encodings/transaction.mli +++ b/etherlink/bin_node/lib_dev/encodings/transaction.mli @@ -68,3 +68,7 @@ val to_transaction_object : hash:Ethereum_types.hash -> transaction -> (Ethereum_types.legacy_transaction_object, string) result + +(** [auth_signer authorization] returns the signer of the [authorization]. *) +val auth_signer : + authorization_list_item -> (Ethereum_types.address, string) result -- GitLab From c177a2d529fe1143414c0fadff844e368067d996 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Mon, 8 Sep 2025 14:12:26 +0200 Subject: [PATCH 2/2] EVM/Node: check authorizations at prevalidation --- .../bin_node/lib_dev/encodings/transaction.ml | 2 +- etherlink/bin_node/lib_dev/prevalidator.ml | 40 +++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/etherlink/bin_node/lib_dev/encodings/transaction.ml b/etherlink/bin_node/lib_dev/encodings/transaction.ml index 138d50861ff8..ac52023b3f73 100644 --- a/etherlink/bin_node/lib_dev/encodings/transaction.ml +++ b/etherlink/bin_node/lib_dev/encodings/transaction.ml @@ -646,7 +646,7 @@ let authorization_hash authorization = let rlp_authorization = auth_message authorization in Buffer.add_bytes buffer rlp_authorization ; let raw_bytes = Buffer.to_bytes buffer in - Tezos_crypto.Signature.Secp256k1.keccak256_digest raw_bytes + Tezos_crypto.Hacl.Hash.Keccak_256.digest raw_bytes let auth_signer ({r; s; y_parity; _} as authorization : authorization_list_item) = diff --git a/etherlink/bin_node/lib_dev/prevalidator.ml b/etherlink/bin_node/lib_dev/prevalidator.ml index 2c6cccceea42..5a018a1e4e52 100644 --- a/etherlink/bin_node/lib_dev/prevalidator.ml +++ b/etherlink/bin_node/lib_dev/prevalidator.ml @@ -230,13 +230,12 @@ let ( let**? ) v f = let open Lwt_result_syntax in match v with Ok v -> f v | Error err -> return (Error err) -let validate_chain_id ctxt (transaction : Transaction.transaction) : +let validate_chain_id chain_id (transaction : Transaction.transaction) : (unit, string) result tzresult Lwt.t = let open Lwt_result_syntax in match transaction.chain_id with | None -> return (Ok ()) | Some transaction_chain_id -> - let (Chain_id chain_id) = ctxt.chain_id in if Z.equal transaction_chain_id chain_id then return (Ok ()) else return (Error "Invalid chain id") @@ -276,6 +275,37 @@ let validate_gas_limit session (transaction : Transaction.transaction) : execution_gas_limit)) else return (Ok ()) +let validate_authorizations (type state) ~session ~chain_id authorization_list = + let open Lwt_result_syntax in + let (module Backend_rpc : Services_backend_sig.S + with type Reader.state = state) = + session.state_backend + in + let read_nonce address = + Etherlink_durable_storage.nonce + (Backend_rpc.Reader.read session.state) + address + |> lwt_map_error (fun _ -> "Couldn't retrieve address' nonce") + in + let check_auth (item : Transaction.authorization_list_item) = + if chain_id != item.chain_id then fail "Authorization chain id mismatch" + else + let*? signer_address = Transaction.auth_signer item in + let* current_nonce = read_nonce signer_address in + let current_nonce = + match current_nonce with + | Some (Qty current_nonce) -> current_nonce + | None -> Z.zero + in + if Z.equal current_nonce item.nonce then return_unit + else fail "Authorization nonce mismatch" + in + let*! opt_err = + Lwt_list.map_p check_auth authorization_list + |> Lwt.map (List.find Result.is_error) + in + match opt_err with Some error -> return error | None -> return (Ok ()) + let validate_sender_not_a_contract (type state) session caller : (unit, string) result tzresult Lwt.t = let open Lwt_result_syntax in @@ -435,10 +465,14 @@ let minimal_validation ~next_nonce ~max_number_of_chunks ctxt transaction ~caller = let open Lwt_result_syntax in let (Session session) = ctxt.session in + let (Chain_id chain_id) = ctxt.chain_id in let** () = validate_minimum_gas_requirement ~session ~transaction in - let** () = validate_chain_id ctxt transaction in + let** () = validate_chain_id chain_id transaction in let** () = validate_nonce ~next_nonce transaction in let** () = validate_sender_not_a_contract session caller in + let** () = + validate_authorizations ~session ~chain_id transaction.authorization_list + in let** () = validate_tx_data_size ~max_number_of_chunks transaction in let** () = validate_gas_limit session transaction in return (Ok ()) -- GitLab