diff --git a/Cargo.lock b/Cargo.lock index 538ebee04..d78282ea0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6372,7 +6372,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" dependencies = [ - "ethereum-types 0.14.1", + "ethereum-types", "hex", "once_cell", "regex", @@ -6383,19 +6383,6 @@ dependencies = [ "uint", ] -[[package]] -name = "ethbloom" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb684ac8fa8f6c5759f788862bb22ec6fe3cb392f6bfd08e3c64b603661e3f8" -dependencies = [ - "crunchy", - "fixed-hash 0.7.0", - "impl-rlp", - "impl-serde 0.3.2", - "tiny-keccak", -] - [[package]] name = "ethbloom" version = "0.13.0" @@ -6409,27 +6396,13 @@ dependencies = [ "tiny-keccak", ] -[[package]] -name = "ethereum-types" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64b5df66a228d85e4b17e5d6c6aa43b0310898ffe8a85988c4c032357aaabfd" -dependencies = [ - "ethbloom 0.11.1", - "fixed-hash 0.7.0", - "impl-rlp", - "impl-serde 0.3.2", - "primitive-types 0.9.1", - "uint", -] - [[package]] name = "ethereum-types" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ - "ethbloom 0.13.0", + "ethbloom", "fixed-hash 0.8.0", "impl-rlp", "impl-serde 0.4.0", @@ -9211,6 +9184,9 @@ dependencies = [ "futures", "godfig", "mcr-settlement-config", + "movement-signer", + "movement-signer-loader", + "movement-signing-eth", "movement-types", "serde", "serde_json", @@ -9228,6 +9204,7 @@ dependencies = [ "alloy", "anyhow", "godfig", + "movement-signer-loader", "serde", ] @@ -9272,6 +9249,7 @@ name = "mcr-settlement-setup" version = "0.0.2" dependencies = [ "alloy", + "alloy-network", "alloy-primitives 0.7.7", "anyhow", "commander", @@ -9280,6 +9258,10 @@ dependencies = [ "k256", "mcr-settlement-client", "mcr-settlement-config", + "movement-signer", + "movement-signer-aws-kms", + "movement-signer-loader", + "movement-signing-eth", "rand 0.7.3", "serde_json", "tokio", @@ -10642,12 +10624,21 @@ dependencies = [ "k256", "movement-signer", "rand 0.8.5", + "sha3 0.10.8", + "tokio", ] [[package]] name = "movement-signer-test" version = "0.0.2" dependencies = [ + "alloy", + "alloy-consensus", + "alloy-network", + "alloy-primitives 0.7.7", + "alloy-signer", + "alloy-signer-aws", + "alloy-transport-http", "anyhow", "aptos-crypto", "aptos-types", @@ -10657,8 +10648,12 @@ dependencies = [ "maptos-dof-execution", "maptos-execution-util", "movement-signer", + "movement-signer-aws-kms", + "movement-signer-local", "movement-signing-aptos", + "movement-signing-eth", "rand 0.8.5", + "sha3 0.10.8", "tempfile", "tokio", ] @@ -10677,25 +10672,15 @@ dependencies = [ name = "movement-signing-eth" version = "0.0.2" dependencies = [ - "alloy", "alloy-consensus", "alloy-network", "alloy-primitives 0.7.7", "alloy-signer", - "alloy-signer-aws", "alloy-transport-http", - "anyhow", "async-trait", - "aws-config", - "aws-sdk-kms", - "ethereum-types 0.11.0", - "hex", "k256", - "keccak-hash", "movement-signer", "movement-signer-aws-kms", - "sha3 0.10.8", - "tokio", ] [[package]] @@ -12091,19 +12076,6 @@ dependencies = [ "elliptic-curve 0.13.8", ] -[[package]] -name = "primitive-types" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06345ee39fbccfb06ab45f3a1a5798d9dafa04cb8921a76d227040003a234b0e" -dependencies = [ - "fixed-hash 0.7.0", - "impl-codec 0.5.1", - "impl-rlp", - "impl-serde 0.3.2", - "uint", -] - [[package]] name = "primitive-types" version = "0.10.1" diff --git a/Cargo.toml b/Cargo.toml index 551b4f3f8..4edd1cfe5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,6 +130,7 @@ movement-signer-hashicorp-vault = { path = "util/signing/providers/hashicorp-vau movement-signer-local = { path = "util/signing/providers/local" } movement-signer-loader = { path = "util/signing/util/loader" } movement-signing-aptos = { path = "util/signing/integrations/aptos" } +movement-signing-eth = { path = "util/signing/integrations/eth" } ## vault vaultrs = { version = "0.7.3" } diff --git a/protocol-units/da/movement/celestia/light-node-verifier/src/permissioned_signers/mod.rs b/protocol-units/da/movement/celestia/light-node-verifier/src/permissioned_signers/mod.rs index 0efeae8c7..6a5c68292 100644 --- a/protocol-units/da/movement/celestia/light-node-verifier/src/permissioned_signers/mod.rs +++ b/protocol-units/da/movement/celestia/light-node-verifier/src/permissioned_signers/mod.rs @@ -6,7 +6,7 @@ use celestia_rpc::Client; use celestia_types::nmt::Namespace; use celestia_types::Blob as CelestiaBlob; use movement_celestia_da_util::ir_blob::IntermediateBlobRepresentation; -use movement_signer::{cryptography::Curve, Verify}; +use movement_signer::{cryptography::Curve, Digester, Verify}; use std::sync::Arc; /// A verifier of Celestia blobs for permissioned signers @@ -44,7 +44,7 @@ where #[tonic::async_trait] impl VerifierOperations for Verifier where - C: Curve + Verify + Send + Sync, + C: Curve + Verify + Digester + Send + Sync, { async fn verify( &self, diff --git a/protocol-units/da/movement/celestia/light-node-verifier/src/signed/mod.rs b/protocol-units/da/movement/celestia/light-node-verifier/src/signed/mod.rs index 8c7dc2ec2..8bba6c629 100644 --- a/protocol-units/da/movement/celestia/light-node-verifier/src/signed/mod.rs +++ b/protocol-units/da/movement/celestia/light-node-verifier/src/signed/mod.rs @@ -1,6 +1,6 @@ use crate::{Error, Verified, VerifierOperations}; use movement_celestia_da_util::ir_blob::IntermediateBlobRepresentation; -use movement_signer::{cryptography::Curve, Verify}; +use movement_signer::{cryptography::Curve, Digester, Verify}; use std::collections::HashSet; use tracing::info; @@ -26,7 +26,7 @@ where impl VerifierOperations for Verifier where - C: Curve + Verify + Send + Sync, + C: Curve + Verify + Digester + Send + Sync, { async fn verify( &self, @@ -74,7 +74,7 @@ where impl VerifierOperations for InKnownSignersVerifier where - C: Curve + Verify + Send + Sync, + C: Curve + Verify + Digester + Send + Sync, { async fn verify( &self, diff --git a/protocol-units/da/movement/celestia/util/src/ir_blob.rs b/protocol-units/da/movement/celestia/util/src/ir_blob.rs index 7b24c8789..c5eafd5f7 100644 --- a/protocol-units/da/movement/celestia/util/src/ir_blob.rs +++ b/protocol-units/da/movement/celestia/util/src/ir_blob.rs @@ -42,7 +42,7 @@ impl InnerSignedBlobV1Data { } /// Computes the id of InnerSignedBlobV1Data - pub fn compute_id(&self) -> Result + pub fn compute_id(&self) -> Result where C: Curve + Digester, { @@ -59,7 +59,7 @@ impl InnerSignedBlobV1Data { O: Signing, C: Curve + Digester, { - let id = self.compute_id::()?; + let id = self.compute_id::()?; let signature = signer.inner().sign(&id.as_slice()).await?.to_bytes(); let signer = signer.inner().public_key().await?.to_bytes(); @@ -78,12 +78,13 @@ pub struct InnerSignedBlobV1 { impl InnerSignedBlobV1 { pub fn try_verify(&self) -> Result<(), anyhow::Error> where - C: Curve + Verify, + C: Curve + Digester + Verify, { let public_key = C::PublicKey::try_from_bytes(self.signer.as_slice())?; let signature = C::Signature::try_from_bytes(self.signature.as_slice())?; + let id = self.data.compute_id::()?; - if !C::verify(self.data.blob.as_slice(), &signature, &public_key)? { + if !C::verify(id.as_slice(), &signature, &public_key)? { return Err(anyhow::anyhow!("signature verification failed"))?; } @@ -139,7 +140,7 @@ impl IntermediateBlobRepresentation { pub fn verify_signature(&self) -> Result<(), anyhow::Error> where - C: Curve + Verify, + C: Curve + Digester + Verify, { match self { IntermediateBlobRepresentation::SignedV1(inner) => inner.try_verify::(), diff --git a/protocol-units/settlement/mcr/client/Cargo.toml b/protocol-units/settlement/mcr/client/Cargo.toml index 200bd24ff..1141101c2 100644 --- a/protocol-units/settlement/mcr/client/Cargo.toml +++ b/protocol-units/settlement/mcr/client/Cargo.toml @@ -16,6 +16,9 @@ rust-version = { workspace = true } [dependencies] mcr-settlement-config = { workspace = true } +movement-signer-loader = { workspace = true } +movement-signer = { workspace = true } +movement-signing-eth = { workspace = true } alloy = { workspace = true, features = [ "node-bindings", diff --git a/protocol-units/settlement/mcr/client/src/eth_client.rs b/protocol-units/settlement/mcr/client/src/eth_client.rs index 86b502a19..af1433cf8 100644 --- a/protocol-units/settlement/mcr/client/src/eth_client.rs +++ b/protocol-units/settlement/mcr/client/src/eth_client.rs @@ -11,7 +11,7 @@ use alloy::providers::fillers::NonceFiller; use alloy::providers::fillers::WalletFiller; use alloy::providers::{Provider, ProviderBuilder, RootProvider}; use alloy::pubsub::PubSubFrontend; -use alloy::signers::local::PrivateKeySigner; +use alloy::signers::Signer; use alloy_network::Ethereum; use alloy_network::EthereumWallet; use alloy_primitives::Address; @@ -21,6 +21,9 @@ use alloy_transport::BoxTransport; use alloy_transport_ws::WsConnect; use anyhow::Context; use mcr_settlement_config::Config; +use movement_signer::cryptography::secp256k1::Secp256k1; +use movement_signer_loader::identifiers::Load; +use movement_signing_eth::HsmSigner; use movement_types::block::{BlockCommitment, Commitment, Id}; use serde_json::Value as JsonValue; use std::array::TryFromSliceError; @@ -100,19 +103,12 @@ impl > { pub async fn build_with_config(config: &Config) -> Result { - let signer_private_key = match &config.deploy { - Some(deployment_config) => { - info!("Using deployment config for signer private key"); - deployment_config.mcr_deployment_account_private_key.clone() - } - None => { - info!("Using settlement config for signer private key"); - config.settle.signer_private_key.clone() - } - }; - let signer = signer_private_key - .parse::() - .context("Failed to parse the private key for the MCR settlement client signer")?; + let signer_identifier: Box + Send> = + Box::new(config.settle.signer_identifier.clone()); + let signer_provider = signer_identifier.load().await?; + let signer = + HsmSigner::try_new(signer_provider, Some(config.eth_connection.eth_chain_id)).await?; + let signer_address = signer.address(); info!("Signer address: {}", signer_address); let contract_address = config diff --git a/protocol-units/settlement/mcr/config/Cargo.toml b/protocol-units/settlement/mcr/config/Cargo.toml index a45802f06..a4471861c 100644 --- a/protocol-units/settlement/mcr/config/Cargo.toml +++ b/protocol-units/settlement/mcr/config/Cargo.toml @@ -16,5 +16,7 @@ alloy = { workspace = true } godfig = { workspace = true } anyhow = { workspace = true } +movement-signer-loader = { workspace = true } + [lints] workspace = true diff --git a/protocol-units/settlement/mcr/config/src/common/deploy.rs b/protocol-units/settlement/mcr/config/src/common/deploy.rs index 213637ca2..b937bcea4 100644 --- a/protocol-units/settlement/mcr/config/src/common/deploy.rs +++ b/protocol-units/settlement/mcr/config/src/common/deploy.rs @@ -6,9 +6,8 @@ use serde::{Deserialize, Serialize}; pub struct Config { #[serde(default = "mcr_deployment_working_directory")] pub mcr_deployment_working_directory: String, - - #[serde(default = "mcr_deployment_account_private_key")] - pub mcr_deployment_account_private_key: String, + #[serde(default = "mcr_local_anvil_account_private_key")] + pub mcr_local_anvil_account_private_key: String, } env_short_default!( @@ -18,7 +17,7 @@ env_short_default!( ); env_short_default!( - mcr_deployment_account_private_key, + mcr_local_anvil_account_private_key, String, PrivateKeySigner::random().to_bytes().to_string() ); @@ -43,7 +42,7 @@ impl Default for Config { fn default() -> Self { Config { mcr_deployment_working_directory: mcr_deployment_working_directory(), - mcr_deployment_account_private_key: mcr_deployment_account_private_key(), + mcr_local_anvil_account_private_key: mcr_local_anvil_account_private_key(), } } } diff --git a/protocol-units/settlement/mcr/config/src/common/settlement.rs b/protocol-units/settlement/mcr/config/src/common/settlement.rs index 2122222e2..d6caaeb48 100644 --- a/protocol-units/settlement/mcr/config/src/common/settlement.rs +++ b/protocol-units/settlement/mcr/config/src/common/settlement.rs @@ -1,5 +1,6 @@ use alloy::signers::local::PrivateKeySigner; use godfig::env_default; +use movement_signer_loader::identifiers::{local::Local, SignerIdentifier}; use serde::{Deserialize, Serialize}; use std::env; @@ -9,8 +10,8 @@ const DEFAULT_MCR_CONTRACT_ADDRESS: &str = "0x5fc8d32690cc91d4c39d9d3abcbd16989f pub struct Config { #[serde(default = "default_should_settle")] pub should_settle: bool, - #[serde(default = "default_signer_private_key")] - pub signer_private_key: String, + #[serde(default = "default_signer_identifier")] + pub signer_identifier: SignerIdentifier, #[serde(default = "default_mcr_contract_address")] pub mcr_contract_address: String, #[serde(default = "default_settlement_super_block_size")] @@ -19,10 +20,11 @@ pub struct Config { pub settlement_admin_mode: bool, } -pub fn default_signer_private_key() -> String { +pub fn default_signer_identifier() -> SignerIdentifier { let random_wallet = PrivateKeySigner::random(); - let random_wallet_string = random_wallet.to_bytes().to_string(); - env::var("ETH_SIGNER_PRIVATE_KEY").unwrap_or(random_wallet_string) + let private_key_hex_bytes = random_wallet.to_bytes().to_string(); + let signer_identifier = SignerIdentifier::Local(Local { private_key_hex_bytes }); + signer_identifier } env_default!( @@ -44,7 +46,7 @@ impl Default for Config { fn default() -> Self { Config { should_settle: default_should_settle(), - signer_private_key: default_signer_private_key(), + signer_identifier: default_signer_identifier(), mcr_contract_address: default_mcr_contract_address(), settlement_admin_mode: default_settlement_admin_mode(), settlement_super_block_size: default_settlement_super_block_size(), diff --git a/protocol-units/settlement/mcr/setup/Cargo.toml b/protocol-units/settlement/mcr/setup/Cargo.toml index f280635e2..da1149cab 100644 --- a/protocol-units/settlement/mcr/setup/Cargo.toml +++ b/protocol-units/settlement/mcr/setup/Cargo.toml @@ -13,10 +13,15 @@ rust-version.workspace = true [dependencies] mcr-settlement-config = { workspace = true } mcr-settlement-client = { workspace = true } +movement-signer-loader = { workspace = true } +movement-signer = { workspace = true } +movement-signing-eth = { workspace = true } +movement-signer-aws-kms = { workspace = true } dot-movement = { workspace = true } commander = { workspace = true } alloy = { workspace = true } alloy-primitives = { workspace = true } +alloy-network = { workspace = true } anyhow = { workspace = true } k256 = { workspace = true } rand = { workspace = true } diff --git a/protocol-units/settlement/mcr/setup/src/deploy/mod.rs b/protocol-units/settlement/mcr/setup/src/deploy/mod.rs index 59a1b26fb..c16019598 100644 --- a/protocol-units/settlement/mcr/setup/src/deploy/mod.rs +++ b/protocol-units/settlement/mcr/setup/src/deploy/mod.rs @@ -1,10 +1,21 @@ -use alloy::signers::local::PrivateKeySigner; +use alloy::providers::Provider; +use alloy::providers::ProviderBuilder; +use alloy::signers::Signer; +use alloy_network::EthereumWallet; +use alloy_network::TransactionBuilder; +use alloy_primitives::U256; use anyhow::anyhow; use anyhow::Context; use commander::run_command; use dot_movement::DotMovement; +use mcr_settlement_client::eth_client::MCR; use mcr_settlement_config::{common, Config}; +use movement_signer::cryptography::secp256k1::Secp256k1; +use movement_signer_aws_kms::hsm::AwsKms; +use movement_signer_loader::identifiers::SignerIdentifier; +use movement_signing_eth::HsmSigner; use serde_json::Value; +use std::str::FromStr; use tracing::info; /// The local setup strategy for MCR settlement @@ -34,8 +45,7 @@ impl Deploy { ) -> Result { // enforce config.deploy = deploy config.deploy = Some(deploy.clone()); - - let wallet: PrivateKeySigner = deploy.mcr_deployment_account_private_key.parse()?; + let wallet: PrivateKeySigner = deploy.mcr_local_anvil_account_private_key.parse()?; // todo: make sure this shows up in the docker container as well let mut solidity_path = std::env::current_dir()?; @@ -73,7 +83,7 @@ impl Deploy { "--rpc-url", &config.eth_rpc_connection_url(), "--private-key", - &deploy.mcr_deployment_account_private_key, + &deploy.mcr_local_anvil_account_private_key, "--legacy", "--use", &solc_path, @@ -138,19 +148,6 @@ impl Deploy { s.to_owned() })?; - // generate random well-known accounts and addresses - // let mut well_known_account_private_keys = - // if let Some(existing_testing_config) = config.testing.clone() { - // existing_testing_config.well_known_account_private_keys - // } else { - // let mut keys = Vec::new(); - // for _ in 0..10 { - // let wallet = PrivateKeySigner::random(); - // keys.push(wallet.to_bytes().to_string()); - // } - // keys - // }; - info!("setting up MCR Ethereum client move_token_address: {move_token_address}"); info!( "setting up MCR Ethereum client movement_staking_address: {movement_staking_address}" @@ -159,11 +156,54 @@ impl Deploy { if let Some(testing) = &mut config.testing { testing.mcr_testing_admin_account_private_key = - deploy.mcr_deployment_account_private_key.clone(); + deploy.mcr_local_anvil_account_private_key.clone(); testing.move_token_contract_address = move_token_address; testing.movement_staking_contract_address = movement_staking_address; } + use alloy::rpc::types::TransactionRequest; + use alloy::signers::local::PrivateKeySigner; + + // Manage signer contract role update + // For Local signer the deployment account is used so no need to update. + // For Was signer the AWS key account must be declared has a TrustedAttester. + match config.settle.signer_identifier { + SignerIdentifier::Local(_) => (), + SignerIdentifier::AwsKms(ref aws) => { + let key_id = aws.key.key_name(); + let aws: AwsKms = + AwsKms::try_from_env_with_key(key_id.to_string()).await?; + let signer = + HsmSigner::try_new(aws, Some(config.eth_connection.eth_chain_id)).await?; + let address = signer.address(); + + let rpc_url = config.eth_rpc_connection_url(); + let admin = + PrivateKeySigner::from_str(&deploy.mcr_local_anvil_account_private_key)?; + let admin_address = admin.address(); + let admin_provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(EthereumWallet::new(admin)) + .on_builtin(&rpc_url.to_string()) + .await?; + // Grant Attester role to AWS account. + let mcr_contract = MCR::new(mcr_address.parse()?, &admin_provider); + let grant_attester_call = + mcr_contract.grantTrustedAttester(address).from(admin_address); + grant_attester_call.send().await?.get_receipt().await?; + + //transfer some fund to the AWS key account + let tx = TransactionRequest::default() + .with_to(address) + .with_value(U256::from(100_000_000_000_000_000u64)); + admin_provider.send_transaction(tx).await?.get_receipt().await?; + + let balance = admin_provider.get_balance(address).await?; + info!("setting up AWS Account:{address} granted Attester role of MCR contract with balance: {balance}"); + } + SignerIdentifier::HashiCorpVault(_) => (), + } + config.settle.mcr_contract_address = mcr_address; Ok(config) diff --git a/protocol-units/settlement/mcr/setup/src/local/mod.rs b/protocol-units/settlement/mcr/setup/src/local/mod.rs index fb6cf0c79..a48957da6 100644 --- a/protocol-units/settlement/mcr/setup/src/local/mod.rs +++ b/protocol-units/settlement/mcr/setup/src/local/mod.rs @@ -2,7 +2,9 @@ use anyhow::{anyhow, Context}; use commander::run_command; use dot_movement::DotMovement; use mcr_settlement_config::Config; - +use movement_signer::key::Key; +use movement_signer_loader::identifiers::aws_kms::AwsKms; +use movement_signer_loader::identifiers::{local::Local as SignerLocal, SignerIdentifier}; use tracing::info; /// The local setup strategy for MCR settlement @@ -97,7 +99,7 @@ impl Local { mcr_settlement_client::eth_client::read_anvil_json_file_addresses(&*anvil_path) .context("Failed to read Anvil addresses")?; if let Some(deploy) = &mut config.deploy { - deploy.mcr_deployment_account_private_key = anvil_addresses + deploy.mcr_local_anvil_account_private_key = anvil_addresses .get(0) .ok_or(anyhow!("Failed to get Anvil address"))? .private_key @@ -107,7 +109,31 @@ impl Local { .ok_or(anyhow!("Failed to get Anvil address"))? .address .clone(); - info!("Deployer address: {}", deployer_address) + info!("Deployer address: {}", deployer_address); + + // Detect if we execute with AWS from AWS_KMS_KEY_ID env var or not. + config.settle.signer_identifier = match std::env::var("AWS_KMS_KEY_ID") { + Ok(key_id) => { + info!("Use AWS Signer with key_id: {key_id}"); + //For Aws set identifier to AWS and key_id + SignerIdentifier::AwsKms(AwsKms { + key: Key::try_from_canonical_string(&format!( + "movement/dev/full_node/mcr_settlement/signer/{}/0", + key_id + )) + .map_err(|err| anyhow::anyhow!(err))?, + }) + } + Err(_) => { + // Createa new random key to sign settlement. + let private_key_hex = deploy.mcr_local_anvil_account_private_key.to_string(); + //remove the Ox at the beginning + let private_key_hex_bytes = (&private_key_hex[2..]).to_string(); + + info!("Use Local Signer."); + SignerIdentifier::Local(SignerLocal { private_key_hex_bytes }) + } + }; } if let Some(testing) = &mut config.testing { // Remove the old one if the config was existing. diff --git a/util/signing/integrations/eth/Cargo.toml b/util/signing/integrations/eth/Cargo.toml index c19fc17d5..a3adeb7f2 100644 --- a/util/signing/integrations/eth/Cargo.toml +++ b/util/signing/integrations/eth/Cargo.toml @@ -22,18 +22,5 @@ alloy-consensus.workspace = true alloy-transport-http = { workspace = true, features = ["reqwest-rustls-tls"] } k256 = "0.13.4" -[dev-dependencies] -aws-sdk-kms = { workspace = true } -anyhow = { workspace = true } -tokio = { workspace = true } -alloy.workspace = true -alloy-signer-aws = { git = "https://github.com/alloy-rs/alloy.git", rev = "83343b172585fe4e040fb104b4d1421f58cbf9a2" } -aws-config = { workspace = true } - -ethereum-types = "0.11" -keccak-hash = "0.10" -hex = "0.4" -sha3 = "0.10.8" - [lints] workspace = true diff --git a/util/signing/integrations/eth/tests/aws_test.rs b/util/signing/integrations/eth/tests/aws_test.rs deleted file mode 100644 index 7da73d961..000000000 --- a/util/signing/integrations/eth/tests/aws_test.rs +++ /dev/null @@ -1,101 +0,0 @@ -use alloy::node_bindings::Anvil; -use alloy::providers::{Provider, ProviderBuilder}; -use alloy::rpc::types::TransactionRequest; -use alloy::signers::local::PrivateKeySigner; -use alloy_network::EthereumWallet; -use alloy_network::TransactionBuilder; -use alloy_network::TxSigner; -use alloy_primitives::U256; -use movement_signer::cryptography::secp256k1::Secp256k1; -use movement_signer::Signing; -use movement_signer::Verify; -use movement_signer_aws_kms::hsm::AwsKms; -use movement_signing_eth::HsmSigner; -use sha3::{Digest, Keccak256}; -use std::env; - -#[tokio::test] -async fn basic_signing_verify() -> Result<(), anyhow::Error> { - let message = b"Hello, world!"; - let digest: [u8; 32] = Keccak256::new_with_prefix(&message).finalize().into(); - let key_id = env::var("AWS_KEY_ID").expect("AWS_KEY_ID not set"); - let aws: AwsKms = AwsKms::try_from_env_with_key(key_id).await?; - let public_key = aws.public_key().await?; - let signature = aws.sign(&digest).await?; - - assert!(Secp256k1::verify(&digest, &signature, &public_key)?); - Ok(()) -} - -#[tokio::test] -async fn test_aws_kms_send_tx() -> Result<(), anyhow::Error> { - // Start Anvil - let anvil = Anvil::new().port(8545u16).arg("-vvvvv").spawn(); - let rpc_url = anvil.endpoint_url(); - let chain_id = anvil.chain_id(); - - // Use AWS KMS - let _access_key = env::var("AWS_ACCESS_KEY").expect("AWS_ACCESS_KEY not set"); - let _secret_key = env::var("AWS_SECRET_KEY").expect("AWS_SECRET_KEY not set"); - let key_id = env::var("AWS_KEY_ID").expect("AWS_KEY_ID not set"); - - println!("key_id:{key_id}"); - - let aws: AwsKms = AwsKms::try_from_env_with_key(key_id).await?; - let signer = HsmSigner::try_new(aws, Some(chain_id)).await?; - let address = signer.address(); - println!("DEEEEB Key address:{}", address); - - let key_provider = ProviderBuilder::new() - .with_recommended_fillers() - .wallet(EthereumWallet::new(signer)) - .on_builtin(&rpc_url.to_string()) - .await?; - - let admin: PrivateKeySigner = anvil.keys()[1].clone().into(); - let admin_address = admin.address(); - let admin_provider = ProviderBuilder::new() - .with_recommended_fillers() - .wallet(EthereumWallet::new(admin)) - .on_builtin(&rpc_url.to_string()) - .await?; - - //transfer some eth to the key. - let tx = TransactionRequest::default() - .with_to(address) - .with_value(U256::from(1000000000)); - let receipt = admin_provider.send_transaction(tx).await?.get_receipt().await?; - println!("Admin -> Key receipt: {receipt:?}",); - - let account = key_provider.get_accounts().await; - println!("Account: {:?}", account); - let balance = key_provider.get_balance(address).await; - println!("Balance: {:?}", balance); - - //transfer back some eth. - let tx = TransactionRequest::default() - .with_from(address) - .with_to(admin_address) - .with_value(U256::from(5)) - .gas_limit(3000000); - println!("Tx from {:?}", tx.from); - - let receipt = key_provider.send_transaction(tx).await; //.get_receipt().await?; - println!("Key -> Admin receipt: {receipt:?}",); - - // // Print ANvil output. - // use std::io; - // use std::io::BufRead; - // use std::io::BufReader; - // use std::io::Write; - - // let anvil_out = anvil.child_mut().stdout.take().unwrap(); - // let mut stdout_writer = io::stdout(); - // let mut reader = BufReader::new(anvil_out).lines(); - // while let Some(Ok(line)) = reader.next() { - // stdout_writer.write_all(line.as_bytes())?; - // stdout_writer.write_all(b"\n")?; - // } - - Ok(()) -} diff --git a/util/signing/interface/Cargo.toml b/util/signing/interface/Cargo.toml index 1bbaa2271..9c54a4b09 100644 --- a/util/signing/interface/Cargo.toml +++ b/util/signing/interface/Cargo.toml @@ -16,7 +16,7 @@ ed25519-dalek = { workspace = true } k256 = { workspace = true, features = ["ecdsa"] } anyhow = { workspace = true } sha2 = { workspace = true } -serde = { workspace = true } +serde = { workspace = true, features = ["derive"] } async-trait = { workspace = true } [lints] diff --git a/util/signing/interface/src/cryptography/secp256k1.rs b/util/signing/interface/src/cryptography/secp256k1.rs index bd731ad38..6a3957022 100644 --- a/util/signing/interface/src/cryptography/secp256k1.rs +++ b/util/signing/interface/src/cryptography/secp256k1.rs @@ -1,7 +1,8 @@ use crate::cryptography::Curve; use crate::{DigestError, Digester, Verify, VerifyError}; use anyhow::Context; -use k256::ecdsa::{self, signature::Verifier}; +use ed25519_dalek::Verifier; +use k256::ecdsa::{self}; use sha2::Digest as _; /// The secp256k1 elliptic curve. diff --git a/util/signing/interface/src/lib.rs b/util/signing/interface/src/lib.rs index a6e0ed94a..8e9fb1f9f 100644 --- a/util/signing/interface/src/lib.rs +++ b/util/signing/interface/src/lib.rs @@ -15,7 +15,7 @@ pub enum SignerError { Decode(#[source] Box), #[error("signing key not found")] KeyNotFound, - #[error("failed to sign")] + #[error("failed to sign: {0}")] Internal(String), } diff --git a/util/signing/providers/aws-kms/src/hsm/key.rs b/util/signing/providers/aws-kms/src/hsm/key.rs index 2db4e1217..eb53ea8cd 100644 --- a/util/signing/providers/aws-kms/src/hsm/key.rs +++ b/util/signing/providers/aws-kms/src/hsm/key.rs @@ -26,12 +26,14 @@ where impl SignerBuilder> for Builder where C: Curve + AwsKmsCryptographySpec + Send + Sync, + AwsKms: movement_signer::Signing, { async fn build(&self, key: Key) -> Result, SignerBuilderError> { let mut hsm = AwsKms::try_from_env() .await .map_err(|e| SignerBuilderError::Internal(e.to_string()))?; - hsm.set_key_id(key.to_delimited_canonical_string("/")); + // AWS Key id is defined by the key name. + hsm.set_key_id(key.key_name().to_string()); if self.create_key { hsm = hsm .create_key() diff --git a/util/signing/providers/aws-kms/src/hsm/mod.rs b/util/signing/providers/aws-kms/src/hsm/mod.rs index d05ea22fe..aae6a0bf4 100644 --- a/util/signing/providers/aws-kms/src/hsm/mod.rs +++ b/util/signing/providers/aws-kms/src/hsm/mod.rs @@ -2,10 +2,10 @@ use crate::cryptography::AwsKmsCryptographySpec; use anyhow::Context; use aws_sdk_kms::primitives::Blob; use aws_sdk_kms::Client; +use movement_signer::cryptography::secp256k1::{self as mvtsecp256k1, Secp256k1 as MvtSecp256k1}; use movement_signer::cryptography::TryFromBytes; use movement_signer::{cryptography::Curve, SignerError, Signing}; -use secp256k1::ecdsa::Signature as Secp256k1Signature; -use secp256k1::Error as Secp256k1Error; + pub mod key; /// An AWS KMS HSM. @@ -62,24 +62,17 @@ where } } +// The implementation is specific to Secp256k1 for the signature and private key. #[async_trait::async_trait] -impl Signing for AwsKms -where - C: Curve + AwsKmsCryptographySpec + Sync, -{ - async fn sign(&self, message: &[u8]) -> Result { - println!("Preparing to sign message. Message bytes: {:?}", message); - +impl Signing for AwsKms { + async fn sign(&self, message: &[u8]) -> Result { let blob = Blob::new(message); - // Todo: update to use Parameter Store to fetch Key Id - let key_id = std::env::var("AWS_KMS_KEY_ID") - .map_err(|_| SignerError::Internal("AWS_KMS_KEY_ID not set".to_string()))?; - println!("Using Key ID: {}", key_id); let request = self .client .sign() - .key_id(key_id) - .signing_algorithm(C::signing_algorithm_spec()) + .key_id(self.key_id.clone()) + .message_type(aws_sdk_kms::types::MessageType::Digest) + .signing_algorithm(MvtSecp256k1::signing_algorithm_spec()) .message(blob); let res = request @@ -87,42 +80,47 @@ where .await .map_err(|e| SignerError::Internal(format!("Failed to sign: {}", e.to_string())))?; - println!("Response signature (DER format): {:?}", res.signature()); - // Convert DER signature to raw format using secp256k1 let der_signature = res .signature() .context("No signature available") .map_err(|e| SignerError::Internal(e.to_string()))?; - let secp_signature = - Secp256k1Signature::from_der(der_signature.as_ref()).map_err(|e: Secp256k1Error| { - SignerError::Internal(format!("Failed to parse DER signature: {}", e)) - })?; + let secp_signature = k256::ecdsa::Signature::from_der(der_signature.as_ref()) + .map_err(|e| SignerError::Decode(e.into()))?; - let raw_signature = secp_signature.serialize_compact(); - println!("Raw signature: {:?}", raw_signature); + let secp_signature = secp_signature.normalize_s().unwrap_or(secp_signature); // Convert the raw signature into the appropriate curve type - let signature = ::Signature::try_from_bytes(&raw_signature).map_err(|e| { - SignerError::Internal(format!("Failed to convert signature: {}", e.to_string())) - })?; + let signature = mvtsecp256k1::Signature::try_from_bytes(&secp_signature.to_bytes()) + .map_err(|e| { + SignerError::Internal(format!("Failed to convert signature: {}", e.to_string())) + })?; Ok(signature) } - async fn public_key(&self) -> Result { + async fn public_key(&self) -> Result { + println!("AwsKms public_key key_id: {:?}", self.key_id); let res = self.client.get_public_key().key_id(&self.key_id).send().await.map_err(|e| { SignerError::Internal(format!("failed to get public key: {}", e.to_string())) })?; - let public_key = C::PublicKey::try_from_bytes( + let public_key = mvtsecp256k1::PublicKey::try_from_bytes( res.public_key() .context("No public key available") + //Decode pubic key .map_err(|e| { - SignerError::Internal(format!( - "failed to convert public key: {}", - e.to_string() - )) + SignerError::Internal(format!("failed to read public key: {}", e.to_string())) + }) + .and_then(|key| { + spki::SubjectPublicKeyInfoRef::try_from(key.as_ref()) + .map(|spki| spki.subject_public_key.raw_bytes()) + .map_err(|e| { + SignerError::Internal(format!( + "failed to convert public key: {}", + e.to_string() + )) + }) })? .as_ref(), ) @@ -134,6 +132,7 @@ where } // Utility function for DER-to-raw signature conversion +// It' never used? pub fn der_to_raw_signature(der: &[u8]) -> Result<[u8; 64], String> { if der.len() < 8 || der[0] != 0x30 { return Err("Invalid DER signature".to_string()); diff --git a/util/signing/providers/local/Cargo.toml b/util/signing/providers/local/Cargo.toml index 715663645..cd36eedc1 100644 --- a/util/signing/providers/local/Cargo.toml +++ b/util/signing/providers/local/Cargo.toml @@ -19,5 +19,9 @@ rand = { version = "0.8.5" } async-trait = { workspace = true } hex = { workspace = true } +[dev-dependencies] +tokio = { workspace = true } +sha3 = "0.10.8" + [lints] workspace = true diff --git a/util/signing/providers/local/src/signer/mod.rs b/util/signing/providers/local/src/signer/mod.rs index 34a1af781..899e2adec 100644 --- a/util/signing/providers/local/src/signer/mod.rs +++ b/util/signing/providers/local/src/signer/mod.rs @@ -46,6 +46,7 @@ impl LocalSigner { /// Constructs a new [LocalSigner] with a random key pair. pub fn random() -> Self { let signing_key = SigningKey::::random(&mut rand::thread_rng()); + let verifying_key = signing_key.verifying_key().clone(); Self::new(signing_key, verifying_key) } @@ -66,7 +67,7 @@ impl LocalSigner { } pub fn from_signing_key_hex(hex: &str) -> Result { - let bytes = hex::decode(hex).map_err(|e| SignerError::Decode(e.into()))?; + let bytes = hex::decode(hex).map_err(|e| SignerError::Decode(anyhow::anyhow!(e).into()))?; Self::from_signing_key_bytes(&bytes) } } diff --git a/util/signing/testing/Cargo.toml b/util/signing/testing/Cargo.toml index c47c7a049..5abbefd5e 100644 --- a/util/signing/testing/Cargo.toml +++ b/util/signing/testing/Cargo.toml @@ -19,6 +19,9 @@ async-trait = { workspace = true } maptos-dof-execution = { workspace = true } maptos-execution-util = { workspace = true } movement-signing-aptos = { workspace = true } +movement-signer-local = { workspace = true } +movement-signer-aws-kms = { workspace = true } +movement-signing-eth = { workspace = true } aptos-crypto = { workspace = true } aptos-types = { workspace = true } anyhow = { workspace = true } @@ -26,8 +29,18 @@ chrono = { workspace = true } ed25519-dalek = { workspace = true, features = ["rand_core"] } # Workspace is on rand 0.7 due largely to aptos-core rand = "0.8" +sha3 = "0.10.8" tempfile = { workspace = true } tokio = { workspace = true, features = ["macros"] } +alloy.workspace = true +alloy-signer-aws = { git = "https://github.com/alloy-rs/alloy.git", rev = "83343b172585fe4e040fb104b4d1421f58cbf9a2" } +alloy-primitives.workspace = true +alloy-signer.workspace = true +alloy-network.workspace = true +alloy-consensus.workspace = true +alloy-transport-http = { workspace = true, features = ["reqwest-rustls-tls"] } + + [lints] workspace = true diff --git a/util/signing/testing/tests/signer.rs b/util/signing/testing/tests/signer.rs index 8687dbea8..68febd18e 100644 --- a/util/signing/testing/tests/signer.rs +++ b/util/signing/testing/tests/signer.rs @@ -21,3 +21,129 @@ mod ed25519 { Ok(()) } } + +mod secp256k1 { + use alloy::node_bindings::Anvil; + use alloy::providers::{Provider, ProviderBuilder}; + use alloy::rpc::types::TransactionRequest; + use alloy::signers::local::PrivateKeySigner; + use alloy_network::EthereumWallet; + use alloy_network::TransactionBuilder; + use alloy_network::TxSigner; + use alloy_primitives::U256; + use movement_signer::cryptography::secp256k1::Secp256k1; + use movement_signer::Signing; + use movement_signer::Verify; + use movement_signer_aws_kms::hsm::AwsKms; + use movement_signer_local::signer::LocalSigner; + use movement_signing_eth::HsmSigner; + use sha3::{Digest, Keccak256}; + use std::env; + + #[tokio::test] + async fn local_signing_verify() -> Result<(), anyhow::Error> { + let message = b"Hello, world!"; + let digest: [u8; 32] = Keccak256::new_with_prefix(&message).finalize().into(); + + let signer = LocalSigner::::random(); + + let public_key = signer.public_key().await?; + let signature = signer.sign(&digest).await?; + + assert!(movement_signer::cryptography::secp256k1::Secp256k1::verify( + &digest, + &signature, + &public_key + ) + .unwrap()); + Ok(()) + } + + #[tokio::test] + async fn aws_signing_verify() -> Result<(), anyhow::Error> { + let message = b"Hello, world!"; + let digest: [u8; 32] = Keccak256::new_with_prefix(&message).finalize().into(); + ///skip the test in Local mode + if env::var("AWS_KMS_KEY_ID").is_ok() { + let key_id = env::var("AWS_KMS_KEY_ID").expect("AWS_KMS_KEY_ID not set"); + let aws: AwsKms = AwsKms::try_from_env_with_key(key_id).await?; + let public_key = aws.public_key().await?; + let signature = aws.sign(&digest).await?; + + assert!(Secp256k1::verify(&digest, &signature, &public_key)?); + } + Ok(()) + } + + #[tokio::test] + async fn send_tx() -> Result<(), anyhow::Error> { + // Start Anvil + let anvil = Anvil::new().port(8545u16).spawn(); + let rpc_url = anvil.endpoint_url(); + let chain_id = anvil.chain_id(); + + // Detect if we execute with AWS env var or not. + let (key_provider, address) = match env::var("AWS_KMS_KEY_ID") { + Ok(key_id) => { + // Use AWS KMS + let _access_key = env::var("AWS_ACCESS_KEY").expect("AWS_ACCESS_KEY not set"); + let _secret_key = env::var("AWS_SECRET_KEY").expect("AWS_SECRET_KEY not set"); + let aws: AwsKms = AwsKms::try_from_env_with_key(key_id).await?; + let signer = HsmSigner::try_new(aws, Some(chain_id)).await?; + let address = signer.address(); + let key_provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(EthereumWallet::new(signer)) + .on_builtin(&rpc_url.to_string()) + .await?; + (key_provider, address) + } + Err(_) => { + //Use Local KMS + let local = LocalSigner::::random(); + let signer = HsmSigner::try_new(local, Some(chain_id)).await?; + let address = signer.address(); + let key_provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(EthereumWallet::new(signer)) + .on_builtin(&rpc_url.to_string()) + .await?; + (key_provider, address) + } + }; + + let admin: PrivateKeySigner = anvil.keys()[1].clone().into(); + let admin_address = admin.address(); + let admin_provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(EthereumWallet::new(admin)) + .on_builtin(&rpc_url.to_string()) + .await?; + + //transfer some eth to the key. + let tx = TransactionRequest::default() + .with_to(address) + .with_value(U256::from(10_000_000_000_000_000u64)); + admin_provider.send_transaction(tx).await?.get_receipt().await?; + + let balance = key_provider.get_balance(address).await?; + + //transfer back some eth. + let tx = TransactionRequest::default() + .with_from(address) + .with_to(admin_address) + .with_value(U256::from(500)) + .with_chain_id(chain_id) + .gas_limit(3_000_000); + + key_provider.send_transaction(tx).await?.get_receipt().await?; + + let new_balance = key_provider.get_balance(address).await?; + assert!( + balance != new_balance, + "AWS account didn't change. Last transfer doesn't execute correctly." + ); + + Ok(()) + } +}