1use k256::{
2 ecdsa::SigningKey,
3 elliptic_curve::rand_core::OsRng,
4};
5use sha3::{Digest, Keccak256};
6
7pub struct EthKeyPair {
8 pub address: String,
9 pub privkey: String,
10 pub pubkey_compressed: String,
11 pub pubkey_uncompressed: String,
12}
13
14impl EthKeyPair {
15 pub fn new() -> Self {
17 let private_key = SigningKey::random(&mut OsRng);
18 let privkey_hex = hex::encode(private_key.to_bytes());
19
20 let public_key = private_key.verifying_key();
21 let pubkey_compressed = public_key.to_encoded_point(true).as_bytes().to_vec();
23 let pubkey_compressed_hex = hex::encode(&pubkey_compressed);
24 let pubkey_uncompressed = public_key.to_encoded_point(false).as_bytes().to_vec();
26 let pubkey_uncompressed_hex = hex::encode(&pubkey_uncompressed);
27
28 let hash = Keccak256::digest(&pubkey_uncompressed[1..]); let address = format!("0x{}", hex::encode(&hash[12..]));
31
32 Self {
33 address,
34 privkey: privkey_hex,
35 pubkey_compressed:pubkey_compressed_hex,
36 pubkey_uncompressed:pubkey_uncompressed_hex,
37 }
38 }
39 pub async fn sign_message(&self, message: &str) -> Result<String, String> {
41 use ethers::core::types::Signature;
42 use ethers::signers::{LocalWallet, Signer};
43 use ethers::core::k256::SecretKey;
44 use std::time::{SystemTime, UNIX_EPOCH};
45
46 let secret_bytes = hex::decode(&self.privkey).map_err(|e| e.to_string())?;
48 let secret_key = SecretKey::from_slice(&secret_bytes).map_err(|e| e.to_string())?;
49 let wallet = LocalWallet::from(secret_key);
50
51 let address = &self.address;
52 let timestamps = SystemTime::now()
53 .duration_since(UNIX_EPOCH)
54 .unwrap()
55 .as_secs();
56 let message_with_timestamp = format!("{}:{}", message, timestamps);
57
58 let wallet_addr = format!("0x{}", hex::encode(wallet.address().as_bytes()));
60 debug_assert_eq!(wallet_addr.to_lowercase(), address.to_lowercase());
61
62 let signature: Signature = wallet
64 .sign_message(message_with_timestamp.clone())
65 .await
66 .map_err(|e| e.to_string())?;
67
68 let result = serde_json::json!({
70 "address": address,
71 "signed_message": message_with_timestamp,
72 "signature": format!("0x{}", hex::encode(signature.to_vec())),
73 });
74 Ok(result.to_string())
75 }
76}
77
78 pub fn verify_ethereum_signature(message: &str, signature: &str, address: &str) -> Result<bool, String> {
80 use ethers::core::types::{Address, Signature};
81 use ethers::utils::hash_message;
82 use std::str::FromStr;
83
84 let signature = Signature::from_str(signature).map_err(|e| e.to_string())?;
86 let expected_address = Address::from_str(address).map_err(|e| e.to_string())?;
87
88 let message_hash = hash_message(message);
90
91 let recovered_address = signature.recover(message_hash).map_err(|e| e.to_string())?;
93
94 Ok(recovered_address == expected_address)
96 }
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use hex;
102
103 #[tokio::test]
104 async fn test_sign_message() {
105 let keypair = EthKeyPair::new();
106 let message = "Hello, world!";
107 let signature = keypair.sign_message(message).await.unwrap_or_else(|e| panic!("Failed to sign message: {}", e));
108
109 let sig_json: serde_json::Value = serde_json::from_str(&signature).unwrap_or_else(|e| panic!("Failed to parse signature JSON: {}", e));
111 println!("Signature JSON: {}", serde_json::to_string_pretty(&sig_json).unwrap_or_else(|e| panic!("Failed to format JSON: {}", e)));
112
113 assert!(sig_json.get("address").is_some());
115 assert!(sig_json.get("signed_message").is_some());
116 assert!(sig_json.get("signature").is_some());
117 }
118
119 #[test]
120 fn test_eth_keypair_generation() {
121 let keypair = EthKeyPair::new();
122
123 assert_eq!(keypair.address.len(), 42);
125 assert!(keypair.address.starts_with("0x"));
126
127 assert_eq!(keypair.pubkey_compressed.len(), 66);
129
130 let compressed_bytes = hex::decode(&keypair.pubkey_compressed).unwrap();
132 assert!(
133 compressed_bytes[0] == 0x02 || compressed_bytes[0] == 0x03,
134 "Compressed pubkey should start with 0x02 or 0x03"
135 );
136
137 assert_eq!(keypair.pubkey_uncompressed.len(), 130);
139
140 let uncompressed_bytes = hex::decode(&keypair.pubkey_uncompressed).unwrap();
142 assert_eq!(uncompressed_bytes[0], 0x04,
143 "Uncompressed pubkey should start with 0x04");
144
145 println!("Private Key: {}", keypair.privkey);
147 println!("Compressed Public Key: {}", keypair.pubkey_compressed);
148 println!("Uncompressed Public Key: {}", keypair.pubkey_uncompressed);
149 println!("Ethereum Address: {}", keypair.address);
150 }
151
152 #[tokio::test]
153 async fn test_check_signature_is_valid() {
154 let keypair = EthKeyPair::new();
155 let message = "Hello, world!";
156
157 let signature = keypair.sign_message(message)
159 .await
160 .unwrap_or_else(|e| panic!("Failed to sign message: {}", e));
161
162 let sig_json: serde_json::Value = serde_json::from_str(&signature)
164 .unwrap_or_else(|e| panic!("Failed to parse signature JSON: {}", e));
165
166 let address = sig_json["address"].as_str()
168 .expect("Missing 'address' field in signature JSON");
169 let signed_message = sig_json["signed_message"].as_str()
170 .expect("Missing 'signed_message' field in signature JSON");
171 let signature_hex = sig_json["signature"].as_str()
172 .expect("Missing 'signature' field in signature JSON");
173
174 let is_valid = verify_ethereum_signature(
176 signed_message,
177 signature_hex,
178 address
179 ).expect("Signature verification failed");
180
181 assert!(is_valid, "Signature verification failed");
182 }
183}