kobe_evm/
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)]
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 hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
72 let bytes = hex::decode(hex_str).map_err(|_| Error::InvalidHex)?;
73
74 let private_key = SigningKey::from_slice(&bytes).map_err(|_| Error::InvalidPrivateKey)?;
75 let address = Self::derive_address(&private_key);
76
77 Ok(Self {
78 private_key,
79 address,
80 })
81 }
82
83 fn derive_address(private_key: &SigningKey) -> Address {
85 let public_key = private_key.verifying_key();
86 let public_key_bytes = public_key.to_encoded_point(false);
87 public_key_to_address(public_key_bytes.as_bytes())
88 }
89
90 #[inline]
92 #[must_use]
93 pub fn secret_bytes(&self) -> Zeroizing<[u8; 32]> {
94 Zeroizing::new(self.private_key.to_bytes().into())
95 }
96
97 #[inline]
99 #[must_use]
100 pub fn secret_hex(&self) -> Zeroizing<String> {
101 Zeroizing::new(hex::encode(self.private_key.to_bytes()))
102 }
103
104 #[inline]
106 #[must_use]
107 pub fn pubkey_hex(&self) -> String {
108 let public_key = self.private_key.verifying_key();
109 let bytes = public_key.to_encoded_point(false);
110 hex::encode(bytes.as_bytes())
111 }
112
113 #[inline]
115 #[must_use]
116 pub fn address(&self) -> String {
117 to_checksum_address(&self.address)
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[cfg(feature = "rand")]
126 #[test]
127 fn test_generate() {
128 let wallet = StandardWallet::generate().unwrap();
129 assert!(wallet.address().starts_with("0x"));
130 assert_eq!(wallet.address().len(), 42);
131 }
132
133 #[cfg(feature = "rand")]
134 #[test]
135 fn test_from_hex() {
136 let wallet = StandardWallet::generate().unwrap();
137 let hex = wallet.secret_hex();
138
139 let imported = StandardWallet::from_hex(&hex).unwrap();
140 assert_eq!(wallet.address(), imported.address());
141 }
142
143 #[cfg(feature = "rand")]
144 #[test]
145 fn test_from_hex_with_prefix() {
146 use alloc::format;
147 let wallet = StandardWallet::generate().unwrap();
148 let hex = format!("0x{}", wallet.secret_hex().as_str());
149
150 let imported = StandardWallet::from_hex(&hex).unwrap();
151 assert_eq!(wallet.address(), imported.address());
152 }
153
154 #[cfg(feature = "rand")]
155 #[test]
156 fn test_from_bytes() {
157 let wallet = StandardWallet::generate().unwrap();
158 let bytes = wallet.secret_bytes();
159
160 let imported = StandardWallet::from_bytes(&bytes).unwrap();
161 assert_eq!(wallet.address(), imported.address());
162 }
163}