Skip to content

Commit

Permalink
feat: missing GKMSSigner feature (#91)
Browse files Browse the repository at this point in the history
Co-authored-by: Calvin Lau <[email protected]>
  • Loading branch information
JayT106 and calvinaco authored Oct 25, 2024
1 parent 4551572 commit ce149aa
Show file tree
Hide file tree
Showing 16 changed files with 870 additions and 60 deletions.
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

0 comments on commit ce149aa

Please sign in to comment.