1use std::collections::HashMap;
2
3use k256::ecdsa::{SigningKey, VerifyingKey};
4use serde::{Deserialize, Serialize};
5use sha3::{Digest, Keccak256};
6
7use crate::{Keyring, KeyringAccount, KeyringError};
8
9fn public_key_to_address(verifying_key: &VerifyingKey) -> String {
12 let point = verifying_key.to_encoded_point(false);
13 let pubkey_bytes = &point.as_bytes()[1..];
15 let hash = Keccak256::digest(pubkey_bytes);
16 let addr_bytes = &hash[12..];
17 format!("0x{}", hex::encode(addr_bytes))
18}
19
20fn parse_private_key(key_hex: &str) -> Result<SigningKey, KeyringError> {
22 let stripped = key_hex.strip_prefix("0x").unwrap_or(key_hex);
23 let bytes = hex::decode(stripped)
24 .map_err(|e| KeyringError::InvalidKey(format!("invalid hex: {}", e)))?;
25 SigningKey::from_bytes(bytes.as_slice().into())
26 .map_err(|e| KeyringError::InvalidKey(format!("invalid secp256k1 key: {}", e)))
27}
28
29#[derive(Serialize, Deserialize)]
31struct SimpleKeyringState {
32 keys: Vec<SimpleKeyEntry>,
33}
34
35#[derive(Serialize, Deserialize)]
36struct SimpleKeyEntry {
37 address: String,
38 private_key_hex: String,
39 label: Option<String>,
40}
41
42pub struct SimpleKeyring {
43 keys: HashMap<String, SigningKey>,
45 labels: HashMap<String, Option<String>>,
47}
48
49impl SimpleKeyring {
50 pub fn new() -> Self {
51 Self {
52 keys: HashMap::new(),
53 labels: HashMap::new(),
54 }
55 }
56}
57
58impl Default for SimpleKeyring {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64impl Keyring for SimpleKeyring {
65 fn keyring_type(&self) -> &str {
66 "simple"
67 }
68
69 fn serialize(&self) -> Result<Vec<u8>, KeyringError> {
70 let entries: Vec<SimpleKeyEntry> = self
71 .keys
72 .iter()
73 .map(|(addr, sk)| SimpleKeyEntry {
74 address: addr.clone(),
75 private_key_hex: hex::encode(sk.to_bytes()),
76 label: self.labels.get(addr).cloned().flatten(),
77 })
78 .collect();
79 let state = SimpleKeyringState { keys: entries };
80 serde_json::to_vec(&state).map_err(|e| KeyringError::SerializationError(e.to_string()))
81 }
82
83 fn deserialize(data: &[u8]) -> Result<Self, KeyringError> {
84 let state: SimpleKeyringState = serde_json::from_slice(data)
85 .map_err(|e| KeyringError::SerializationError(e.to_string()))?;
86 let mut keyring = SimpleKeyring::new();
87 for entry in state.keys {
88 let sk = parse_private_key(&entry.private_key_hex)?;
89 let address = entry.address.to_lowercase();
90 keyring.keys.insert(address.clone(), sk);
91 keyring.labels.insert(address, entry.label);
92 }
93 Ok(keyring)
94 }
95
96 fn add_accounts(
97 &mut self,
98 private_keys: &[String],
99 ) -> Result<Vec<KeyringAccount>, KeyringError> {
100 let mut accounts = Vec::new();
101 for key_hex in private_keys {
102 let sk = parse_private_key(key_hex)?;
103 let vk = VerifyingKey::from(&sk);
104 let address = public_key_to_address(&vk);
105
106 if self.keys.contains_key(&address) {
107 return Err(KeyringError::DuplicateAccount(address));
108 }
109
110 self.keys.insert(address.clone(), sk);
111 self.labels.insert(address.clone(), None);
112 accounts.push(KeyringAccount {
113 address,
114 label: None,
115 });
116 }
117 Ok(accounts)
118 }
119
120 fn get_accounts(&self) -> Vec<KeyringAccount> {
121 self.keys
122 .keys()
123 .map(|addr| KeyringAccount {
124 address: addr.clone(),
125 label: self.labels.get(addr).cloned().flatten(),
126 })
127 .collect()
128 }
129
130 fn export_account(&self, address: &str) -> Result<String, KeyringError> {
131 let addr = address.to_lowercase();
132 let sk = self
133 .keys
134 .get(&addr)
135 .ok_or_else(|| KeyringError::AccountNotFound(addr.clone()))?;
136 Ok(format!("0x{}", hex::encode(sk.to_bytes())))
137 }
138
139 fn remove_account(&mut self, address: &str) -> Result<(), KeyringError> {
140 let addr = address.to_lowercase();
141 if self.keys.remove(&addr).is_none() {
142 return Err(KeyringError::AccountNotFound(addr));
143 }
144 self.labels.remove(&addr);
145 Ok(())
146 }
147
148 fn sign_hash(&self, address: &str, hash: &[u8; 32]) -> Result<[u8; 65], KeyringError> {
149 let addr = address.to_lowercase();
150 let sk = self
151 .keys
152 .get(&addr)
153 .ok_or_else(|| KeyringError::AccountNotFound(addr.clone()))?;
154
155 use k256::ecdsa::signature::hazmat::PrehashSigner;
156 let (signature, recovery_id): (k256::ecdsa::Signature, k256::ecdsa::RecoveryId) = sk
157 .sign_prehash(hash)
158 .map_err(|e| KeyringError::SigningError(e.to_string()))?;
159
160 let r_bytes = signature.r().to_bytes();
161 let s_bytes = signature.s().to_bytes();
162 let v = recovery_id.to_byte();
163
164 let mut sig = [0u8; 65];
165 sig[..32].copy_from_slice(&r_bytes);
166 sig[32..64].copy_from_slice(&s_bytes);
167 sig[64] = v;
168 Ok(sig)
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 const TEST_KEY: &str = "0x4c0883a69102937d6231471b5dbb6204fe512961708279f22a82e1e0e3e1d0a2";
178
179 fn make_keyring_with_key() -> (SimpleKeyring, String) {
180 let mut kr = SimpleKeyring::new();
181 let accounts = kr.add_accounts(&[TEST_KEY.to_string()]).unwrap();
182 let addr = accounts[0].address.clone();
183 (kr, addr)
184 }
185
186 #[test]
187 fn test_add_accounts_valid_key() {
188 let (kr, addr) = make_keyring_with_key();
189 assert!(addr.starts_with("0x"));
190 assert_eq!(addr.len(), 42); assert_eq!(kr.get_accounts().len(), 1);
192 }
193
194 #[test]
195 fn test_get_accounts_returns_correct_addresses() {
196 let (kr, addr) = make_keyring_with_key();
197 let accounts = kr.get_accounts();
198 assert_eq!(accounts.len(), 1);
199 assert_eq!(accounts[0].address, addr);
200 }
201
202 #[test]
203 fn test_export_account_returns_correct_key() {
204 let (kr, addr) = make_keyring_with_key();
205 let exported = kr.export_account(&addr).unwrap();
206 let stripped = exported.strip_prefix("0x").unwrap();
208 assert_eq!(stripped.len(), 64);
209 let sk = parse_private_key(&exported).unwrap();
211 let vk = VerifyingKey::from(&sk);
212 let re_addr = public_key_to_address(&vk);
213 assert_eq!(re_addr, addr);
214 }
215
216 #[test]
217 fn test_remove_account() {
218 let (mut kr, addr) = make_keyring_with_key();
219 assert_eq!(kr.get_accounts().len(), 1);
220 kr.remove_account(&addr).unwrap();
221 assert_eq!(kr.get_accounts().len(), 0);
222 }
223
224 #[test]
225 fn test_sign_hash_produces_valid_signature() {
226 let (kr, addr) = make_keyring_with_key();
227 let hash = [0xab_u8; 32];
228 let sig = kr.sign_hash(&addr, &hash).unwrap();
229 assert_eq!(sig.len(), 65);
230 assert!(sig[64] == 0 || sig[64] == 1);
232
233 use k256::ecdsa::{RecoveryId, Signature, VerifyingKey};
235 let signature = Signature::from_slice(&sig[..64]).unwrap();
236 let recovery_id = RecoveryId::from_byte(sig[64]).unwrap();
237 let recovered = VerifyingKey::recover_from_prehash(&hash, &signature, recovery_id).unwrap();
238 let recovered_addr = public_key_to_address(&recovered);
239 assert_eq!(recovered_addr, addr);
240 }
241
242 #[test]
243 fn test_error_invalid_key() {
244 let mut kr = SimpleKeyring::new();
245 let result = kr.add_accounts(&["not-a-valid-key".to_string()]);
246 assert!(result.is_err());
247 match result.unwrap_err() {
248 KeyringError::InvalidKey(_) => {}
249 other => panic!("Expected InvalidKey, got: {:?}", other),
250 }
251 }
252
253 #[test]
254 fn test_error_account_not_found() {
255 let kr = SimpleKeyring::new();
256 let result = kr.export_account("0xdeadbeef");
257 assert!(result.is_err());
258 match result.unwrap_err() {
259 KeyringError::AccountNotFound(_) => {}
260 other => panic!("Expected AccountNotFound, got: {:?}", other),
261 }
262 }
263
264 #[test]
265 fn test_error_duplicate_account() {
266 let (mut kr, _) = make_keyring_with_key();
267 let result = kr.add_accounts(&[TEST_KEY.to_string()]);
268 assert!(result.is_err());
269 match result.unwrap_err() {
270 KeyringError::DuplicateAccount(_) => {}
271 other => panic!("Expected DuplicateAccount, got: {:?}", other),
272 }
273 }
274
275 #[test]
276 fn test_serialize_deserialize_roundtrip() {
277 let (kr, addr) = make_keyring_with_key();
278 let data = kr.serialize().unwrap();
279 let kr2 = SimpleKeyring::deserialize(&data).unwrap();
280
281 let accounts2 = kr2.get_accounts();
282 assert_eq!(accounts2.len(), 1);
283 assert_eq!(accounts2[0].address, addr);
284
285 let exported1 = kr.export_account(&addr).unwrap();
287 let exported2 = kr2.export_account(&addr).unwrap();
288 assert_eq!(exported1, exported2);
289 }
290
291 #[test]
292 fn test_remove_account_not_found() {
293 let mut kr = SimpleKeyring::new();
294 let result = kr.remove_account("0xnonexistent");
295 assert!(result.is_err());
296 match result.unwrap_err() {
297 KeyringError::AccountNotFound(_) => {}
298 other => panic!("Expected AccountNotFound, got: {:?}", other),
299 }
300 }
301
302 #[test]
303 fn test_sign_hash_account_not_found() {
304 let kr = SimpleKeyring::new();
305 let hash = [0u8; 32];
306 let result = kr.sign_hash("0xnonexistent", &hash);
307 assert!(result.is_err());
308 match result.unwrap_err() {
309 KeyringError::AccountNotFound(_) => {}
310 other => panic!("Expected AccountNotFound, got: {:?}", other),
311 }
312 }
313
314 #[test]
315 fn test_add_multiple_accounts() {
316 let mut kr = SimpleKeyring::new();
317 let key2 = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
318 let accounts = kr
319 .add_accounts(&[TEST_KEY.to_string(), key2.to_string()])
320 .unwrap();
321 assert_eq!(accounts.len(), 2);
322 assert_ne!(accounts[0].address, accounts[1].address);
323 assert_eq!(kr.get_accounts().len(), 2);
324 }
325
326 #[test]
327 fn test_key_without_0x_prefix() {
328 let mut kr = SimpleKeyring::new();
329 let key_no_prefix = "4c0883a69102937d6231471b5dbb6204fe512961708279f22a82e1e0e3e1d0a2";
330 let accounts = kr.add_accounts(&[key_no_prefix.to_string()]).unwrap();
331 assert_eq!(accounts.len(), 1);
332 let mut kr2 = SimpleKeyring::new();
334 let accounts2 = kr2.add_accounts(&[TEST_KEY.to_string()]).unwrap();
335 assert_eq!(accounts[0].address, accounts2[0].address);
336 }
337}