diff --git a/etherlink/CHANGES_NODE.md b/etherlink/CHANGES_NODE.md index 4e29ffabd5910b4475b3bc5871b5bfd55b1bae04..eaf752727cc180c06a10c306f85e82f84f6529d1 100644 --- a/etherlink/CHANGES_NODE.md +++ b/etherlink/CHANGES_NODE.md @@ -14,9 +14,10 @@ ### Execution changes +- Add support for GCP KMS `EC_SIGN_SECP256K1_SHA256` keys. (!18618) - Add a new command `show gcp key` which can be used to retrieve Tezos-specific - (b58-encoded public key, Tezos implicit account) information about a key - stored in a GCP KMS. (!18617) + (b58-encoded public key, Tezos implicit account) and Ethereum-specific (EOA + address) information about a key stored in a GCP KMS. (!18617 !18618) ### Storage changes diff --git a/etherlink/bin_node/lib_dev/gcp_kms.ml b/etherlink/bin_node/lib_dev/gcp_kms.ml index d6a59f9f0e12a928ce9c55051d7a4545a7a19e2e..f1cca4f5b7f308665591352708abb376537d3590 100644 --- a/etherlink/bin_node/lib_dev/gcp_kms.ml +++ b/etherlink/bin_node/lib_dev/gcp_kms.ml @@ -22,10 +22,13 @@ type partial = unit handler interpret their responses. *) type t = Signature.public_key handler -type signature_algorithm = EC_SIGN_P256_SHA256 +type signature_algorithm = EC_SIGN_P256_SHA256 | EC_SIGN_SECP256K1_SHA256 let signature_algorithm (kms : t) = - match kms.public_key with P256 _ -> EC_SIGN_P256_SHA256 | _ -> assert false + match kms.public_key with + | P256 _ -> EC_SIGN_P256_SHA256 + | Secp256k1 _ -> EC_SIGN_SECP256K1_SHA256 + | _ -> assert false type hash_algorithm = Blake2B @@ -33,6 +36,7 @@ let signature_algorithm_of_string = let open Result_syntax in function | "EC_SIGN_P256_SHA256" -> return EC_SIGN_P256_SHA256 + | "EC_SIGN_SECP256K1_SHA256" -> return EC_SIGN_SECP256K1_SHA256 | algo -> tzfail (error_of_fmt "Unsupported algorithm %s" algo) let service_name = "Gcp_kms" @@ -215,24 +219,29 @@ let extract_tail der_str n = if len < n then error_with "input is too short" else Result.return (String.sub der_str (len - n) n) +let pem_to_der pem = + let lines = String.split_on_char '\n' pem in + let b64 = + lines + |> List.filter (fun l -> not (String.starts_with ~prefix:"-----" l)) + |> String.concat "" + in + Base64.decode_exn b64 + +let tag = function + | EC_SIGN_SECP256K1_SHA256 -> '\x01' + | EC_SIGN_P256_SHA256 -> '\x02' + let key_of_pem algo pem = let open Result_syntax in - match algo with - | EC_SIGN_P256_SHA256 -> ( - let* pem = - match X509.Public_key.decode_pem pem with - | Ok pem -> return pem - | Error (`Msg err) -> - error_with "Could not decode pem public key (%s)" err - in - let key_str = X509.Public_key.encode_der pem in - let* key_str = extract_tail key_str 65 in - match - Signature.Public_key.of_bytes_without_validation - (Bytes.unsafe_of_string (Format.sprintf "\x02%s" key_str)) - with - | Some res -> return res - | _ -> error_with "Not a valid public key") + let der = pem_to_der pem in + let* key_str = extract_tail der 65 in + match + Signature.Public_key.of_bytes_without_validation + (Bytes.unsafe_of_string (Format.sprintf "%c%s" (tag algo) key_str)) + with + | Some res -> return res + | _ -> error_with "Not a valid public key" let public_key kms_handler = let open Lwt_result_syntax in @@ -310,6 +319,9 @@ let signature_of_b64encoded algo b64_sig = | EC_SIGN_P256_SHA256 -> let*? p256_sig = Signature.P256.of_string signature in return (Signature.of_p256 p256_sig) + | EC_SIGN_SECP256K1_SHA256 -> + let*? secp256k1_sig = Signature.Secp256k1.of_string signature in + return (Signature.of_secp256k1 secp256k1_sig) let sign kms_handler hash payload = let open Lwt_result_syntax in @@ -371,3 +383,12 @@ let from_gcp_key (config : Configuration.gcp_kms) gcp_key = return {kms_handler with public_key} let public_key t = t.public_key + +let ethereum_address_opt kms = + let pk = public_key (kms : t) in + match pk with + | Secp256k1 s -> + let str = Signature.Secp256k1.eth_address_of_public_key s in + let (`Hex hex) = Hex.of_bytes str in + Some Ethereum_types.(Address (Hex hex)) + | _ -> None diff --git a/etherlink/bin_node/lib_dev/gcp_kms.mli b/etherlink/bin_node/lib_dev/gcp_kms.mli index 6fbac911de86b848d28eaddbc02fbd61ddfb12ef..dd76621a42307cd29b67fd8c454ad3f4b4b10525 100644 --- a/etherlink/bin_node/lib_dev/gcp_kms.mli +++ b/etherlink/bin_node/lib_dev/gcp_kms.mli @@ -17,3 +17,8 @@ val gcp_key : t -> Configuration.gcp_key val public_key : t -> Signature.Public_key.t val sign : t -> hash_algorithm -> bytes -> Signature.t tzresult Lwt.t + +(** [ethereum_address_opt kms] returns the Ethereum address of the key managed + by [kms], if said key is compatible ([EC_SIGN_SECP256K1_SHA256]). Returns + [None] otherwise. *) +val ethereum_address_opt : t -> Ethereum_types.address option diff --git a/etherlink/bin_node/main.ml b/etherlink/bin_node/main.ml index 83eb40388238c2bd5c4adde7daad321f29063b58..ee3a177c8a611b230e31488b8622afe0714c6f0a 100644 --- a/etherlink/bin_node/main.ml +++ b/etherlink/bin_node/main.ml @@ -1427,15 +1427,24 @@ let show_kms_key_info_command = match gcp_key_from_string_opt key_str with | Some gcp_key -> let open Evm_node_lib_dev in + let open Evm_node_lib_dev_encoding in let* kms = Gcp_kms.from_gcp_key config.gcp_kms gcp_key in let pk = Gcp_kms.public_key kms in let pkh = Signature.Public_key.hash pk in - Format.printf - "@[Public key: %a@ Public key hash: %a@ @]" + let eth_opt = Gcp_kms.ethereum_address_opt kms in + let open Format in + printf + "@[Public key: %a@ Public key hash: %a%a@ @]" Signature.Public_key.pp pk Signature.Public_key_hash.pp - pkh ; + pkh + (pp_print_option (fun fmt addr -> + fprintf + fmt + "@ Ethereum address: %s" + (Ethereum_types.Address.to_string addr))) + eth_opt ; return_unit | None -> failwith "%s is not a valid URI for a key held by a GCP KMS" key_str) diff --git a/src/lib_crypto/secp256k1.ml b/src/lib_crypto/secp256k1.ml index 87e3b026f1f82820a154de1837fa6e33be41ec07..20c8c5fba62d293cedd1b5ea1733366538daebe9 100644 --- a/src/lib_crypto/secp256k1.ml +++ b/src/lib_crypto/secp256k1.ml @@ -353,6 +353,18 @@ let deterministic_nonce_hash sk msg = let pop_verify _ ?msg:_ _ = false +let public_key_to_bytes_uncompressed pk = + Bigstring.to_bytes (Key.to_bytes ~compress:false context pk) + +let eth_address_of_public_key s = + let s_bytes = public_key_to_bytes_uncompressed s in + (* The public key hash is the hash of pk[1..]. *) + let s_hash = + Hacl.Hash.Keccak_256.digest Bytes.(sub s_bytes 1 (length s_bytes - 1)) + in + (* The ethereum address is pkhash[12..]. *) + Bytes.(sub s_hash 12 (length s_hash - 12)) + let recover signature msg = let open Error_monad.Result_syntax in (* Decode the signature. *) @@ -361,16 +373,5 @@ let recover signature msg = in (* Recover the public key that signed the message. *) let* public_key = Sign.recover context ~signature (Bigstring.of_bytes msg) in - let public_key_bytes = - Key.to_bytes ~compress:false context public_key |> Bigstring.to_bytes - in - (* The public key hash is the hash of pk[1..]. *) - let public_key_hash = - Hacl.Hash.Keccak_256.digest - (Bytes.sub public_key_bytes 1 (Bytes.length public_key_bytes - 1)) - in - (* The ethereum address is pkhash[12..]. *) - let address = - Bytes.sub public_key_hash 12 (Bytes.length public_key_hash - 12) - in + let address = eth_address_of_public_key public_key in return address diff --git a/src/lib_crypto/secp256k1.mli b/src/lib_crypto/secp256k1.mli index 3518a7f70960456aa83b8ad53902aed38accb690..ea0e7675070de4fab54f91cd5d18a83bb7f27b10 100644 --- a/src/lib_crypto/secp256k1.mli +++ b/src/lib_crypto/secp256k1.mli @@ -41,3 +41,7 @@ val check_keccak256 : Public_key.t -> t -> bytes -> bool the rightmost 160-bits of the Keccak-256 hash of the corresponding ECDSA public key. *) val recover : bytes -> bytes -> (bytes, string) result + +(** [eth_address_of_public_key pk] computes the Ethereum address of an + Externally Owned Account (EOA) using this public key. *) +val eth_address_of_public_key : Public_key.t -> bytes