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_private_key_hex(hex_str: &str) -> Result<Self, Error> {
66 let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
67 let bytes = hex::decode(hex_str).map_err(|_| Error::InvalidHex)?;
68
69 let private_key = SigningKey::from_slice(&bytes).map_err(|_| Error::InvalidPrivateKey)?;
70 let address = Self::derive_address(&private_key);
71
72 Ok(Self {
73 private_key,
74 address,
75 })
76 }
77
78 fn derive_address(private_key: &SigningKey) -> Address {
80 let public_key = private_key.verifying_key();
81 let public_key_bytes = public_key.to_encoded_point(false);
82 public_key_to_address(public_key_bytes.as_bytes())
83 }
84
85 #[inline]
87 #[must_use]
88 pub fn private_key_hex(&self) -> Zeroizing<String> {
89 Zeroizing::new(hex::encode(self.private_key.to_bytes()))
90 }
91
92 #[inline]
94 #[must_use]
95 pub fn public_key_hex(&self) -> String {
96 let public_key = self.private_key.verifying_key();
97 let bytes = public_key.to_encoded_point(false);
98 hex::encode(bytes.as_bytes())
99 }
100
101 #[must_use]
103 pub const fn address(&self) -> &Address {
104 &self.address
105 }
106
107 #[inline]
109 #[must_use]
110 pub fn address_string(&self) -> String {
111 to_checksum_address(&self.address)
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[cfg(feature = "rand")]
120 #[test]
121 fn test_generate() {
122 let wallet = StandardWallet::generate().unwrap();
123 assert!(wallet.address_string().starts_with("0x"));
124 assert_eq!(wallet.address_string().len(), 42);
125 }
126
127 #[cfg(feature = "rand")]
128 #[test]
129 fn test_from_private_key() {
130 let wallet = StandardWallet::generate().unwrap();
131 let pk_hex = wallet.private_key_hex();
132
133 let imported = StandardWallet::from_private_key_hex(&pk_hex).unwrap();
134 assert_eq!(wallet.address_string(), imported.address_string());
135 }
136
137 #[cfg(feature = "rand")]
138 #[test]
139 fn test_from_private_key_with_prefix() {
140 use alloc::format;
141 let wallet = StandardWallet::generate().unwrap();
142 let pk_hex = format!("0x{}", wallet.private_key_hex().as_str());
143
144 let imported = StandardWallet::from_private_key_hex(&pk_hex).unwrap();
145 assert_eq!(wallet.address_string(), imported.address_string());
146 }
147}