diff --git a/Cargo.lock b/Cargo.lock index e5e83db18..235549858 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -972,6 +972,12 @@ dependencies = [ "regex", ] +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "bech32" version = "0.11.0" @@ -1347,6 +1353,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ + "sha2 0.10.8", "tinyvec", ] @@ -1550,7 +1557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf52cc4b4cdf73fc07d9eeaea6d27bb39eed81f4bf8c89f01df86ace4e6da10" dependencies = [ "base64 0.22.1", - "bech32", + "bech32 0.11.0", "blockstore", "bytes", "celestia-proto", @@ -1819,6 +1826,58 @@ dependencies = [ "indexmap 1.9.3", ] +[[package]] +name = "coins-bip32" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b6be4a5df2098cd811f3194f64ddb96c267606bffd9689ac7b0160097b01ad3" +dependencies = [ + "bs58", + "coins-core", + "digest 0.10.7", + "hmac 0.12.1", + "k256 0.13.4", + "serde", + "sha2 0.10.8", + "thiserror 1.0.69", +] + +[[package]] +name = "coins-bip39" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528" +dependencies = [ + "bitvec", + "coins-bip32", + "hmac 0.12.1", + "once_cell", + "pbkdf2 0.12.2", + "rand 0.8.5", + "sha2 0.10.8", + "thiserror 1.0.69", +] + +[[package]] +name = "coins-core" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" +dependencies = [ + "base64 0.21.7", + "bech32 0.9.1", + "bs58", + "digest 0.10.7", + "generic-array", + "hex", + "ripemd", + "serde", + "serde_derive", + "sha2 0.10.8", + "sha3 0.10.8", + "thiserror 1.0.69", +] + [[package]] name = "colorchoice" version = "1.0.3" @@ -2400,7 +2459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ "serde", - "uuid", + "uuid 1.11.0", ] [[package]] @@ -2839,6 +2898,28 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "eth-keystore" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" +dependencies = [ + "aes", + "ctr", + "digest 0.10.7", + "hex", + "hmac 0.12.1", + "pbkdf2 0.11.0", + "rand 0.8.5", + "scrypt", + "serde", + "serde_json", + "sha2 0.10.8", + "sha3 0.10.8", + "thiserror 1.0.69", + "uuid 0.8.2", +] + [[package]] name = "ethabi" version = "18.0.0" @@ -2864,8 +2945,10 @@ checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" dependencies = [ "crunchy", "fixed-hash", + "impl-codec", "impl-rlp", "impl-serde", + "scale-info", "tiny-keccak 2.0.2", ] @@ -2877,12 +2960,60 @@ checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ "ethbloom", "fixed-hash", + "impl-codec", "impl-rlp", "impl-serde", "primitive-types", + "scale-info", "uint", ] +[[package]] +name = "ethers-core" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d80cc6ad30b14a48ab786523af33b37f28a8623fc06afd55324816ef18fb1f" +dependencies = [ + "arrayvec 0.7.6", + "bytes", + "chrono", + "const-hex", + "elliptic-curve 0.13.8", + "ethabi", + "generic-array", + "k256 0.13.4", + "num_enum 0.7.3", + "open-fastrlp", + "rand 0.8.5", + "rlp", + "serde", + "serde_json", + "strum", + "tempfile", + "thiserror 1.0.69", + "tiny-keccak 2.0.2", + "unicode-xid 0.2.6", +] + +[[package]] +name = "ethers-signers" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228875491c782ad851773b652dd8ecac62cda8571d3bc32a5853644dd26766c2" +dependencies = [ + "async-trait", + "coins-bip32", + "coins-bip39", + "const-hex", + "elliptic-curve 0.13.8", + "eth-keystore", + "ethers-core", + "rand 0.8.5", + "sha2 0.10.8", + "thiserror 1.0.69", + "tracing", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -3586,8 +3717,29 @@ checksum = "1112c453c2e155b3e683204ffff52bcc6d6495d04b68d9e90cd24161270c5058" dependencies = [ "async-trait", "base64 0.21.7", - "google-cloud-metadata", - "google-cloud-token", + "google-cloud-metadata 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "google-cloud-token 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "home", + "jsonwebtoken", + "reqwest 0.12.9", + "serde", + "serde_json", + "thiserror 1.0.69", + "time", + "tokio", + "tracing", + "urlencoding", +] + +[[package]] +name = "google-cloud-auth" +version = "0.16.0" +source = "git+https://github.com/yoshidan/google-cloud-rust.git?tag=v20240627#8f387a13309707c493d2e581961ee5b0f20631fc" +dependencies = [ + "async-trait", + "base64 0.21.7", + "google-cloud-metadata 0.5.0 (git+https://github.com/yoshidan/google-cloud-rust.git?tag=v20240627)", + "google-cloud-token 0.1.2 (git+https://github.com/yoshidan/google-cloud-rust.git?tag=v20240627)", "home", "jsonwebtoken", "reqwest 0.12.9", @@ -3600,6 +3752,51 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "google-cloud-gax" +version = "0.18.0" +source = "git+https://github.com/yoshidan/google-cloud-rust.git?tag=v20240627#8f387a13309707c493d2e581961ee5b0f20631fc" +dependencies = [ + "google-cloud-token 0.1.2 (git+https://github.com/yoshidan/google-cloud-rust.git?tag=v20240627)", + "http 0.2.12", + "thiserror 1.0.69", + "tokio", + "tokio-retry", + "tonic 0.11.0", + "tower 0.4.13", + "tracing", +] + +[[package]] +name = "google-cloud-googleapis" +version = "0.14.0" +source = "git+https://github.com/yoshidan/google-cloud-rust.git?tag=v20240627#8f387a13309707c493d2e581961ee5b0f20631fc" +dependencies = [ + "prost 0.12.6", + "prost-types", + "tonic 0.11.0", +] + +[[package]] +name = "google-cloud-kms" +version = "0.3.0" +source = "git+https://github.com/yoshidan/google-cloud-rust.git?tag=v20240627#8f387a13309707c493d2e581961ee5b0f20631fc" +dependencies = [ + "async-trait", + "ethers-core", + "ethers-signers", + "google-cloud-auth 0.16.0 (git+https://github.com/yoshidan/google-cloud-rust.git?tag=v20240627)", + "google-cloud-gax", + "google-cloud-googleapis", + "google-cloud-token 0.1.2 (git+https://github.com/yoshidan/google-cloud-rust.git?tag=v20240627)", + "k256 0.13.4", + "prost-types", + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", +] + [[package]] name = "google-cloud-metadata" version = "0.5.0" @@ -3611,6 +3808,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "google-cloud-metadata" +version = "0.5.0" +source = "git+https://github.com/yoshidan/google-cloud-rust.git?tag=v20240627#8f387a13309707c493d2e581961ee5b0f20631fc" +dependencies = [ + "reqwest 0.12.9", + "thiserror 1.0.69", + "tokio", +] + [[package]] name = "google-cloud-storage" version = "0.20.0" @@ -3623,9 +3830,9 @@ dependencies = [ "base64 0.21.7", "bytes", "futures-util", - "google-cloud-auth", - "google-cloud-metadata", - "google-cloud-token", + "google-cloud-auth 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", + "google-cloud-metadata 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "google-cloud-token 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "hex", "once_cell", "percent-encoding", @@ -3653,6 +3860,14 @@ dependencies = [ "async-trait", ] +[[package]] +name = "google-cloud-token" +version = "0.1.2" +source = "git+https://github.com/yoshidan/google-cloud-rust.git?tag=v20240627#8f387a13309707c493d2e581961ee5b0f20631fc" +dependencies = [ + "async-trait", +] + [[package]] name = "governor" version = "0.4.2" @@ -5856,6 +6071,31 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "open-fastrlp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" +dependencies = [ + "arrayvec 0.7.6", + "auto_impl", + "bytes", + "ethereum-types", + "open-fastrlp-derive", +] + +[[package]] +name = "open-fastrlp-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" +dependencies = [ + "bytes", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 1.0.109", +] + [[package]] name = "openssl" version = "0.10.68" @@ -6152,6 +6392,15 @@ dependencies = [ "serde", ] +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "pbkdf2" version = "0.12.2" @@ -6159,6 +6408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", + "hmac 0.12.1", ] [[package]] @@ -7310,7 +7560,7 @@ dependencies = [ "rkyv_derive", "seahash", "tinyvec", - "uuid", + "uuid 1.11.0", ] [[package]] @@ -7331,9 +7581,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" dependencies = [ "bytes", + "rlp-derive", "rustc-hex", ] +[[package]] +name = "rlp-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 1.0.109", +] + [[package]] name = "rocksdb" version = "0.21.0" @@ -7664,6 +7926,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -7833,6 +8104,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" +dependencies = [ + "hmac 0.12.1", + "pbkdf2 0.11.0", + "salsa20", + "sha2 0.10.8", +] + [[package]] name = "sct" version = "0.7.1" @@ -8103,7 +8386,7 @@ dependencies = [ "thiserror 1.0.69", "time", "url", - "uuid", + "uuid 1.11.0", ] [[package]] @@ -8533,7 +8816,7 @@ dependencies = [ "num-bigint 0.4.6", "num-rational", "num-traits", - "pbkdf2", + "pbkdf2 0.12.2", "pin-project", "poly1305", "rand 0.8.5", @@ -9203,7 +9486,7 @@ dependencies = [ "hex", "hmac 0.12.1", "parity-scale-codec", - "pbkdf2", + "pbkdf2 0.12.2", "regex", "schnorrkel", "secrecy 0.8.0", @@ -9705,6 +9988,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-retry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" +dependencies = [ + "pin-project", + "rand 0.8.5", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -9830,6 +10124,7 @@ dependencies = [ "axum 0.6.20", "base64 0.21.7", "bytes", + "flate2", "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", @@ -9848,6 +10143,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", + "webpki-roots", ] [[package]] @@ -10295,6 +10591,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", + "serde", +] + [[package]] name = "uuid" version = "1.11.0" @@ -11740,7 +12046,7 @@ dependencies = [ "async-trait", "backon", "base58", - "bech32", + "bech32 0.11.0", "bincode", "blake2 0.10.6", "blake2b_simd", @@ -11915,8 +12221,14 @@ name = "zksync_eth_signer" version = "0.1.0" dependencies = [ "async-trait", + "ethers-signers", + "google-cloud-gax", + "google-cloud-kms", + "hex", "rlp", "thiserror 1.0.69", + "tokio", + "tracing", "zksync_basic_types", "zksync_crypto_primitives", ] @@ -12566,7 +12878,7 @@ dependencies = [ "async-trait", "bincode", "flate2", - "google-cloud-auth", + "google-cloud-auth 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "google-cloud-storage", "http 1.2.0", "prost 0.12.6", diff --git a/core/bin/zksync_server/src/node_builder.rs b/core/bin/zksync_server/src/node_builder.rs index d74928e8f..38319f74b 100644 --- a/core/bin/zksync_server/src/node_builder.rs +++ b/core/bin/zksync_server/src/node_builder.rs @@ -6,8 +6,8 @@ use std::time::Duration; use anyhow::{bail, Context}; use zksync_config::{ configs::{ - da_client::DAClientConfig, secrets::DataAvailabilitySecrets, wallets::Wallets, - GeneralConfig, Secrets, + da_client::DAClientConfig, eth_sender::SigningMode, secrets::DataAvailabilitySecrets, + wallets::Wallets, GeneralConfig, Secrets, }, ContractsConfig, GenesisConfig, }; @@ -46,7 +46,7 @@ use zksync_node_framework::{ main_node_strategy::MainNodeInitStrategyLayer, NodeStorageInitializerLayer, }, object_store::ObjectStoreLayer, - pk_signing_eth_client::PKSigningEthClientLayer, + pk_signing_eth_client::{PKSigningEthClientLayer, SigningEthClientType}, pools_layer::PoolsLayerBuilder, postgres::PostgresLayer, prometheus_exporter::PrometheusExporterLayer, @@ -75,7 +75,6 @@ use zksync_types::{ pubdata_da::PubdataSendingMode, settlement::SettlementMode, SHARED_BRIDGE_ETHER_TOKEN_ADDRESS, }; use zksync_vlog::prometheus::PrometheusExporterConfig; - /// Macro that looks into a path to fetch an optional config, /// and clones it into a variable. macro_rules! try_load_config { @@ -146,11 +145,29 @@ impl MainNodeBuilder { fn add_pk_signing_client_layer(mut self) -> anyhow::Result { let eth_config = try_load_config!(self.configs.eth); let wallets = try_load_config!(self.wallets.eth_sender); + + let eth_sender = self + .configs + .eth + .clone() + .context("eth_config")? + .sender + .context("sender")?; + + let signing_mode = eth_sender.signing_mode.clone(); + tracing::info!("Using signing mode: {:?}", signing_mode); + + let client_type = match signing_mode { + SigningMode::GcloudKms => SigningEthClientType::GKMSSigningEthClient, + SigningMode::PrivateKey => SigningEthClientType::PKSigningEthClient, + }; + self.node.add_layer(PKSigningEthClientLayer::new( eth_config, self.contracts_config.clone(), self.genesis_config.settlement_layer_id(), wallets, + client_type, )); Ok(self) } diff --git a/core/lib/config/src/configs/eth_sender.rs b/core/lib/config/src/configs/eth_sender.rs index be2f5b532..1355b8ee2 100644 --- a/core/lib/config/src/configs/eth_sender.rs +++ b/core/lib/config/src/configs/eth_sender.rs @@ -42,6 +42,7 @@ impl EthConfig { tx_aggregation_paused: false, tx_aggregation_only_prove_and_execute: false, time_in_mempool_in_l1_blocks_cap: 1800, + signing_mode: SigningMode::PrivateKey, }), gas_adjuster: Some(GasAdjusterConfig { default_priority_fee_per_gas: 1000000000, @@ -79,6 +80,13 @@ pub enum ProofLoadingMode { FriProofFromGcs, } +#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Default)] +pub enum SigningMode { + #[default] + PrivateKey, + GcloudKms, +} + #[derive(Debug, Deserialize, Clone, PartialEq)] pub struct SenderConfig { /// Amount of confirmations required to consider L1 transaction committed. @@ -121,6 +129,8 @@ pub struct SenderConfig { /// Cap of time in mempool for price calculations #[serde(default = "SenderConfig::default_time_in_mempool_in_l1_blocks_cap")] pub time_in_mempool_in_l1_blocks_cap: u32, + /// Type of signing client for Ethereum transactions. + pub signing_mode: SigningMode, } impl SenderConfig { diff --git a/core/lib/config/src/testonly.rs b/core/lib/config/src/testonly.rs index 749dba1f9..23f5e1b46 100644 --- a/core/lib/config/src/testonly.rs +++ b/core/lib/config/src/testonly.rs @@ -23,6 +23,7 @@ use crate::{ avail::{AvailClientConfig, AvailDefaultConfig}, DAClientConfig::Avail, }, + eth_sender::SigningMode, external_price_api_client::ForcedPriceClientConfig, }, AvailConfig, @@ -418,6 +419,7 @@ impl Distribution for EncodeDist { tx_aggregation_paused: false, tx_aggregation_only_prove_and_execute: false, time_in_mempool_in_l1_blocks_cap: self.sample(rng), + signing_mode: SigningMode::PrivateKey, } } } diff --git a/core/lib/env_config/src/eth_sender.rs b/core/lib/env_config/src/eth_sender.rs index f822109ed..a2af80fc1 100644 --- a/core/lib/env_config/src/eth_sender.rs +++ b/core/lib/env_config/src/eth_sender.rs @@ -45,7 +45,7 @@ impl FromEnv for GasAdjusterConfig { #[cfg(test)] mod tests { use zksync_basic_types::pubdata_da::PubdataSendingMode; - use zksync_config::configs::eth_sender::ProofSendingMode; + use zksync_config::configs::eth_sender::{ProofSendingMode, SigningMode}; use super::*; use crate::test_utils::{hash, EnvMutex}; @@ -61,7 +61,6 @@ mod tests { aggregated_block_execute_deadline: 4_000, max_aggregated_tx_gas: 4_000_000, max_eth_tx_data_size: 120_000, - timestamp_criteria_max_allowed_lag: 30, max_aggregated_blocks_to_commit: 3, max_aggregated_blocks_to_execute: 4, @@ -76,6 +75,7 @@ mod tests { tx_aggregation_only_prove_and_execute: false, tx_aggregation_paused: false, time_in_mempool_in_l1_blocks_cap: 2000, + signing_mode: SigningMode::PrivateKey, }), gas_adjuster: Some(GasAdjusterConfig { default_priority_fee_per_gas: 20000000000, @@ -141,6 +141,7 @@ mod tests { ETH_SENDER_SENDER_PUBDATA_SENDING_MODE="Calldata" ETH_WATCH_CONFIRMATIONS_FOR_ETH_EVENT="0" ETH_WATCH_ETH_NODE_POLL_INTERVAL="300" + ETH_SENDER_SENDER_SIGNING_MODE="PrivateKey" ETH_CLIENT_WEB3_URL="http://127.0.0.1:8545" ETH_CLIENT_GATEWAY_WEB3_URL="http://127.0.0.1:8547" diff --git a/core/lib/eth_client/src/clients/http/mod.rs b/core/lib/eth_client/src/clients/http/mod.rs index 111507c65..830b2a9ec 100644 --- a/core/lib/eth_client/src/clients/http/mod.rs +++ b/core/lib/eth_client/src/clients/http/mod.rs @@ -4,7 +4,7 @@ use vise::{ Buckets, Counter, EncodeLabelSet, EncodeLabelValue, Family, Histogram, LabeledFamily, Metrics, }; -pub use self::signing::{PKSigningClient, SigningClient}; +pub use self::signing::{GKMSSigningClient, PKSigningClient, SigningClient}; mod decl; mod query; diff --git a/core/lib/eth_client/src/clients/http/signing.rs b/core/lib/eth_client/src/clients/http/signing.rs index e602f98a3..e827a411a 100644 --- a/core/lib/eth_client/src/clients/http/signing.rs +++ b/core/lib/eth_client/src/clients/http/signing.rs @@ -2,7 +2,9 @@ use std::{fmt, sync::Arc}; use async_trait::async_trait; use zksync_contracts::hyperchain_contract; -use zksync_eth_signer::{EthereumSigner, PrivateKeySigner, TransactionParameters}; +use zksync_eth_signer::{ + g_kms_signer::GKMSSigner, EthereumSigner, PrivateKeySigner, TransactionParameters, +}; use zksync_types::{ ethabi, web3, Address, K256PrivateKey, SLChainId, EIP_4844_TX_TYPE, H160, U256, }; @@ -40,6 +42,37 @@ impl PKSigningClient { } } +pub type GKMSSigningClient = SigningClient; + +impl GKMSSigningClient { + pub async fn new_raw( + diamond_proxy_addr: Address, + default_priority_fee_per_gas: u64, + chain_id: SLChainId, + query_client: Box>, + key_name: String, + ) -> Self { + let signer = match GKMSSigner::new(key_name, chain_id.0).await { + Ok(s) => s, + Err(e) => panic!("Failed to create GKMSSigner: {:?}", e), + }; + + SigningClient::new( + query_client, + hyperchain_contract(), + signer.get_address().await.unwrap(), + signer, + diamond_proxy_addr, + default_priority_fee_per_gas.into(), + chain_id, + ) + } + + pub fn get_address(&self) -> Address { + self.inner.sender_account + } +} + /// Gas limit value to be used in transaction if for some reason /// gas limit was not set for it. /// diff --git a/core/lib/eth_client/src/clients/mod.rs b/core/lib/eth_client/src/clients/mod.rs index b08f92115..42398fd78 100644 --- a/core/lib/eth_client/src/clients/mod.rs +++ b/core/lib/eth_client/src/clients/mod.rs @@ -6,6 +6,6 @@ mod mock; pub use zksync_web3_decl::client::{Client, DynClient, L1}; pub use self::{ - http::{PKSigningClient, SigningClient}, + http::{GKMSSigningClient, PKSigningClient, SigningClient}, mock::{MockSettlementLayer, MockSettlementLayerBuilder}, }; diff --git a/core/lib/eth_signer/Cargo.toml b/core/lib/eth_signer/Cargo.toml index 92bb47824..0d1da52fa 100644 --- a/core/lib/eth_signer/Cargo.toml +++ b/core/lib/eth_signer/Cargo.toml @@ -17,3 +17,11 @@ zksync_crypto_primitives.workspace = true async-trait.workspace = true rlp.workspace = true thiserror.workspace = true +google-cloud-kms = { git="https://github.com/yoshidan/google-cloud-rust.git", tag="v20240627", features=["eth"]} +google-cloud-gax = { git="https://github.com/yoshidan/google-cloud-rust.git", tag="v20240627"} +hex = "0.4.3" +tracing = "0.1" +ethers-signers = "2.0" + +[dev-dependencies] +tokio = { workspace = true, features = ["full"] } diff --git a/core/lib/eth_signer/src/g_kms_signer.rs b/core/lib/eth_signer/src/g_kms_signer.rs new file mode 100644 index 000000000..ce4266610 --- /dev/null +++ b/core/lib/eth_signer/src/g_kms_signer.rs @@ -0,0 +1,162 @@ +use std::result::Result; + +use ethers_signers::Signer as EthSigner; +use google_cloud_gax::retry::RetrySetting; +use google_cloud_kms::{ + client::{Client, ClientConfig}, + signer::ethereum::Signer, +}; +use hex; +use tracing::{self}; +use zksync_basic_types::{ + web3::{keccak256, Signature}, + Address, H256, U256, +}; +use zksync_crypto_primitives::{EIP712TypedStructure, Eip712Domain, PackedEthSignature}; + +use crate::{ + raw_ethereum_tx::{Transaction, TransactionParameters}, + EthereumSigner, SignerError, +}; + +pub const GOOGLE_KMS_OP_KEY_NAME: &str = "GOOGLE_KMS_OP_KEY_NAME"; +pub const GOOGLE_KMS_OP_BLOB_KEY_NAME: &str = "GOOGLE_KMS_OP_BLOB_KEY_NAME"; + +#[derive(Clone)] +pub struct GKMSSigner { + signer: Signer, +} + +impl GKMSSigner { + pub async fn new(key_name: String, _chain_id: u64) -> Result { + let config = ClientConfig::default() + .with_auth() + .await + .map_err(|e| SignerError::SigningFailed(e.to_string()))?; + + let client = Client::new(config) + .await + .map_err(|e| SignerError::SigningFailed(e.to_string()))?; + + let signer = Signer::new(client, &key_name, _chain_id, Some(RetrySetting::default())) + .await + .map_err(|e| SignerError::SigningFailed(e.to_string()))?; + + tracing::info!("KMS signer address: {:?}", hex::encode(signer.address())); + + Ok(GKMSSigner { signer }) + } + + fn u256_to_h256(u: U256) -> H256 { + let mut bytes = [0u8; 32]; + u.to_big_endian(&mut bytes); + H256::from(bytes) + } +} + +#[async_trait::async_trait] +impl EthereumSigner for GKMSSigner { + /// Get Ethereum address that matches the private key. + async fn get_address(&self) -> Result { + Ok(self.signer.address()) + } + + /// Signs typed struct using Ethereum private key by EIP-712 signature standard. + /// Result of this function is the equivalent of RPC calling `eth_signTypedData`. + async fn sign_typed_data( + &self, + domain: &Eip712Domain, + typed_struct: &S, + ) -> Result { + let digest = + H256::from(PackedEthSignature::typed_data_to_signed_bytes(domain, typed_struct).0); + + let signature = self + .signer + .sign_digest(digest.as_bytes()) + .await + .map_err(|e| SignerError::SigningFailed(e.to_string()))?; + + // Convert the signature components to the appropriate format. + let r_h256 = GKMSSigner::u256_to_h256(signature.r); + let s_h256 = GKMSSigner::u256_to_h256(signature.s); + + // Ensure the `v` component is in the correct byte format. + let v_byte = match signature.v.try_into() { + Ok(v) => v, + Err(_) => { + return Err(SignerError::SigningFailed( + "V value conversion failed".to_string(), + )) + } + }; + + // Construct the Ethereum signature from the R, S, and V components. + let eth_sig = PackedEthSignature::from_rsv(&r_h256, &s_h256, v_byte); + + Ok(eth_sig) + } + + /// Signs and returns the RLP-encoded transaction. + async fn sign_transaction( + &self, + raw_tx: TransactionParameters, + ) -> Result, SignerError> { + // According to the code in web3 + // We should use `max_fee_per_gas` as `gas_price` if we use EIP1559 + let gas_price = raw_tx.max_fee_per_gas; + let max_priority_fee_per_gas = raw_tx.max_priority_fee_per_gas; + + let tx = Transaction { + to: raw_tx.to, + nonce: raw_tx.nonce, + gas: raw_tx.gas, + gas_price, + value: raw_tx.value, + data: raw_tx.data, + transaction_type: raw_tx.transaction_type, + access_list: raw_tx.access_list.unwrap_or_default(), + max_priority_fee_per_gas, + max_fee_per_blob_gas: raw_tx.max_fee_per_blob_gas, + blob_versioned_hashes: raw_tx.blob_versioned_hashes, + }; + + let encoded = tx.encode_pub(raw_tx.chain_id, None); + let digest = H256(keccak256(encoded.as_ref())); + + let signature = self + .signer + .sign_digest(digest.as_bytes()) + .await + .map_err(|e| SignerError::SigningFailed(e.to_string()))?; + + let adjusted_v = if let Some(transaction_type) = tx.transaction_type.map(|t| t.as_u64()) { + match transaction_type { + 0 => signature.v + raw_tx.chain_id * 2 + 35, // EIP-155 + _ => signature.v, // EIP-2930 and others + } + } else { + signature.v + raw_tx.chain_id * 2 + 35 // EIP-155 + }; + + let r_h256 = GKMSSigner::u256_to_h256(signature.r); + let s_h256 = GKMSSigner::u256_to_h256(signature.s); + + tracing::debug!( + "KMS sign_transaction signature: v: {}, r: {}, s: {}", + adjusted_v, + hex::encode(r_h256), + hex::encode(s_h256), + ); + + let web3_sig = Signature { + v: adjusted_v, + r: r_h256, + s: s_h256, + }; + + let signed = tx.encode_pub(raw_tx.chain_id, Some(&web3_sig)); + + return Ok(signed); + } +} diff --git a/core/lib/eth_signer/src/lib.rs b/core/lib/eth_signer/src/lib.rs index 8b6025eb1..936a32671 100644 --- a/core/lib/eth_signer/src/lib.rs +++ b/core/lib/eth_signer/src/lib.rs @@ -4,6 +4,7 @@ use zksync_crypto_primitives::{EIP712TypedStructure, Eip712Domain, PackedEthSign pub use crate::{pk_signer::PrivateKeySigner, raw_ethereum_tx::TransactionParameters}; +pub mod g_kms_signer; mod pk_signer; mod raw_ethereum_tx; diff --git a/core/lib/eth_signer/src/raw_ethereum_tx.rs b/core/lib/eth_signer/src/raw_ethereum_tx.rs index bea64305b..3de44f91d 100644 --- a/core/lib/eth_signer/src/raw_ethereum_tx.rs +++ b/core/lib/eth_signer/src/raw_ethereum_tx.rs @@ -268,4 +268,8 @@ impl Transaction { transaction_hash, } } + + pub fn encode_pub(&self, chain_id: u64, signature: Option<&Signature>) -> Vec { + self.encode(chain_id, signature) + } } diff --git a/core/lib/protobuf_config/src/eth.rs b/core/lib/protobuf_config/src/eth.rs index 10e80810b..70c3e5d0b 100644 --- a/core/lib/protobuf_config/src/eth.rs +++ b/core/lib/protobuf_config/src/eth.rs @@ -45,6 +45,24 @@ impl proto::PubdataSendingMode { } } +impl proto::SigningMode { + fn new(x: &configs::eth_sender::SigningMode) -> Self { + use configs::eth_sender::SigningMode as From; + match x { + From::PrivateKey => Self::PrivateKey, + From::GcloudKms => Self::GcloudKms, + } + } + + fn parse(&self) -> configs::eth_sender::SigningMode { + use configs::eth_sender::SigningMode as To; + match self { + Self::PrivateKey => To::PrivateKey, + Self::GcloudKms => To::GcloudKms, + } + } +} + impl ProtoRepr for proto::Eth { type Type = configs::eth_sender::EthConfig; @@ -110,6 +128,10 @@ impl ProtoRepr for proto::Sender { time_in_mempool_in_l1_blocks_cap: self .time_in_mempool_in_l1_blocks_cap .unwrap_or(Self::Type::default_time_in_mempool_in_l1_blocks_cap()), + signing_mode: required(&self.signing_mode) + .and_then(|x| Ok(proto::SigningMode::try_from(*x)?)) + .context("signing_mode")? + .parse(), }) } @@ -138,6 +160,7 @@ impl ProtoRepr for proto::Sender { tx_aggregation_only_prove_and_execute: Some(this.tx_aggregation_only_prove_and_execute), tx_aggregation_paused: Some(this.tx_aggregation_paused), time_in_mempool_in_l1_blocks_cap: Some(this.time_in_mempool_in_l1_blocks_cap), + signing_mode: Some(proto::SigningMode::new(&this.signing_mode).into()), } } } diff --git a/core/lib/protobuf_config/src/proto/config/eth_sender.proto b/core/lib/protobuf_config/src/proto/config/eth_sender.proto index 1176afd7c..142de9951 100644 --- a/core/lib/protobuf_config/src/proto/config/eth_sender.proto +++ b/core/lib/protobuf_config/src/proto/config/eth_sender.proto @@ -27,6 +27,11 @@ enum PubdataSendingMode { RELAYED_L2_CALLDATA = 3; } +enum SigningMode { + PRIVATE_KEY = 0; + GCLOUD_KMS = 1; +} + message Sender { reserved 1; reserved "aggregated_proof_sizes"; optional uint64 wait_confirmations = 2; // optional @@ -45,10 +50,12 @@ message Sender { optional uint64 l1_batch_min_age_before_execute_seconds = 15; // optional; s optional uint64 max_acceptable_priority_fee_in_gwei = 16; // required; gwei optional PubdataSendingMode pubdata_sending_mode = 18; // required - reserved 19; reserved "proof_loading_mode"; + reserved 19; + reserved "proof_loading_mode"; optional bool tx_aggregation_paused = 20; // required optional bool tx_aggregation_only_prove_and_execute = 21; // required optional uint32 time_in_mempool_in_l1_blocks_cap = 22; // optional + optional SigningMode signing_mode = 99; // required } message GasAdjuster { diff --git a/core/node/node_framework/src/implementations/layers/pk_signing_eth_client.rs b/core/node/node_framework/src/implementations/layers/pk_signing_eth_client.rs index fdef23a40..4f072cb97 100644 --- a/core/node/node_framework/src/implementations/layers/pk_signing_eth_client.rs +++ b/core/node/node_framework/src/implementations/layers/pk_signing_eth_client.rs @@ -3,7 +3,7 @@ use zksync_config::{ configs::{wallets, ContractsConfig}, EthConfig, }; -use zksync_eth_client::clients::PKSigningClient; +use zksync_eth_client::clients::{GKMSSigningClient, PKSigningClient}; use zksync_types::SLChainId; use crate::{ @@ -15,12 +15,20 @@ use crate::{ }; /// Wiring layer for [`PKSigningClient`]. +#[derive(Debug)] +#[non_exhaustive] +pub enum SigningEthClientType { + PKSigningEthClient, + GKMSSigningEthClient, +} + #[derive(Debug)] pub struct PKSigningEthClientLayer { eth_sender_config: EthConfig, contracts_config: ContractsConfig, sl_chain_id: SLChainId, wallets: wallets::EthSender, + client_type: SigningEthClientType, } #[derive(Debug, FromContext)] @@ -43,12 +51,14 @@ impl PKSigningEthClientLayer { contracts_config: ContractsConfig, sl_chain_id: SLChainId, wallets: wallets::EthSender, + client_type: SigningEthClientType, ) -> Self { Self { eth_sender_config, contracts_config, sl_chain_id, wallets, + client_type, } } } @@ -63,34 +73,88 @@ impl WiringLayer for PKSigningEthClientLayer { } async fn wire(self, input: Self::Input) -> Result { - let private_key = self.wallets.operator.private_key(); - let gas_adjuster_config = self - .eth_sender_config - .gas_adjuster - .as_ref() - .context("gas_adjuster config is missing")?; - let EthInterfaceResource(query_client) = input.eth_client; - - let signing_client = PKSigningClient::new_raw( - private_key.clone(), - self.contracts_config.diamond_proxy_addr, - gas_adjuster_config.default_priority_fee_per_gas, - self.sl_chain_id, - query_client.clone(), - ); - let signing_client = BoundEthInterfaceResource(Box::new(signing_client)); - - let signing_client_for_blobs = self.wallets.blob_operator.map(|blob_operator| { - let private_key = blob_operator.private_key(); - let signing_client_for_blobs = PKSigningClient::new_raw( - private_key.clone(), - self.contracts_config.diamond_proxy_addr, - gas_adjuster_config.default_priority_fee_per_gas, - self.sl_chain_id, - query_client, - ); - BoundEthInterfaceForBlobsResource(Box::new(signing_client_for_blobs)) - }); + let signing_client; + let mut signing_client_for_blobs = None; + + match self.client_type { + SigningEthClientType::PKSigningEthClient => { + let private_key = self.wallets.operator.private_key(); + let gas_adjuster_config = self + .eth_sender_config + .gas_adjuster + .as_ref() + .context("gas_adjuster config is missing")?; + let EthInterfaceResource(query_client) = input.eth_client; + + let sc = PKSigningClient::new_raw( + private_key.clone(), + self.contracts_config.diamond_proxy_addr, + gas_adjuster_config.default_priority_fee_per_gas, + self.sl_chain_id, + query_client.clone(), + ); + signing_client = BoundEthInterfaceResource(Box::new(sc)); + + signing_client_for_blobs = self.wallets.blob_operator.map(|blob_operator| { + let private_key = blob_operator.private_key(); + let signing_client_for_blobs = PKSigningClient::new_raw( + private_key.clone(), + self.contracts_config.diamond_proxy_addr, + gas_adjuster_config.default_priority_fee_per_gas, + self.sl_chain_id, + query_client, + ); + BoundEthInterfaceForBlobsResource(Box::new(signing_client_for_blobs)) + }); + } + SigningEthClientType::GKMSSigningEthClient => { + let gas_adjuster_config = self + .eth_sender_config + .gas_adjuster + .as_ref() + .context("gas_adjuster config is missing")?; + + let gkms_op_key_name = std::env::var("GOOGLE_KMS_OP_KEY_NAME").ok(); + tracing::info!( + "KMS op key name: {:?}", + std::env::var("GOOGLE_KMS_OP_KEY_NAME") + ); + + let EthInterfaceResource(query_client) = input.eth_client; + + let sc = GKMSSigningClient::new_raw( + self.contracts_config.diamond_proxy_addr, + gas_adjuster_config.default_priority_fee_per_gas, + self.sl_chain_id, + query_client.clone(), + gkms_op_key_name + .expect("gkms_op_key_name is required but was None") + .to_string(), + ) + .await; + + signing_client = BoundEthInterfaceResource(Box::new(sc)); + + let gkms_op_blob_key_name = std::env::var("GOOGLE_KMS_OP_BLOB_KEY_NAME").ok(); + tracing::info!( + "KMS op blob key name: {:?}", + std::env::var("GOOGLE_KMS_OP_BLOB_KEY_NAME") + ); + + if let Some(key_name) = gkms_op_blob_key_name { + let blobs_resources = GKMSSigningClient::new_raw( + self.contracts_config.diamond_proxy_addr, + gas_adjuster_config.default_priority_fee_per_gas, + self.sl_chain_id, + query_client, + key_name.to_string(), + ) + .await; + signing_client_for_blobs = + Some(BoundEthInterfaceForBlobsResource(Box::new(blobs_resources))); + }; + } + }; Ok(Output { signing_client, diff --git a/etc/env/base/eth_sender.toml b/etc/env/base/eth_sender.toml index ad5709551..97c5a6c1b 100644 --- a/etc/env/base/eth_sender.toml +++ b/etc/env/base/eth_sender.toml @@ -48,6 +48,8 @@ max_acceptable_priority_fee_in_gwei = 100000000000 pubdata_sending_mode = "Blobs" +signing_mode = "PrivateKey" + [eth_sender.gas_adjuster] # Priority fee to be used by GasAdjuster (in wei). default_priority_fee_per_gas = 1_000_000_000