Skip to content

Commit

Permalink
Problem: No gkms support for eth_signer (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
JayT106 authored and thomas-nguy committed Jul 25, 2024
1 parent 9cdee2c commit d265acd
Show file tree
Hide file tree
Showing 21 changed files with 1,856 additions and 947 deletions.
2,363 changes: 1,459 additions & 904 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions core/bin/zksync_server/src/node_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
use anyhow::Context;
use zksync_config::{
configs::{
consensus::ConsensusConfig, eth_sender::PubdataSendingMode, wallets::Wallets,
consensus::ConsensusConfig,
eth_sender::{PubdataSendingMode, SigningMode},
wallets::Wallets,
GeneralConfig, Secrets,
},
ContractsConfig, GenesisConfig,
Expand Down Expand Up @@ -43,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 @@ -141,11 +143,17 @@ 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_config = try_load_config!(eth_config.sender);

self.node.add_layer(PKSigningEthClientLayer::new(
eth_config,
self.contracts_config.clone(),
self.genesis_config.l1_chain_id,
wallets,
match eth_sender_config.signing_mode {
SigningMode::PrivateKey => SigningEthClientType::PKSigningEthClient,
SigningMode::GcloudKms => SigningEthClientType::GKMSSigningEthClient,
},
));
Ok(self)
}
Expand Down
19 changes: 19 additions & 0 deletions core/lib/config/src/configs/eth_sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ impl EthConfig {
l1_batch_min_age_before_execute_seconds: None,
max_acceptable_priority_fee_in_gwei: 100000000000,
pubdata_sending_mode: PubdataSendingMode::Calldata,
signing_mode: SigningMode::PrivateKey,
gkms_op_key_name: None,
gkms_op_blob_key_name: None,
}),
gas_adjuster: Some(GasAdjusterConfig {
default_priority_fee_per_gas: 1000000000,
Expand Down Expand Up @@ -84,6 +87,13 @@ pub enum PubdataSendingMode {
Custom,
}

#[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 @@ -117,6 +127,15 @@ pub struct SenderConfig {

/// The mode in which we send pubdata: Calldata, Blobs or Custom (DA layers, Object Store, etc.)
pub pubdata_sending_mode: PubdataSendingMode,

/// Type of signing client for Ethereum transactions.
pub signing_mode: SigningMode,

/// Optional GCP KMS operation key name for Ethereum transactions.
pub gkms_op_key_name: Option<String>,

/// Optional GCP KMS operation key name for Ethereum blobs.
pub gkms_op_blob_key_name: Option<String>,
}

impl SenderConfig {
Expand Down
8 changes: 7 additions & 1 deletion core/lib/config/src/testonly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use zksync_basic_types::{
use zksync_consensus_utils::EncodeDist;
use zksync_crypto_primitives::K256PrivateKey;

use crate::configs::{self, eth_sender::PubdataSendingMode};
use crate::configs::{
self,
eth_sender::{PubdataSendingMode, SigningMode},
};

trait Sample {
fn sample(rng: &mut (impl Rng + ?Sized)) -> Self;
Expand Down Expand Up @@ -379,6 +382,9 @@ impl Distribution<configs::eth_sender::SenderConfig> for EncodeDist {
l1_batch_min_age_before_execute_seconds: self.sample(rng),
max_acceptable_priority_fee_in_gwei: self.sample(rng),
pubdata_sending_mode: PubdataSendingMode::Calldata,
signing_mode: SigningMode::PrivateKey,
gkms_op_key_name: None,
gkms_op_blob_key_name: None,
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion core/lib/env_config/src/eth_sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl FromEnv for GasAdjusterConfig {

#[cfg(test)]
mod tests {
use zksync_config::configs::eth_sender::{ProofSendingMode, PubdataSendingMode};
use zksync_config::configs::eth_sender::{ProofSendingMode, PubdataSendingMode, SigningMode};

use super::*;
use crate::test_utils::{hash, EnvMutex};
Expand Down Expand Up @@ -70,6 +70,9 @@ mod tests {
l1_batch_min_age_before_execute_seconds: Some(1000),
max_acceptable_priority_fee_in_gwei: 100_000_000_000,
pubdata_sending_mode: PubdataSendingMode::Calldata,
signing_mode: SigningMode::PrivateKey,
gkms_op_key_name: None,
gkms_op_blob_key_name: None,
}),
gas_adjuster: Some(GasAdjusterConfig {
default_priority_fee_per_gas: 20000000000,
Expand Down Expand Up @@ -131,6 +134,7 @@ mod tests {
ETH_SENDER_SENDER_L1_BATCH_MIN_AGE_BEFORE_EXECUTE_SECONDS="1000"
ETH_SENDER_SENDER_MAX_ACCEPTABLE_PRIORITY_FEE_IN_GWEI="100000000000"
ETH_SENDER_SENDER_PUBDATA_SENDING_MODE="Calldata"
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
33 changes: 32 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,7 @@ 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::{EthereumSigner, GKMSSigner, PrivateKeySigner, TransactionParameters};
use zksync_types::{
ethabi, web3, Address, K256PrivateKey, L1ChainId, EIP_4844_TX_TYPE, H160, U256,
};
Expand Down Expand Up @@ -40,6 +40,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,
l1_chain_id: L1ChainId,
query_client: Box<DynClient<L1>>,
key_name: String,
) -> Self {
let signer = match GKMSSigner::new(key_name, l1_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(),
l1_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::{MockEthereum, MockEthereumBuilder},
};
5 changes: 5 additions & 0 deletions core/lib/eth_signer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ zksync_types.workspace = true
rlp.workspace = true
thiserror.workspace = true
async-trait.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"] }
158 changes: 158 additions & 0 deletions core/lib/eth_signer/src/gkms_signer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
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_types::{
web3::{keccak256, Signature},
Address, EIP712TypedStructure, Eip712Domain, PackedEthSignature, H256, U256,
};

use crate::{
raw_ethereum_tx::{Transaction, TransactionParameters},
EthereumSigner, SignerError,
};

#[derive(Debug, 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);
}
}
5 changes: 4 additions & 1 deletion core/lib/eth_signer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use async_trait::async_trait;
use zksync_types::{Address, EIP712TypedStructure, Eip712Domain, PackedEthSignature};

pub use crate::{pk_signer::PrivateKeySigner, raw_ethereum_tx::TransactionParameters};
pub use crate::{
gkms_signer::GKMSSigner, pk_signer::PrivateKeySigner, raw_ethereum_tx::TransactionParameters,
};

mod gkms_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 d265acd

Please sign in to comment.