kobe_eth/
standard_wallet.rs1#[cfg(feature = "alloc")]
7use alloc::string::String;
8
9use alloy_primitives::Address;
10use k256::ecdsa::SigningKey;
11use zeroize::Zeroizing;
12
13use crate::Error;
14use crate::address::{public_key_to_address, to_checksum_address};
15
16#[derive(Debug)]
31pub struct StandardWallet {
32 private_key: SigningKey,
34 address: Address,
36}
37
38impl StandardWallet {
39 #[cfg(feature = "rand")]
49 pub fn generate() -> Result<Self, Error> {
50 use k256::elliptic_curve::rand_core::OsRng;
51 let private_key = SigningKey::random(&mut OsRng);
52 let address = Self::derive_address(&private_key);
53
54 Ok(Self {
55 private_key,
56 address,
57 })
58 }
59
60 pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, Error> {
66 let private_key = SigningKey::from_slice(bytes).map_err(|_| Error::InvalidPrivateKey)?;
67 let address = Self::derive_address(&private_key);
68
69 Ok(Self {
70 private_key,
71 address,
72 })
73 }
74
75 pub fn from_hex(hex_str: &str) -> Result<Self, Error> {
81 let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
82 let bytes = hex::decode(hex_str).map_err(|_| Error::InvalidHex)?;
83
84 let private_key = SigningKey::from_slice(&bytes).map_err(|_| Error::InvalidPrivateKey)?;
85 let address = Self::derive_address(&private_key);
86
87 Ok(Self {
88 private_key,
89 address,
90 })
91 }
92
93 fn derive_address(private_key: &SigningKey) -> Address {
95 let public_key = private_key.verifying_key();
96 let public_key_bytes = public_key.to_encoded_point(false);
97 public_key_to_address(public_key_bytes.as_bytes())
98 }
99
100 #[inline]
102 #[must_use]
103 pub fn secret_bytes(&self) -> Zeroizing<[u8; 32]> {
104 Zeroizing::new(self.private_key.to_bytes().into())
105 }
106
107 #[inline]
109 #[must_use]
110 pub fn secret_hex(&self) -> Zeroizing<String> {
111 Zeroizing::new(hex::encode(self.private_key.to_bytes()))
112 }
113
114 #[inline]
116 #[must_use]
117 pub fn pubkey_hex(&self) -> String {
118 let public_key = self.private_key.verifying_key();
119 let bytes = public_key.to_encoded_point(false);
120 hex::encode(bytes.as_bytes())
121 }
122
123 #[inline]
125 #[must_use]
126 pub fn address(&self) -> String {
127 to_checksum_address(&self.address)
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[cfg(feature = "rand")]
136 #[test]
137 fn test_generate() {
138 let wallet = StandardWallet::generate().unwrap();
139 assert!(wallet.address().starts_with("0x"));
140 assert_eq!(wallet.address().len(), 42);
141 }
142
143 #[cfg(feature = "rand")]
144 #[test]
145 fn test_from_hex() {
146 let wallet = StandardWallet::generate().unwrap();
147 let hex = wallet.secret_hex();
148
149 let imported = StandardWallet::from_hex(&hex).unwrap();
150 assert_eq!(wallet.address(), imported.address());
151 }
152
153 #[cfg(feature = "rand")]
154 #[test]
155 fn test_from_hex_with_prefix() {
156 use alloc::format;
157 let wallet = StandardWallet::generate().unwrap();
158 let hex = format!("0x{}", wallet.secret_hex().as_str());
159
160 let imported = StandardWallet::from_hex(&hex).unwrap();
161 assert_eq!(wallet.address(), imported.address());
162 }
163
164 #[cfg(feature = "rand")]
165 #[test]
166 fn test_from_bytes() {
167 let wallet = StandardWallet::generate().unwrap();
168 let bytes = wallet.secret_bytes();
169
170 let imported = StandardWallet::from_bytes(&bytes).unwrap();
171 assert_eq!(wallet.address(), imported.address());
172 }
173}