Skip to main content

hyper_keyring/
simple.rs

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
9/// Derives an Ethereum address from a secp256k1 public key.
10/// Address = keccak256(uncompressed_pubkey_without_prefix)[12..32], hex-encoded with 0x prefix.
11fn public_key_to_address(verifying_key: &VerifyingKey) -> String {
12    let point = verifying_key.to_encoded_point(false);
13    // Skip the 0x04 prefix byte — use only the 64 bytes of x and y coordinates
14    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
20/// Parse a hex private key string (with or without 0x prefix) into a SigningKey.
21fn 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/// Serializable form for persistence.
30#[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    /// address (lowercase, 0x-prefixed) -> signing key
44    keys: HashMap<String, SigningKey>,
45    /// address -> optional label
46    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    // A well-known test private key (do NOT use in production)
177    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); // 0x + 40 hex chars
191        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        // The exported key should decode to the same signing key
207        let stripped = exported.strip_prefix("0x").unwrap();
208        assert_eq!(stripped.len(), 64);
209        // Re-import should derive the same address
210        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        // v should be 0 or 1
231        assert!(sig[64] == 0 || sig[64] == 1);
232
233        // Verify signature using k256
234        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        // Exported key should match
286        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        // Should derive the same address as with 0x prefix
333        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}