Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: missing GKMSSigner feature #91

Merged
merged 1 commit into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
518 changes: 497 additions & 21 deletions Cargo.lock

Large diffs are not rendered by default.

22 changes: 20 additions & 2 deletions core/bin/zksync_server/src/node_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use anyhow::Context;
use zksync_config::{
configs::{
da_client::DAClientConfig, secrets::DataAvailabilitySecrets, wallets::Wallets,
eth_sender::SigningMode,
GeneralConfig, Secrets,
},
ContractsConfig, GenesisConfig,
Expand Down Expand Up @@ -44,7 +45,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_metrics::PostgresMetricsLayer,
prometheus_exporter::PrometheusExporterLayer,
Expand Down Expand Up @@ -73,7 +74,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 {
Expand Down Expand Up @@ -144,11 +144,29 @@ impl MainNodeBuilder {
fn add_pk_signing_client_layer(mut self) -> anyhow::Result<Self> {
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)
}
Expand Down
10 changes: 10 additions & 0 deletions core/lib/config/src/configs/eth_sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,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,
Expand Down Expand Up @@ -80,6 +81,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 {
pub aggregated_proof_sizes: Vec<usize>,
Expand Down Expand Up @@ -123,6 +131,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 {
Expand Down
3 changes: 2 additions & 1 deletion core/lib/config/src/testonly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use zksync_crypto_primitives::K256PrivateKey;

use crate::{
configs::{
self, da_client::DAClientConfig::Avail, external_price_api_client::ForcedPriceClientConfig,
self, da_client::DAClientConfig::Avail, external_price_api_client::ForcedPriceClientConfig, eth_sender::SigningMode,
},
AvailConfig,
};
Expand Down Expand Up @@ -411,6 +411,7 @@ impl Distribution<configs::eth_sender::SenderConfig> 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,
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions core/lib/env_config/src/eth_sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,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};
Expand All @@ -59,7 +59,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,
Expand All @@ -74,6 +73,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,
Expand Down Expand Up @@ -139,6 +139,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"

"#;
Expand Down
2 changes: 1 addition & 1 deletion core/lib/eth_client/src/clients/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
35 changes: 34 additions & 1 deletion core/lib/eth_client/src/clients/http/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -40,6 +42,37 @@ impl PKSigningClient {
}
}

pub type GKMSSigningClient = SigningClient<GKMSSigner>;

impl GKMSSigningClient {
pub async fn new_raw(
diamond_proxy_addr: Address,
default_priority_fee_per_gas: u64,
chain_id: SLChainId,
query_client: Box<DynClient<L1>>,
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.
///
Expand Down
2 changes: 1 addition & 1 deletion core/lib/eth_client/src/clients/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
8 changes: 8 additions & 0 deletions core/lib/eth_signer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
161 changes: 161 additions & 0 deletions core/lib/eth_signer/src/g_kms_signer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
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::{Address, H256, U256,web3::{keccak256, Signature}};
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<Self, SignerError> {
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<Address, SignerError> {
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<S: EIP712TypedStructure + Sync>(
&self,
domain: &Eip712Domain,
typed_struct: &S,
) -> Result<PackedEthSignature, SignerError> {
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<Vec<u8>, SignerError> {
// According to the code in web3 <https://docs.rs/web3/latest/src/web3/api/accounts.rs.html#86>
// 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);
}
}
1 change: 1 addition & 0 deletions core/lib/eth_signer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
4 changes: 4 additions & 0 deletions core/lib/eth_signer/src/raw_ethereum_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,4 +268,8 @@ impl Transaction {
transaction_hash,
}
}

pub fn encode_pub(&self, chain_id: u64, signature: Option<&Signature>) -> Vec<u8> {
self.encode(chain_id, signature)
}
}
Loading
Loading