use k256::{
ecdsa::SigningKey,
elliptic_curve::rand_core::OsRng,
PublicKey,
SecretKey,
};
use sha3::{Digest, Keccak256};
use hkdf::Hkdf;
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
use aes_gcm::aead::Aead;
use rand::RngCore;
pub struct Wallet {
pub address: String,
pub privkey: String,
pub pubkey_compressed: String,
pub pubkey_uncompressed: String,
}
impl Wallet {
pub fn new() -> Self {
Self::from_private_key(None).expect("Failed to generate wallet with random key")
}
pub fn from_private_key(private_key_hex: Option<&str>) -> Result<Self, String> {
let private_key = match private_key_hex {
Some(hex_key) => {
let key_bytes = hex::decode(hex_key.trim_start_matches("0x"))
.map_err(|e| format!("Invalid private key format: {}", e))?;
let secret_key = SecretKey::from_slice(&key_bytes)
.map_err(|e| format!("Invalid private key: {}", e))?;
SigningKey::from(secret_key)
},
None => {
SigningKey::random(&mut OsRng)
}
};
let privkey_hex = format!("0x{}", hex::encode(private_key.to_bytes()));
let public_key = private_key.verifying_key();
let pubkey_compressed = public_key.to_encoded_point(true).as_bytes().to_vec();
let pubkey_compressed_hex = format!("0x{}", hex::encode(&pubkey_compressed));
let pubkey_uncompressed = public_key.to_encoded_point(false).as_bytes().to_vec();
let pubkey_uncompressed_hex = format!("0x{}", hex::encode(&pubkey_uncompressed));
let hash = Keccak256::digest(&pubkey_uncompressed[1..]); let address = format!("0x{}", hex::encode(&hash[12..]));
Ok(Self {
address,
privkey: privkey_hex,
pubkey_compressed: pubkey_compressed_hex,
pubkey_uncompressed: pubkey_uncompressed_hex,
})
}
pub async fn sign_message(&self, message: &str) -> Result<String, String> {
use ethers::core::types::Signature;
use ethers::signers::{LocalWallet, Signer};
use ethers::core::k256::SecretKey;
use std::time::{SystemTime, UNIX_EPOCH};
let secret_bytes = hex::decode(self.privkey.trim_start_matches("0x")).map_err(|e| e.to_string())?;
let secret_key = SecretKey::from_slice(&secret_bytes).map_err(|e| e.to_string())?;
let wallet = LocalWallet::from(secret_key);
let address = &self.address;
let timestamps = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| format!("Failed to get system time: {}", e))?
.as_secs();
let message_with_timestamp = format!("{}:{}", message, timestamps);
let wallet_addr = format!("0x{}", hex::encode(wallet.address().as_bytes()));
debug_assert_eq!(wallet_addr.to_lowercase(), address.to_lowercase());
let signature: Signature = wallet
.sign_message(message_with_timestamp.clone())
.await
.map_err(|e| e.to_string())?;
let result = serde_json::json!({
"address": address,
"signed_message": message_with_timestamp,
"signature": format!("0x{}", hex::encode(signature.to_vec())),
});
Ok(result.to_string())
}
pub fn encrypt_message(&self, message: &str, recipient_compressed_pubkey: &str) -> Result<String, String> {
let recipient_pubkey_bytes = hex::decode(recipient_compressed_pubkey.trim_start_matches("0x"))
.map_err(|e| format!("Invalid recipient public key: {}", e))?;
let recipient_public_key = PublicKey::from_sec1_bytes(&recipient_pubkey_bytes)
.map_err(|e| format!("Failed to parse recipient public key: {}", e))?;
let ephemeral_private_key = SigningKey::random(&mut OsRng);
let ephemeral_public_key = ephemeral_private_key.verifying_key();
let shared_secret = {
use ethers::core::k256::ecdh::diffie_hellman;
let secret = diffie_hellman(
ephemeral_private_key.as_nonzero_scalar(),
recipient_public_key.as_affine()
);
secret.raw_secret_bytes().to_vec()
};
let kdf = Hkdf::<Keccak256>::new(None, &shared_secret);
let mut derived_key = [0u8; 32]; kdf.expand(b"EIP-5630-ECIES-AES-256-GCM", &mut derived_key)
.map_err(|_| "HKDF expansion failed".to_string())?;
let mut nonce_bytes = [0u8; 12]; rand::rng().fill_bytes(&mut nonce_bytes);
let cipher = Aes256Gcm::new_from_slice(&derived_key)
.map_err(|_| "Failed to create cipher".to_string())?;
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher.encrypt(nonce, message.as_bytes().as_ref())
.map_err(|e| format!("Encryption failed: {}", e))?;
let ephemeral_pubkey_bytes = ephemeral_public_key.to_encoded_point(true).as_bytes().to_vec();
let encrypted_data = [
ephemeral_pubkey_bytes.as_slice(),
&nonce_bytes,
ciphertext.as_slice(),
].concat();
Ok(format!("0x{}", hex::encode(encrypted_data)))
}
pub fn decrypt_message(&self, encrypted_message: &str) -> Result<String, String> {
let encrypted_data = hex::decode(encrypted_message.trim_start_matches("0x"))
.map_err(|e| format!("Invalid encrypted message: {}", e))?;
if encrypted_data.len() < 33 + 12 + 16 {
return Err("Encrypted message is too short".to_string());
}
let ephemeral_pubkey_bytes = &encrypted_data[0..33]; let nonce_bytes = &encrypted_data[33..45]; let ciphertext = &encrypted_data[45..];
let ephemeral_public_key = PublicKey::from_sec1_bytes(ephemeral_pubkey_bytes)
.map_err(|e| format!("Failed to parse ephemeral public key: {}", e))?;
let private_key_bytes = hex::decode(self.privkey.trim_start_matches("0x"))
.map_err(|e| format!("Failed to decode private key: {}", e))?;
let secret_key = SecretKey::from_slice(&private_key_bytes)
.map_err(|e| format!("Failed to create secret key: {}", e))?;
let private_key = SigningKey::from(secret_key);
let shared_secret = {
use ethers::core::k256::ecdh::diffie_hellman;
let secret = diffie_hellman(
private_key.as_nonzero_scalar(),
ephemeral_public_key.as_affine()
);
secret.raw_secret_bytes().to_vec()
};
let kdf = Hkdf::<Keccak256>::new(None, &shared_secret);
let mut derived_key = [0u8; 32]; kdf.expand(b"EIP-5630-ECIES-AES-256-GCM", &mut derived_key)
.map_err(|_| "HKDF expansion failed".to_string())?;
let cipher = Aes256Gcm::new_from_slice(&derived_key)
.map_err(|_| "Failed to create cipher".to_string())?;
let nonce = Nonce::from_slice(nonce_bytes);
let plaintext = cipher.decrypt(nonce, ciphertext.as_ref())
.map_err(|_| "Decryption failed".to_string())?;
let message = String::from_utf8(plaintext)
.map_err(|e| format!("Failed to decode message: {}", e))?;
Ok(message)
}
}