kms_secp256k1_api/services/mocks/
mock_ethereum_keys_service.rs

1use crate::{
2    config::Config,
3    constants::ETH_SECP_LEN,
4    services::{
5        crypto_service::CryptoService,
6        keys_service::{KeyEntry, KeysServiceTrait, SigEntry},
7        mocks::mock_keys_service::{KeyPair, MockKeysService},
8    },
9};
10use base64::Engine;
11use base64::engine::general_purpose::STANDARD;
12use ethers::{
13    signers::{LocalWallet, Signer},
14    types::{H256, TransactionRequest, TxHash},
15};
16use k256::{ecdsa::SigningKey, elliptic_curve::rand_core::OsRng};
17use serde_json::json;
18use std::str::FromStr;
19use tracing::error;
20
21pub struct MockEthereumKeysService {
22    inner: MockKeysService,
23}
24
25#[async_trait::async_trait]
26impl KeysServiceTrait for MockEthereumKeysService {
27    /// Creates a new cryptographic key.
28    ///
29    /// Returns the public key encoded as a hexadecimal string on success.
30    /// Returns an error string describing the issue on failure.
31    async fn create_key(&mut self, _config: &Config) -> Result<KeyEntry, String> {
32        let signing_key = SigningKey::random(&mut OsRng);
33        let verifying_key = signing_key.verifying_key();
34        let encoded_point = verifying_key.to_encoded_point(true);
35
36        let private_key_bytes = signing_key.to_bytes();
37        let private_key = hex::encode(private_key_bytes);
38
39        let pubkey_bytes = encoded_point.as_bytes();
40        let public_key = hex::encode(pubkey_bytes);
41
42        let address = self.resolve_key(&public_key)?;
43
44        {
45            let key_pair = KeyPair {
46                public_key: public_key.clone(),
47                private_key,
48                address: address.clone(),
49            };
50            let mut keys = self.inner.keys.lock().await;
51            keys.entry(address.clone()).or_insert(key_pair);
52        }
53
54        Ok(KeyEntry {
55            public_key: Some(public_key.clone()).into(),
56            address: address.clone().into(),
57            public_key_base64: STANDARD.encode(public_key).into(),
58            key_id: address.into(),
59        })
60    }
61
62    /// Signs a given transaction hash using the specified key.
63    ///
64    /// Takes the transaction hash, key, and configuration.
65    /// Returns the signature as a hexadecimal string if successful.
66    /// Returns an error string if signing fails.
67    async fn sign_transaction_hash(
68        &mut self,
69        _config: &Config,
70        transaction_hash: &str,
71        key: &str,
72    ) -> Result<SigEntry, String> {
73        let key = self.resolve_key(key)?;
74
75        let key_pair = {
76            let keys = self.inner.keys.lock().await;
77            keys.get(&key)
78                .ok_or_else(|| "Public key not found".to_string())?
79                .clone()
80        };
81
82        let tx_hash_bytes = hex::decode(transaction_hash)
83            .map_err(|e| format!("Invalid transaction hash hex: {e}"))?;
84
85        if tx_hash_bytes.len() != 32 {
86            return Err("Transaction hash must be 32 bytes".into());
87        }
88
89        let tx_hash = H256::from_slice(&tx_hash_bytes);
90
91        let wallet: LocalWallet = key_pair
92            .private_key
93            .parse()
94            .map_err(|e| format!("Failed to parse secret key into wallet: {e}"))?;
95
96        let signature = wallet
97            .sign_hash(tx_hash)
98            .map_err(|e| format!("Failed to sign hash: {e}"))?;
99
100        // Verify signature
101        let is_valid = self
102            .verify(
103                transaction_hash,
104                &signature.to_string(),
105                &key_pair.public_key,
106            )
107            .await?;
108
109        if !is_valid {
110            return Err("Signature verification failed".to_string());
111        }
112
113        let signature = hex::encode(signature.to_vec());
114        Ok(SigEntry {
115            address: key.into(),
116            public_key: key_pair.public_key.into(),
117            signature: signature.into(),
118        })
119    }
120
121    /// Signs a transaction represented as a JSON string with the given public key.
122    ///
123    /// Returns the signed transaction as a JSON string on success.
124    /// Returns an error string if parsing the transaction or signing fails.
125    async fn sign_transaction(
126        &mut self,
127        _config: &Config,
128        transaction_str: &str,
129        key: &str,
130    ) -> Result<String, String> {
131        // Parse the transaction JSON (can be wrapped or plain)
132        let parsed: serde_json::Value = serde_json::from_str(transaction_str)
133            .map_err(|e| format!("Failed to parse input JSON: {e}"))?;
134
135        // Extract transaction and signatures array if wrapped
136        let (transaction_value, mut signatures) = match parsed {
137            serde_json::Value::Object(mut map) => {
138                if let Some(tx) = map.remove("transaction") {
139                    let sigs = map
140                        .remove("signatures")
141                        .and_then(|v| v.as_array().cloned())
142                        .unwrap_or_default();
143                    (tx, sigs)
144                } else {
145                    (serde_json::Value::Object(map), vec![])
146                }
147            }
148            _ => return Err("Unsupported transaction format".to_string()),
149        };
150
151        // Deserialize transaction to struct for sighash
152        let transaction: TransactionRequest = serde_json::from_value(transaction_value.clone())
153            .map_err(|e| format!("Failed to parse transaction: {e}"))?;
154
155        // Calculate sighash and validate
156        let mut transaction_hash_str = format!("{:x}", transaction.sighash());
157        transaction_hash_str = transaction_hash_str.trim_start_matches("0x").to_string();
158
159        TxHash::from_str(&transaction_hash_str).map_err(|e| {
160            error!("Invalid transaction hash: {:?}", e);
161            "Invalid transaction hash".to_string()
162        })?;
163
164        // Get key pair and wallet
165        let key = self.resolve_key(key)?;
166        let key_pair = {
167            let keys = self.inner.keys.lock().await;
168            keys.get(&key)
169                .ok_or_else(|| "Public key not found".to_string())?
170                .clone()
171        };
172
173        let wallet: LocalWallet = key_pair
174            .private_key
175            .parse()
176            .map_err(|e| format!("Failed to parse secret key into wallet: {e}"))?;
177
178        // Sign the transaction
179        let signature = wallet
180            .sign_transaction(&transaction.clone().into())
181            .await
182            .map_err(|e| format!("Failed to sign transaction: {e}"))?;
183
184        let signature_hex = signature.to_string();
185
186        // Verify signature
187        let is_valid = self
188            .verify(&transaction_hash_str, &signature_hex, &key_pair.public_key)
189            .await?;
190        if !is_valid {
191            return Err("Generated signature failed verification".to_string());
192        }
193
194        // Append signature info
195        signatures.push(json!({
196            "address": key,
197            "signer": &key_pair.public_key,
198            "v": format!("{:x}", signature.v),
199            "r": format!("{:x}", signature.r),
200            "s": format!("{:x}", signature.s),
201            "hash": transaction_hash_str,
202            "signature": signature_hex
203        }));
204
205        // Return wrapped transaction + signatures
206        let result = json!({
207            "transaction": transaction_value,
208            "signatures": signatures
209        });
210
211        serde_json::to_string(&result)
212            .map_err(|e| format!("Failed to serialize final signed transaction: {e}"))
213    }
214
215    /// Verifies an Ethereum EIP-155 signature for a given transaction hash and public key.
216    ///
217    /// Returns `Ok(true)` if the signature is valid, `Ok(false)` if invalid.
218    /// Returns an error string for failures such as invalid formats.
219    async fn verify(
220        &mut self,
221        transaction_hash_hex: &str,
222        signature_hex: &str,
223        key: &str,
224    ) -> Result<bool, String> {
225        let key = self.resolve_key(key)?;
226        let key_pair = {
227            let keys = self.inner.keys.lock().await;
228            keys.get(&key)
229                .ok_or_else(|| "Public key not found".to_string())?
230                .clone()
231        };
232        self.inner
233            .verify_eip155(transaction_hash_hex, signature_hex, &key_pair.public_key)
234            .map_err(|e| {
235                let msg = format!("Signature verification failed: {e}");
236                error!("{}", msg);
237                msg
238            })
239    }
240
241    /// Verifies a signature via the Key Management Service (KMS).
242    ///
243    /// Asynchronous function that returns `Ok(true)` if verification succeeds,
244    /// or an error string if it fails.
245    async fn verify_via_kms(
246        &mut self,
247        transaction_hash_hex: &str,
248        signature_hex: &str,
249        key: &str,
250    ) -> Result<bool, String> {
251        let key = self.resolve_key(key)?;
252        let key_pair = {
253            let keys = self.inner.keys.lock().await;
254            keys.get(&key)
255                .ok_or_else(|| "Public key not found".to_string())?
256                .clone()
257        };
258        self.inner
259            .verify_via_kms_eip155(transaction_hash_hex, signature_hex, &key_pair.public_key)
260            .await
261    }
262
263    /// Deletes a public key from storage.
264    ///
265    /// Returns `Ok(true)` if the key was deleted, `Ok(false)` if the key was not found.
266    /// Returns an error string if deletion fails.
267    async fn delete_key(&mut self, key: &str) -> Result<bool, String> {
268        let key = self.resolve_key(key)?;
269        Ok(self.inner.delete_key(&key).await)
270    }
271
272    /// Lists all stored keys along with their associated metadata.
273    ///
274    /// Returns a vector of `KeyEntry` on success.
275    /// Returns an error string on failure.
276    async fn list_keys(&mut self) -> Result<Vec<KeyEntry>, String> {
277        Ok(self.inner.list_keys().await)
278    }
279}
280
281impl MockEthereumKeysService {
282    /// Creates a new `MockEthereumKeysService` with an empty in-memory key store.
283    ///
284    /// # Arguments
285    ///
286    /// * `_config` - Unused configuration object, included for interface compatibility.
287    /// * `crypto_service` - The crypto service to use for key operations.
288    ///
289    /// # Errors
290    ///
291    /// This function currently does not return an error, but it returns a `Result`
292    /// to match a common interface and allow future fallibility.
293    pub fn new(config: Config, crypto_service: CryptoService) -> Result<Self, String> {
294        Ok(Self {
295            inner: MockKeysService::new(config, crypto_service),
296        })
297    }
298
299    /// Resolves a given key string to an Ethereum address.
300    ///
301    /// This function checks whether the input `key` is a compressed secp256k1 public key
302    /// (by comparing its length to the expected `ETH_SECP_LEN`). If so, it attempts to convert
303    /// the public key to its corresponding Ethereum address using the crypto service. Otherwise,
304    /// it assumes the key is already an address and returns it as-is.
305    ///
306    /// # Parameters
307    /// - `key`: A string that is either an Ethereum address or a compressed public key (hex-encoded, starting with "02"/"03").
308    ///
309    /// # Returns
310    /// - `Ok(String)`: The resolved Ethereum address as a string.
311    /// - `Err(String)`: An error message if public key conversion fails.
312    ///
313    /// # Errors
314    /// - Returns an error if the input is treated as a public key and the conversion fails.
315    ///
316    fn resolve_key(&mut self, key: &str) -> Result<String, String> {
317        if key.len() == ETH_SECP_LEN {
318            self.inner.crypto_service.address_eth(key).map_err(|e| {
319                let msg = format!("Failed to convert public key to address: {e:?}");
320                error!("{}", &msg);
321                msg
322            })
323        } else {
324            Ok(key.to_string())
325        }
326    }
327}