Skip to content
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
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/agglayer-gcp-kms/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ async-trait.workspace = true
eyre.workspace = true
gcloud-sdk.workspace = true
serde = { workspace = true, features = ["derive"] }
thiserror.workspace = true
tracing.workspace = true

agglayer-config.workspace = true
Expand Down
19 changes: 0 additions & 19 deletions crates/agglayer-gcp-kms/src/error.rs

This file was deleted.

23 changes: 7 additions & 16 deletions crates/agglayer-gcp-kms/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@

use agglayer_config::GcpKmsConfig;
use alloy::signers::gcp::{GcpKeyRingRef, GcpSigner, KeySpecifier};
use eyre::Context as _;
use gcloud_sdk::{
google::cloud::kms::v1::key_management_service_client::KeyManagementServiceClient, GoogleApi,
};
use serde::Deserialize;

pub(crate) mod error;
pub(crate) mod params;
pub(crate) mod signer;

pub use error::Error;
pub use signer::KmsSigner;
use tracing::debug;

Expand Down Expand Up @@ -59,15 +58,15 @@ impl KMS {
///
/// # Returns
///
/// * `Result<KmsSigner, Error>` - A result containing the KmsSigner on
/// success, or an Error on failure.
/// * `eyre::Result<KmsSigner>` - A result containing the KmsSigner on
/// success, or an error report on failure.
///
/// # Errors
///
/// This function will return an error if it fails to retrieve the required
/// environment variables or if there is an issue creating the GCP KMS
/// signer.
pub async fn gcp_kms_signers(&self) -> Result<KmsSigners, Error> {
pub async fn gcp_kms_signers(&self) -> eyre::Result<KmsSigners> {
let params = KMSParameters::try_from(&self.config)?;
debug!("Using GCP KMS with parameters: {:?}", params);

Expand All @@ -89,19 +88,13 @@ impl KMS {
let client =
GoogleApi::from_function(KeyManagementServiceClient::new, GOOGLE_API_URL, None)
.await
.map_err(|e| {
Error::KmsError(
eyre::Error::new(e).wrap_err("Unable to create GoogleApiClient"),
)
})?;
.wrap_err("Unable to create GoogleApiClient")?;

// Use GcpSigner::new with the proper client type
let pp_settlement_gcp_signer =
GcpSigner::new(client.clone(), pp_settlement_specifier, Some(self.chain_id))
.await
.map_err(|e| {
Error::KmsError(eyre::Error::new(e).wrap_err("Unable to create GcpSigner"))
})?;
.wrap_err("Unable to create PP settlement GcpSigner")?;

let is_the_same_key = params.key_name_pp_settlement == params.key_name_tx_settlement
&& params.key_version_pp_settlement == params.key_version_tx_settlement;
Expand All @@ -112,9 +105,7 @@ impl KMS {
Some(
GcpSigner::new(client, tx_settlement_specifier, Some(self.chain_id))
.await
.map_err(|e| {
Error::KmsError(eyre::Error::new(e).wrap_err("Unable to create GcpSigner"))
})?,
.wrap_err("Unable to create tx settlement GcpSigner")?,
)
};

Expand Down
77 changes: 68 additions & 9 deletions crates/agglayer-gcp-kms/src/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
use std::str::FromStr;

use agglayer_config::GcpKmsConfig;
use eyre::eyre;
use tracing::warn;

pub use crate::error::Error;

const GOOGLE_PROJECT_ID: &str = "GOOGLE_PROJECT_ID";
const GOOGLE_LOCATION: &str = "GOOGLE_LOCATION";
const GOOGLE_KEYRING: &str = "GOOGLE_KEYRING";
Expand Down Expand Up @@ -42,32 +41,34 @@ fn from_env_or_conf<T>(
env_key: &str,
env_key_fallback: Option<&str>,
config_value: &Option<T>,
) -> Result<T, Error>
) -> eyre::Result<T>
where
T: FromStr + Clone,
{
if let Ok(value) = std::env::var(env_key) {
return value
.parse()
.map_err(|_| Error::KmsConfig(env_key.to_string()));
.map_err(|_| eyre!("KMS configuration error: invalid value for key or env {env_key}"));
} else if let Some(env_key_fallback) = env_key_fallback {
if let Ok(value) = std::env::var(env_key_fallback) {
warn!("Fallback KMS env:{env_key}=>env:{env_key_fallback}");
return value
.parse()
.map_err(|_| Error::KmsConfig(env_key_fallback.to_string()));
return value.parse().map_err(|_| {
eyre!("KMS configuration error: invalid value for key or env {env_key_fallback}")
});
}
}

if let Some(config_value) = config_value {
return Ok(config_value.clone());
}

Err(Error::KmsConfig(env_key.to_string()))
Err(eyre!(
"KMS configuration error: missing key or env {env_key}"
))
}

impl TryFrom<&GcpKmsConfig> for KMSParameters {
type Error = Error;
type Error = eyre::Report;
fn try_from(config: &GcpKmsConfig) -> Result<Self, Self::Error> {
// Get configuration values from environment variables or config
let project_id = from_env_or_conf(GOOGLE_PROJECT_ID, None, &config.project_id)?;
Expand Down Expand Up @@ -223,4 +224,62 @@ mod test {
assert_eq!(params.key_name_tx_settlement, "conf_key_name_tx");
assert_eq!(params.key_version_tx_settlement, 2);
}

#[test]
#[serial]
fn missing_required_value_reports_kms_context() {
let _raii = set_env(false, false);
let config = GcpKmsConfig {
project_id: None,
location: None,
keyring: None,
pp_settlement_key_name: None,
pp_settlement_key_version: None,
tx_settlement_key_name: None,
tx_settlement_key_version: None,
};

let error = crate::KMSParameters::try_from(&config).unwrap_err();

assert_eq!(
error.to_string(),
"KMS configuration error: missing key or env GOOGLE_PROJECT_ID"
);
}

#[test]
#[serial]
fn invalid_primary_env_value_reports_kms_context() {
let _raii = (
set_env(false, false),
SetEnvGuard::new("GOOGLE_KEY_VERSION_PP_SETTLEMENT", Some("invalid")),
);
let config = config();

let error = crate::KMSParameters::try_from(&config).unwrap_err();

assert_eq!(
error.to_string(),
"KMS configuration error: invalid value for key or env \
GOOGLE_KEY_VERSION_PP_SETTLEMENT"
);
}

#[test]
#[serial]
fn invalid_fallback_env_value_reports_kms_context() {
let _raii = (
set_env(false, false),
SetEnvGuard::new("GOOGLE_KEY_VERSION", Some("invalid")),
);
let mut config = config();
config.pp_settlement_key_version = None;

let error = crate::KMSParameters::try_from(&config).unwrap_err();

assert_eq!(
error.to_string(),
"KMS configuration error: invalid value for key or env GOOGLE_KEY_VERSION"
);
}
}
13 changes: 5 additions & 8 deletions crates/agglayer-gcp-kms/src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ use alloy::{
};
use alloy_primitives::{Address, ChainId, Signature, B256};
use async_trait::async_trait;

use crate::Error;
use eyre::Context as _;

/// A wrapper around [`GcpSigner`] providing additional functionality
/// for signing messages and transactions.
Expand All @@ -29,24 +28,22 @@ impl KmsSigner {
pub async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
&self,
message: S,
) -> Result<Signature, Error> {
) -> eyre::Result<Signature> {
self.signer
.sign_message(message.as_ref())
.await
.map_err(|e| Error::KmsError(eyre::Error::new(e).wrap_err("Unable to sign message")))
.wrap_err("Unable to sign message")
}

/// Signs a transaction using the internal signer, this method can fail if
/// the signer fails to create the digest.
pub async fn sign_transaction(&self, tx: &TypedTransaction) -> Result<Signature, Error> {
pub async fn sign_transaction(&self, tx: &TypedTransaction) -> eyre::Result<Signature> {
// Convert the TypedTransaction to a mutable dyn SignableTransaction
let mut tx_clone = tx.clone();
self.signer
.sign_transaction(&mut tx_clone)
.await
.map_err(|e| {
Error::KmsError(eyre::Error::new(e).wrap_err("Unable to sign transaction"))
})
.wrap_err("Unable to sign transaction")
}

/// Returns the address associated with the signer.
Expand Down
2 changes: 1 addition & 1 deletion crates/agglayer-signer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2021"

[dependencies]
async-trait.workspace = true
thiserror.workspace = true
eyre.workspace = true

alloy.workspace = true
alloy-primitives.workspace = true
Expand Down
19 changes: 0 additions & 19 deletions crates/agglayer-signer/src/error.rs

This file was deleted.

29 changes: 15 additions & 14 deletions crates/agglayer-signer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ use alloy::{
};
use alloy_primitives::{Address, ChainId, Signature, B256};
use async_trait::async_trait;

mod error;

pub use error::Error;
use eyre::{eyre, Context as _};

/// A an alloy [`Signer`] that can house either a local keystore or a KMS
/// signer.
Expand All @@ -33,19 +30,23 @@ pub enum ConfiguredSigner {

impl ConfiguredSigner {
/// Decrypt the first local keystore specified in the configuration.
#[allow(clippy::result_large_err)]
pub(crate) fn local_wallet(
chain_id: u64,
local: &LocalConfig,
) -> Result<(PrivateKeySigner, Option<PrivateKeySigner>), Error> {
let pk1 = local.private_keys.first().ok_or(Error::NoPk)?;
let signer1 = PrivateKeySigner::decrypt_keystore(&pk1.path, &pk1.password)?
) -> eyre::Result<(PrivateKeySigner, Option<PrivateKeySigner>)> {
let pk1 = local
.private_keys
.first()
.ok_or_else(|| eyre!("no private keys specified in the configuration"))?;
let signer1 = PrivateKeySigner::decrypt_keystore(&pk1.path, &pk1.password)
.wrap_err("local signer error")?
.with_chain_id(Some(chain_id));

let mut signer2 = None;
if let Some(pk2) = local.private_keys.get(1) {
signer2 = Some(
PrivateKeySigner::decrypt_keystore(&pk2.path, &pk2.password)?
PrivateKeySigner::decrypt_keystore(&pk2.path, &pk2.password)
.wrap_err("local signer error")?
.with_chain_id(Some(chain_id)),
)
};
Expand Down Expand Up @@ -81,7 +82,7 @@ pub struct ConfiguredSigners {

impl ConfiguredSigners {
/// Get either a local wallet or GCP KMS signer based on the configuration.
pub async fn new(config: &Config) -> Result<Self, Error> {
pub async fn new(config: &Config) -> eyre::Result<Self> {
match &config.auth {
AuthConfig::GcpKms(ref kms) => {
let kms = KMS::new(config.l1.chain_id, kms.clone());
Expand Down Expand Up @@ -189,25 +190,25 @@ impl ConfiguredSigner {
async fn sign_transaction_local(
wallet: &PrivateKeySigner,
tx: &TypedTransaction,
) -> Result<Signature, Error> {
) -> eyre::Result<Signature> {
// Convert the TypedTransaction to a mutable dyn SignableTransaction
let mut tx_clone = tx.clone();
wallet
.sign_transaction(&mut tx_clone)
.await
.map_err(Error::Signer)
.wrap_err("signer error")
}

/// Signs a transaction using the appropriate signer.
///
/// This method provides transaction signing functionality that delegates
/// to the underlying signer implementation.
#[inline]
pub async fn sign_transaction_typed(&self, tx: &TypedTransaction) -> Result<Signature, Error> {
pub async fn sign_transaction_typed(&self, tx: &TypedTransaction) -> eyre::Result<Signature> {
match self {
ConfiguredSigner::Local(wallet) => Self::sign_transaction_local(wallet, tx).await,
ConfiguredSigner::Kms(signer) => {
signer.sign_transaction(tx).await.map_err(Error::GcpKms)
signer.sign_transaction(tx).await.wrap_err("GcpKMS error")
}
}
}
Expand Down
Loading
Loading