kobe_evm/
standard_wallet.rs1#[cfg(feature = "alloc")]
7use alloc::string::String;
8
9use alloy_primitives::Address;
10use k256::ecdsa::SigningKey;
11use zeroize::{Zeroize, Zeroizing};
12
13use crate::Error;
14use crate::address::{public_key_to_address, to_checksum_address};
15
16#[derive(Debug)]
21pub struct StandardWallet {
22 private_key: SigningKey,
24 address: Address,
26}
27
28impl StandardWallet {
29 #[cfg(feature = "rand")]
39 pub fn generate() -> Result<Self, Error> {
40 use k256::elliptic_curve::rand_core::OsRng;
41 let private_key = SigningKey::random(&mut OsRng);
42 let address = Self::derive_address(&private_key)?;
43
44 Ok(Self {
45 private_key,
46 address,
47 })
48 }
49
50 pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, Error> {
56 let private_key = SigningKey::from_slice(bytes).map_err(|_| Error::InvalidPrivateKey)?;
57 let address = Self::derive_address(&private_key)?;
58
59 Ok(Self {
60 private_key,
61 address,
62 })
63 }
64
65 pub fn from_hex(hex_str: &str) -> Result<Self, Error> {
71 let stripped = hex_str.strip_prefix("0x").unwrap_or(hex_str);
72 let mut bytes = hex::decode(stripped).map_err(|_| Error::InvalidHex)?;
73
74 let result = SigningKey::from_slice(&bytes).map_err(|_| Error::InvalidPrivateKey);
75 bytes.zeroize();
76 let private_key = result?;
77 let address = Self::derive_address(&private_key)?;
78
79 Ok(Self {
80 private_key,
81 address,
82 })
83 }
84
85 fn derive_address(private_key: &SigningKey) -> Result<Address, Error> {
91 let public_key = private_key.verifying_key();
92 let public_key_bytes = public_key.to_encoded_point(false);
93 public_key_to_address(public_key_bytes.as_bytes())
94 }
95
96 #[inline]
98 #[must_use]
99 pub fn secret_bytes(&self) -> Zeroizing<[u8; 32]> {
100 Zeroizing::new(self.private_key.to_bytes().into())
101 }
102
103 #[inline]
105 #[must_use]
106 pub fn secret_hex(&self) -> Zeroizing<String> {
107 Zeroizing::new(hex::encode(self.private_key.to_bytes()))
108 }
109
110 #[inline]
112 #[must_use]
113 pub fn pubkey_hex(&self) -> String {
114 let public_key = self.private_key.verifying_key();
115 let bytes = public_key.to_encoded_point(false);
116 hex::encode(bytes.as_bytes())
117 }
118
119 #[inline]
121 #[must_use]
122 pub fn address(&self) -> String {
123 to_checksum_address(&self.address)
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[cfg(feature = "rand")]
132 #[test]
133 fn test_generate() {
134 let wallet = StandardWallet::generate().unwrap();
135 assert!(wallet.address().starts_with("0x"));
136 assert_eq!(wallet.address().len(), 42);
137 }
138
139 #[cfg(feature = "rand")]
140 #[test]
141 fn test_from_hex() {
142 let wallet = StandardWallet::generate().unwrap();
143 let hex = wallet.secret_hex();
144
145 let imported = StandardWallet::from_hex(&hex).unwrap();
146 assert_eq!(wallet.address(), imported.address());
147 }
148
149 #[cfg(feature = "rand")]
150 #[test]
151 fn test_from_hex_with_prefix() {
152 use alloc::format;
153 let wallet = StandardWallet::generate().unwrap();
154 let hex = format!("0x{}", wallet.secret_hex().as_str());
155
156 let imported = StandardWallet::from_hex(&hex).unwrap();
157 assert_eq!(wallet.address(), imported.address());
158 }
159
160 #[cfg(feature = "rand")]
161 #[test]
162 fn test_from_bytes() {
163 let wallet = StandardWallet::generate().unwrap();
164 let bytes = wallet.secret_bytes();
165
166 let imported = StandardWallet::from_bytes(&bytes).unwrap();
167 assert_eq!(wallet.address(), imported.address());
168 }
169}