kms_secp256k1_api/services/mocks/
mock_cosmos_keys_service.rs1use crate::{
2 config::Config,
3 constants::{COSMOS_SECP_LEN, DEFAULT_COSMOS_HRP},
4 services::{
5 cosmos_keys_service::{
6 BodyHelper, append_signature_to_transaction, build_auth_info, fee_amount_json,
7 fetch_account_info, signature_to_json,
8 },
9 crypto_service::CryptoService,
10 keys_service::{KeyEntry, KeysServiceTrait, SigEntry},
11 mocks::mock_keys_service::{KeyPair, MockKeysService},
12 },
13};
14use base64::Engine;
15use base64::engine::general_purpose::STANDARD;
16use cosmrs::{
17 bip32::{PrivateKey, PublicKey},
18 proto::cosmos::tx::v1beta1::TxRaw,
19 tendermint::chain::Id,
20 tx::{Fee, MessageExt, SignDoc},
21};
22use k256::{
23 ecdsa::{Signature, SigningKey, signature::Signer},
24 elliptic_curve::rand_core::OsRng,
25 sha2::{Digest, Sha256},
26};
27use serde_json::json;
28use std::str::FromStr;
29use tracing::error;
30
31pub struct MockCosmosKeysService {
32 inner: MockKeysService,
33 hrp: String,
34}
35
36#[async_trait::async_trait]
37impl KeysServiceTrait for MockCosmosKeysService {
38 async fn create_key(&mut self, _config: &Config) -> Result<KeyEntry, String> {
39 let signing_key = SigningKey::random(&mut OsRng);
40 let verifying_key = signing_key.verifying_key();
41 let encoded_point = verifying_key.to_encoded_point(true);
42
43 let private_key_bytes = signing_key.to_bytes();
44 let private_key = hex::encode(private_key_bytes);
45 let pubkey_bytes = encoded_point.as_bytes();
46 let public_key = hex::encode(pubkey_bytes);
47 let address = self.resolve_key(&public_key)?;
48
49 {
50 let key_pair = KeyPair {
51 public_key: public_key.clone(),
52 private_key,
53 address: address.clone(),
54 };
55 let mut keys = self.inner.keys.lock().await;
56 keys.entry(address.clone()).or_insert(key_pair);
57 }
58
59 Ok(KeyEntry {
60 public_key: Some(public_key.clone()).into(),
61 address: address.clone().into(),
62 public_key_base64: STANDARD.encode(public_key).into(),
63 key_id: address.into(),
64 })
65 }
66
67 async fn sign_transaction_hash(
73 &mut self,
74 _config: &Config,
75 transaction_hash: &str,
76 key: &str,
77 ) -> Result<SigEntry, String> {
78 let key = self.resolve_key(key)?;
79
80 let key_pair = {
81 let keys = self.inner.keys.lock().await;
82 keys.get(&key)
83 .ok_or_else(|| "Key not found".to_string())?
84 .clone()
85 };
86
87 let hash_bytes = hex::decode(transaction_hash)
88 .map_err(|e| format!("Invalid transaction hash hex: {e}"))?;
89
90 if hash_bytes.len() != 32 {
91 return Err("Transaction hash must be 32 bytes".to_string());
92 }
93
94 let private_key_bytes = hex::decode(&key_pair.private_key)
95 .map_err(|e| format!("Failed to decode secret key: {e}"))?;
96
97 let signing_key = SigningKey::from_slice(&private_key_bytes)
98 .map_err(|e| format!("Failed to create signing key: {e}"))?;
99
100 let signature: Signature = signing_key.sign(&hash_bytes);
101 let der = signature.to_der();
102 let der_bytes = der.as_bytes();
103 let base64_signature = STANDARD.encode(der_bytes);
104 let signature = self
107 .inner
108 .crypto_service
109 .convert(&base64_signature)
110 .map_err(|e| {
111 let msg = format!("Failed to convert signature: {e}");
112 error!("{}", msg);
113 msg
114 })?;
115
116 let is_valid = self
118 .verify(transaction_hash, &signature, &key_pair.public_key)
119 .await?;
120
121 if !is_valid {
122 return Err("Signature verification failed".to_string());
123 }
124
125 Ok(SigEntry {
126 address: key.into(),
127 public_key: key_pair.public_key.into(),
128 signature: signature.into(),
129 })
130 }
131
132 #[allow(clippy::too_many_lines)]
137 async fn sign_transaction(
138 &mut self,
139 config: &Config,
140 transaction_str: &str,
141 key: &str,
142 ) -> Result<String, String> {
143 let tx_json: serde_json::Value = serde_json::from_str(transaction_str)
144 .map_err(|e| format!("Failed to parse JSON: {e}"))?;
145
146 let chain_id = Id::from_str(&config.get_cosmos_chain_id())
148 .map_err(|e| format!("Failed to fetch chain_id: {e}"))?;
149
150 let key = self.resolve_key(key)?;
152 let key_pair = {
153 let keys = self.inner.keys.lock().await;
154 keys.get(&key)
155 .ok_or_else(|| "Public key not found".to_string())?
156 .clone()
157 };
158 let key = key_pair.address;
159
160 let helper: BodyHelper = serde_json::from_value(tx_json["body"].clone())
162 .map_err(|e| format!("Invalid TxBody: {e}"))?;
163
164 let tx_body = helper.into_body()?;
165
166 let fee: Fee = serde_json::from_value(tx_json["auth_info"]["fee"].clone())
168 .map_err(|e| format!("Invalid Fee: {e}"))?;
169
170 let private_key_bytes = hex::decode(&key_pair.private_key)
171 .map_err(|e| format!("Failed to decode secret key: {e}"))?;
172
173 let signing_key = SigningKey::from_slice(&private_key_bytes)
174 .map_err(|e| format!("Failed to create signing key: {e}"))?;
175
176 let public_key = signing_key.public_key();
177
178 let public_key_bytes = public_key.to_bytes();
179 let pubkey_base64 = STANDARD.encode(public_key_bytes);
180
181 let mut account = fetch_account_info(&key, &pubkey_base64, config)
183 .await
184 .map_err(|e| format!("Failed to fetch account info: {e}"))?;
185
186 let Some(fetched_pub_key) = account.pub_key.take() else {
187 return Err("Account info is missing pub_key".to_string());
188 };
189
190 if fetched_pub_key.key != pubkey_base64 {
191 return Err(format!(
192 "Invalid fetched public key: got {}",
193 fetched_pub_key.key
194 ));
195 }
196 let auth_info = build_auth_info(&public_key_bytes, account.sequence, &fee)?;
197
198 let sign_doc = SignDoc::new(&tx_body, &auth_info, &chain_id, account.sequence)
200 .map_err(|e| format!("SignDoc error: {e}"))?;
201
202 let sign_doc_bytes = sign_doc
203 .into_bytes()
204 .map_err(|e| format!("SignDoc encode error: {e}"))?;
205
206 let transaction_hash = Sha256::digest(&sign_doc_bytes);
208 let signature: Signature = signing_key.sign(&transaction_hash);
209 let transaction_hash = hex::encode(transaction_hash);
210
211 let signature_hex = signature.to_string();
213 let public_key = hex::encode(public_key_bytes);
214
215 let is_valid = self
216 .verify(&transaction_hash, &signature_hex, &public_key)
217 .await?;
218 if !is_valid {
219 return Err("Generated signature failed verification".to_string());
220 }
221
222 let body_bytes = tx_body
223 .into_bytes()
224 .map_err(|e| format!("Failed to encode body: {e}"))?;
225 let auth_info_bytes = auth_info
226 .into_bytes()
227 .map_err(|e| format!("Failed to encode auth_info: {e}"))?;
228
229 let tx_raw = TxRaw {
230 body_bytes,
231 auth_info_bytes,
232 signatures: vec![signature.to_bytes().to_vec()],
233 };
234
235 let tx_raw_bytes = tx_raw
236 .to_bytes()
237 .map_err(|e| format!("Failed to encode TxRaw: {e}"))?;
238
239 let base64_tx = STANDARD.encode(tx_raw_bytes);
240 let broadcast_request = json!({
241 "tx_bytes": base64_tx,
242 "mode": "BROADCAST_MODE_SYNC" });
244 let fee_amount_json = fee_amount_json(&fee);
245
246 let new_signature = signature_to_json(&key, &public_key, &signature, &transaction_hash);
248 let signatures_array = append_signature_to_transaction(&tx_json, new_signature);
249
250 let result = json!({
252 "chain_id": chain_id,
253 "body": tx_json["body"],
254 "auth_info": {
255 "signer_infos": [
256 {
257 "public_key": {
258 "@type": fetched_pub_key.key_type,
259 "key": fetched_pub_key.key,
260 },
261 "mode_info": {
262 "single": { "mode": "SIGN_MODE_DIRECT" }
263 },
264 "sequence": account.sequence,
265 "account_number": account.account_number
266 }
267 ],
268 "fee": {
269 "amount": fee_amount_json,
270 "gas_limit": fee.gas_limit
271 }
272 },
273 "broadcast_request": broadcast_request,
274 "signatures": signatures_array
275 });
276
277 serde_json::to_string(&result)
279 .map_err(|e| format!("Failed to serialize final signed transaction: {e}"))
280 }
281
282 async fn verify(
287 &mut self,
288 transaction_hash_hex: &str,
289 signature_hex: &str,
290 key: &str,
291 ) -> Result<bool, String> {
292 let key = self.resolve_key(key)?;
293 let key_pair = {
294 let keys = self.inner.keys.lock().await;
295 keys.get(&key)
296 .ok_or_else(|| "Public key not found".to_string())?
297 .clone()
298 };
299 self.inner
300 .verify(transaction_hash_hex, signature_hex, &key_pair.public_key)
301 .map_err(|e| {
302 let msg = format!("Signature verification failed: {e}");
303 error!("{}", msg);
304 msg
305 })
306 }
307
308 async fn verify_via_kms(
313 &mut self,
314 transaction_hash_hex: &str,
315 signature_hex: &str,
316 key: &str,
317 ) -> Result<bool, String> {
318 let key = self.resolve_key(key)?;
319 let key_pair = {
320 let keys = self.inner.keys.lock().await;
321 keys.get(&key)
322 .ok_or_else(|| "Public key not found".to_string())?
323 .clone()
324 };
325 self.inner
326 .verify_via_kms(transaction_hash_hex, signature_hex, &key_pair.public_key)
327 .await
328 }
329
330 async fn delete_key(&mut self, key: &str) -> Result<bool, String> {
335 let key = self.resolve_key(key)?;
336 Ok(self.inner.delete_key(&key).await)
337 }
338
339 async fn list_keys(&mut self) -> Result<Vec<KeyEntry>, String> {
344 Ok(self.inner.list_keys().await)
345 }
346}
347
348impl MockCosmosKeysService {
349 pub fn new(config: Config, crypto_service: CryptoService) -> Result<Self, String> {
361 let cosmos_hrp = config.get_cosmos_hrp();
362 let hrp = match cosmos_hrp.as_str() {
363 "" => DEFAULT_COSMOS_HRP,
364 hrp => hrp,
365 }
366 .to_string();
367
368 Ok(Self {
369 inner: MockKeysService::new(config, crypto_service),
370 hrp,
371 })
372 }
373
374 fn resolve_key(&mut self, key: &str) -> Result<String, String> {
395 if key.len() == COSMOS_SECP_LEN {
396 self.inner
397 .crypto_service
398 .address_cosmos(key, &self.hrp)
399 .map_err(|e| {
400 let msg = format!("Failed to convert public key to address: {e:?}");
401 error!("{}", &msg);
402 msg
403 })
404 } else {
405 Ok(key.to_string())
406 }
407 }
408}