diff --git a/CHANGELOG.md b/CHANGELOG.md index e41f824b4..de920d38f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added verification to ensure CARTESI_BLOCKCHAIN_ID matches the id returned from the Ethereum node +- Added support for CARTESI_AUTH_PRIVATE_KEY ## Changed diff --git a/cmd/cartesi-rollups-node/services.go b/cmd/cartesi-rollups-node/services.go index ad5395d2f..9464310cb 100644 --- a/cmd/cartesi-rollups-node/services.go +++ b/cmd/cartesi-rollups-node/services.go @@ -133,6 +133,9 @@ func newAuthorityClaimer() services.CommandService { s.Env = append(s.Env, fmt.Sprintf("AUTHORITY_CLAIMER_HTTP_SERVER_PORT=%v", getPort(portOffsetAuthorityClaimer))) switch auth := config.GetAuth().(type) { + case config.AuthPrivateKey: + s.Env = append(s.Env, + fmt.Sprintf("TX_SIGNING_PRIVATE_KEY=%v", auth.PrivateKey)) case config.AuthMnemonic: s.Env = append(s.Env, fmt.Sprintf("TX_SIGNING_MNEMONIC=%v", auth.Mnemonic)) diff --git a/internal/config/auth.go b/internal/config/auth.go index bd71dc6bc..5e03875a1 100644 --- a/internal/config/auth.go +++ b/internal/config/auth.go @@ -6,6 +6,11 @@ package config // Auth objects are used to sign transactions. type Auth any +// Allows signing through private keys. +type AuthPrivateKey struct { + PrivateKey string +} + // Allows signing through mnemonics. type AuthMnemonic struct { Mnemonic string diff --git a/internal/config/config.go b/internal/config/config.go index 815b0019c..00aa30a86 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -59,6 +59,11 @@ var ( // ------------------------------------------------------------------------------------------------ func GetAuth() Auth { + // if private key is coming from an environment variable + if privateKey, ok := getCartesiAuthPrivateKey(); ok { + return AuthPrivateKey{PrivateKey: privateKey} + } + // getting the (optional) account index index, _ := getCartesiAuthMnemonicAccountIndex() diff --git a/internal/config/generate/Config.toml b/internal/config/generate/Config.toml index 908e872fa..c71813108 100644 --- a/internal/config/generate/Config.toml +++ b/internal/config/generate/Config.toml @@ -170,6 +170,15 @@ description = """ When using mnemonics to sign transactions, the node will use this account index to generate the private key.""" +[auth.CARTESI_AUTH_PRIVATE_KEY] +go-type = "string" +export = false +redact = true +description = """ +The node will use this private key to sign transactions. + +Overrides `CARTESI_AUTH_MNEMONIC`, `CARTESI_AUTH_MNEMONIC_FILE` and `CARTESI_AUTH_AWS_KMS_*`.""" + [auth.CARTESI_AUTH_AWS_KMS_KEY_ID] go-type = "string" export = false diff --git a/internal/config/get.go b/internal/config/get.go index 593e1ecb5..ac40ccc54 100644 --- a/internal/config/get.go +++ b/internal/config/get.go @@ -24,6 +24,11 @@ func getCartesiAuthAwsKmsRegion() (string, bool) { return v, ok } +func getCartesiAuthPrivateKey() (string, bool) { + v, ok := getOptional("CARTESI_AUTH_PRIVATE_KEY", "", false, true, toString) + return v, ok +} + func getCartesiAuthMnemonic() (string, bool) { v, ok := getOptional("CARTESI_AUTH_MNEMONIC", "", false, true, toString) return v, ok diff --git a/offchain/authority-claimer/src/config/cli.rs b/offchain/authority-claimer/src/config/cli.rs index f0d71e469..d1f07dc04 100644 --- a/offchain/authority-claimer/src/config/cli.rs +++ b/offchain/authority-claimer/src/config/cli.rs @@ -91,6 +91,10 @@ impl TryFrom for AuthorityClaimerConfig { #[derive(Debug, Parser)] #[command(name = "tx_signing_config")] pub(crate) struct TxSigningCLIConfig { + /// Signer private key, overrides `tx_signing_mnemonic` , `tx_signing_mnemonic_file` and `tx_signing_aws_kms_*` + #[arg(long, env)] + tx_signing_private_key: Option, + /// Signer mnemonic, overrides `tx_signing_mnemonic_file` and `tx_signing_aws_kms_*` #[arg(long, env)] tx_signing_mnemonic: Option, @@ -117,7 +121,11 @@ impl TryFrom for TxSigningConfig { fn try_from(cli: TxSigningCLIConfig) -> Result { let account_index = cli.tx_signing_mnemonic_account_index; - if let Some(mnemonic) = cli.tx_signing_mnemonic { + if let Some(private_key) = cli.tx_signing_private_key { + Ok(TxSigningConfig::PrivateKey { + private_key: Redacted::new(private_key), + }) + } else if let Some(mnemonic) = cli.tx_signing_mnemonic { Ok(TxSigningConfig::Mnemonic { mnemonic: Redacted::new(mnemonic), account_index, diff --git a/offchain/authority-claimer/src/config/mod.rs b/offchain/authority-claimer/src/config/mod.rs index 25c171c52..21d1932b2 100644 --- a/offchain/authority-claimer/src/config/mod.rs +++ b/offchain/authority-claimer/src/config/mod.rs @@ -35,6 +35,10 @@ pub struct AuthorityClaimerConfig { #[derive(Debug, Clone)] pub enum TxSigningConfig { + PrivateKey { + private_key: Redacted, + }, + Mnemonic { mnemonic: Redacted, account_index: Option, diff --git a/offchain/authority-claimer/src/signer/signer.rs b/offchain/authority-claimer/src/signer/signer.rs index 8a9deeca0..a38333c93 100644 --- a/offchain/authority-claimer/src/signer/signer.rs +++ b/offchain/authority-claimer/src/signer/signer.rs @@ -43,6 +43,15 @@ impl ConditionalSigner { tx_signing_config: &TxSigningConfig, ) -> Result { match tx_signing_config.clone() { + TxSigningConfig::PrivateKey { private_key } => { + let wallet = private_key + .inner() + .as_str() + .parse::() + .context(LocalWalletSnafu)? + .with_chain_id(chain_id); + Ok(ConditionalSigner::LocalWallet(wallet)) + } TxSigningConfig::Mnemonic { mnemonic, account_index, @@ -162,8 +171,19 @@ mod tests { // -------------------------------------------------------------------------------------------- #[tokio::test] - async fn new_local_wallet_conditional_signer() { - let conditional_signer = local_wallet_conditional_signer().await; + async fn new_local_wallet_mnemonic_conditional_signer() { + let conditional_signer = + local_wallet_mnemonic_conditional_signer().await; + assert!(matches!( + conditional_signer, + ConditionalSigner::LocalWallet(_) + )); + } + + #[tokio::test] + async fn new_local_wallet_private_key_conditional_signer() { + let conditional_signer = + local_wallet_private_key_conditional_signer().await; assert!(matches!( conditional_signer, ConditionalSigner::LocalWallet(_) @@ -175,8 +195,19 @@ mod tests { // -------------------------------------------------------------------------------------------- #[tokio::test] - async fn sign_transaction_with_local_wallet_conditional_signer() { - let conditional_signer = local_wallet_conditional_signer().await; + async fn sign_transaction_with_mnemonic_local_wallet_conditional_signer() { + let conditional_signer = + local_wallet_mnemonic_conditional_signer().await; + let message = eip1559_message(); + let result = conditional_signer.sign_transaction(&message).await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn sign_transaction_with_private_key_local_wallet_conditional_signer() + { + let conditional_signer = + local_wallet_private_key_conditional_signer().await; let message = eip1559_message(); let result = conditional_signer.sign_transaction(&message).await; assert!(result.is_ok()); @@ -187,10 +218,12 @@ mod tests { // -------------------------------------------------------------------------------------------- const CHAIN_ID: u64 = 1; + const PRIVATE_KEY: &str = + "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; const MNEMONIC: &str = "indoor dish desk flag debris potato excuse depart ticket judge file exit"; - async fn local_wallet_conditional_signer() -> ConditionalSigner { + async fn local_wallet_mnemonic_conditional_signer() -> ConditionalSigner { let tx_signing_config = TxSigningConfig::Mnemonic { mnemonic: Redacted::new(MNEMONIC.to_string()), account_index: Some(1), @@ -200,6 +233,16 @@ mod tests { .unwrap() } + async fn local_wallet_private_key_conditional_signer() -> ConditionalSigner + { + let tx_signing_config = TxSigningConfig::PrivateKey { + private_key: Redacted::new(PRIVATE_KEY.to_string()), + }; + ConditionalSigner::new(CHAIN_ID, &tx_signing_config) + .await + .unwrap() + } + fn eip1559_message() -> TypedTransaction { TypedTransaction::Eip1559( Eip1559TransactionRequest::new()