kobe_eth/
standard_wallet.rs1#[cfg(feature = "alloc")]
6use alloc::string::String;
7
8use alloy_primitives::Address;
9use k256::ecdsa::SigningKey;
10use zeroize::Zeroizing;
11
12use crate::Error;
13use crate::utils::{public_key_to_address, to_checksum_address};
14
15#[derive(Debug)]
20pub struct StandardWallet {
21 private_key: SigningKey,
23 address: Address,
25}
26
27impl StandardWallet {
28 #[cfg(feature = "rand")]
38 pub fn generate() -> Result<Self, Error> {
39 use k256::elliptic_curve::rand_core::OsRng;
40 let private_key = SigningKey::random(&mut OsRng);
41 let address = Self::derive_address(&private_key);
42
43 Ok(Self {
44 private_key,
45 address,
46 })
47 }
48
49 pub fn from_private_key_hex(hex_str: &str) -> Result<Self, Error> {
55 let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
56 let bytes = hex::decode(hex_str).map_err(|_| Error::InvalidPrivateKey)?;
57
58 let private_key = SigningKey::from_slice(&bytes).map_err(|_| Error::InvalidPrivateKey)?;
59 let address = Self::derive_address(&private_key);
60
61 Ok(Self {
62 private_key,
63 address,
64 })
65 }
66
67 fn derive_address(private_key: &SigningKey) -> Address {
69 let public_key = private_key.verifying_key();
70 let public_key_bytes = public_key.to_encoded_point(false);
71 public_key_to_address(public_key_bytes.as_bytes())
72 }
73
74 #[must_use]
76 pub fn private_key_hex(&self) -> Zeroizing<String> {
77 Zeroizing::new(hex::encode(self.private_key.to_bytes()))
78 }
79
80 #[must_use]
82 pub fn public_key_hex(&self) -> String {
83 let public_key = self.private_key.verifying_key();
84 let bytes = public_key.to_encoded_point(false);
85 hex::encode(bytes.as_bytes())
86 }
87
88 #[must_use]
90 pub const fn address(&self) -> &Address {
91 &self.address
92 }
93
94 #[must_use]
96 pub fn address_string(&self) -> String {
97 to_checksum_address(&self.address)
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[cfg(feature = "rand")]
106 #[test]
107 fn test_generate() {
108 let wallet = StandardWallet::generate().unwrap();
109 assert!(wallet.address_string().starts_with("0x"));
110 assert_eq!(wallet.address_string().len(), 42);
111 }
112
113 #[cfg(feature = "rand")]
114 #[test]
115 fn test_from_private_key() {
116 let wallet = StandardWallet::generate().unwrap();
117 let pk_hex = wallet.private_key_hex();
118
119 let imported = StandardWallet::from_private_key_hex(&pk_hex).unwrap();
120 assert_eq!(wallet.address_string(), imported.address_string());
121 }
122
123 #[cfg(feature = "rand")]
124 #[test]
125 fn test_from_private_key_with_prefix() {
126 use alloc::format;
127 let wallet = StandardWallet::generate().unwrap();
128 let pk_hex = format!("0x{}", wallet.private_key_hex().as_str());
129
130 let imported = StandardWallet::from_private_key_hex(&pk_hex).unwrap();
131 assert_eq!(wallet.address_string(), imported.address_string());
132 }
133}