use std::fmt::{Display, Formatter, Result as FmtResult};
use alloy_primitives::Address;
use hex::encode as hex_encode;
use k256::{
ecdsa::{SigningKey, VerifyingKey},
elliptic_curve::rand_core::OsRng,
};
use serde::{Deserialize, Serialize};
use super::address::public_key_to_address;
use crate::error::Result;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvmWallet {
pub private_key: String,
pub public_key: String,
pub address: Address,
}
impl Display for EvmWallet {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(
f,
"EVM Wallet:\n address: {}\n private_key: {}\n public_key: {}",
self.address, self.private_key, self.public_key
)
}
}
impl EvmWallet {
pub fn generate_random() -> Result<Self> {
let signing_key = SigningKey::random(&mut OsRng);
let private_key_bytes = signing_key.to_bytes();
let private_key = format!("0x{}", hex_encode(private_key_bytes));
let verifying_key = VerifyingKey::from(&signing_key);
let public_key_point = verifying_key.to_encoded_point(false);
let public_key_bytes = public_key_point.as_bytes();
let public_key = format!("0x{}", hex_encode(public_key_bytes));
let address = public_key_to_address(&public_key)?;
Ok(EvmWallet {
private_key,
public_key,
address,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::is_valid_address_format;
#[test]
fn test_generate_random_wallet() {
let wallet = EvmWallet::generate_random().expect("Failed to generate random wallet");
assert!(!wallet.private_key.is_empty());
assert!(wallet.private_key.starts_with("0x"));
assert_eq!(wallet.private_key.len(), 66);
assert!(!wallet.public_key.is_empty());
assert!(wallet.public_key.starts_with("0x"));
assert_eq!(wallet.public_key.len(), 132);
assert_ne!(wallet.address, Address::ZERO);
println!("Generated wallet: {}", wallet);
}
#[test]
fn test_wallet_generation_uniqueness() {
let wallet1 = EvmWallet::generate_random().expect("Failed to generate first wallet");
let wallet2 = EvmWallet::generate_random().expect("Failed to generate second wallet");
assert_ne!(wallet1.private_key, wallet2.private_key);
assert_ne!(wallet1.public_key, wallet2.public_key);
assert_ne!(wallet1.address, wallet2.address);
}
#[test]
fn test_wallet_format_validation() {
let wallet = EvmWallet::generate_random().expect("Failed to generate wallet");
assert!(wallet.private_key.starts_with("0x"));
let private_key_hex = &wallet.private_key[2..];
assert_eq!(private_key_hex.len(), 64);
assert!(private_key_hex.chars().all(|c| c.is_ascii_hexdigit()));
assert!(wallet.public_key.starts_with("0x04")); let public_key_hex = &wallet.public_key[2..];
assert_eq!(public_key_hex.len(), 130);
assert!(public_key_hex.chars().all(|c| c.is_ascii_hexdigit()));
let address_str = wallet.address.to_string();
assert!(is_valid_address_format(&address_str));
}
#[test]
fn test_wallet_display() {
let wallet = EvmWallet::generate_random().expect("Failed to generate wallet");
let display_str = format!("{}", wallet);
assert!(display_str.contains("EVM Wallet:"));
assert!(display_str.contains("address:"));
assert!(display_str.contains("private_key:"));
assert!(display_str.contains("public_key:"));
assert!(display_str.contains(&wallet.address.to_string()));
}
#[test]
fn test_wallet_serialization() {
let wallet = EvmWallet::generate_random().expect("Failed to generate wallet");
let json = serde_json::to_string(&wallet).expect("Failed to serialize wallet");
let deserialized: EvmWallet = serde_json::from_str(&json).expect("Failed to deserialize wallet");
assert_eq!(wallet.private_key, deserialized.private_key);
assert_eq!(wallet.public_key, deserialized.public_key);
assert_eq!(wallet.address, deserialized.address);
}
}