1use crate::schnorr::random_scalar;
2use aes_gcm::aead::{Aead, AeadCore, KeyInit};
3use aes_gcm::{Aes256Gcm, Key, Nonce};
4use argon2::{Argon2, password_hash::SaltString};
5use base64::Engine;
6use base64::engine::general_purpose::STANDARD;
7use blstrs::Scalar;
8use colored::Colorize;
9use dirs::home_dir;
10use ff::PrimeField;
11use rand_core::OsRng;
12use rpassword::read_password;
13use serde::{Deserialize, Serialize};
14use std::fs;
15use std::io::{self, Write};
16use std::path::PathBuf;
17
18#[derive(Serialize, Deserialize)]
20struct Wallet {
21 private_key: String, }
23
24#[derive(Serialize, Deserialize)]
26struct EncryptedData {
27 salt: String,
28 nonce: String,
29 data: String,
30}
31
32pub fn check_and_prepare_seedelf() {
34 println!("{}", "Checking For Existing Seedelf Wallet".bright_blue());
35
36 let home: PathBuf = home_dir().expect("Failed to get home directory");
37 let seedelf_path: PathBuf = home.join(".seedelf");
38
39 if !seedelf_path.exists() {
41 fs::create_dir_all(&seedelf_path).expect("Failed to create .seedelf directory");
42 }
43
44 let contents: Vec<fs::DirEntry> = fs::read_dir(&seedelf_path)
46 .expect("Failed to read .seedelf directory")
47 .filter_map(|entry| entry.ok())
48 .collect::<Vec<_>>();
49
50 if contents.is_empty() {
51 let wallet_name = prompt_wallet_name();
53 let wallet_file_path = seedelf_path.join(format!("{}.wallet", wallet_name));
54 create_wallet(&wallet_file_path);
55 } else {
56 for entry in &contents {
57 if let Ok(file_name) = entry.file_name().into_string() {
58 println!("Found Wallet: {}", file_name.bright_cyan());
59 }
60 }
61 }
62}
63
64fn prompt_wallet_name() -> String {
66 let mut wallet_name = String::new();
67 println!("{}", "\nEnter A Wallet Name:".bright_purple());
68 io::stdout().flush().unwrap();
69 io::stdin()
70 .read_line(&mut wallet_name)
71 .expect("Failed to read wallet name");
72 let final_name: String = wallet_name.trim().to_string();
73 if final_name.is_empty() {
74 println!("{}", "Wallet Must Not Have Empty Name.".red());
75 return prompt_wallet_name();
76 }
77 final_name
78}
79
80fn create_wallet(wallet_path: &PathBuf) {
82 let sk: Scalar = random_scalar(); let private_key_bytes: [u8; 32] = sk.to_repr(); let private_key_hex: String = hex::encode(private_key_bytes);
86
87 let wallet: Wallet = Wallet {
89 private_key: private_key_hex,
90 };
91 let wallet_data: String =
92 serde_json::to_string_pretty(&wallet).expect("Failed to serialize wallet");
93
94 println!(
96 "{}",
97 "\nEnter A Password To Encrypt The Wallet:".bright_purple()
98 );
99 let password: String = read_password().expect("Failed to read password");
100
101 if !password_complexity_check(password.clone()) {
103 println!(
104 "{}",
105 "Passwords Must Contain The Following:\n
106 Minimum Length: At Least 14 Characters.
107 Uppercase Letter: Requires At Least One Uppercase Character.
108 Lowercase Letter: Requires At Least One Lowercase Character.
109 Number: Requires At Least One Digit.
110 Special Character: Requires At Least One Special Symbol.\n"
111 .red()
112 );
113 return create_wallet(wallet_path);
114 }
115
116 println!("{}", "Re-enter the password:".purple());
117 let password_copy: String = read_password().expect("Failed to read password");
118 if password != password_copy {
121 println!("{}", "Passwords Do Not Match; Try Again!".red());
122 return create_wallet(wallet_path);
123 }
124
125 let salt: SaltString = SaltString::generate(&mut OsRng);
126 let mut output_key_material: [u8; 32] = [0u8; 32];
127 let _ = Argon2::default().hash_password_into(
128 password.as_bytes(),
129 salt.to_string().as_bytes(),
130 &mut output_key_material,
131 );
132
133 let key = Key::<Aes256Gcm>::from_slice(&output_key_material);
136 let cipher = Aes256Gcm::new(key);
137 let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
138
139 let encrypted_data = cipher
141 .encrypt(&nonce, wallet_data.as_bytes())
142 .expect("Encryption failed");
143
144 let output: EncryptedData = EncryptedData {
146 salt: salt.as_str().to_string(),
147 nonce: STANDARD.encode(nonce),
148 data: STANDARD.encode(encrypted_data),
149 };
150 let output_data: String =
151 serde_json::to_string_pretty(&output).expect("Failed to serialize wallet");
152
153 fs::write(wallet_path, output_data).expect("Failed to write wallet file");
155 println!(
156 "Wallet Created At: {}",
157 wallet_path.display().to_string().yellow()
158 );
159}
160
161pub fn load_wallet() -> Scalar {
163 let home: PathBuf = home_dir().expect("Failed to get home directory");
164 let seedelf_path: PathBuf = home.join(".seedelf");
165
166 let contents: Vec<fs::DirEntry> = fs::read_dir(&seedelf_path)
168 .expect("Failed to read .seedelf directory")
169 .filter_map(|entry| entry.ok())
170 .collect::<Vec<_>>();
171
172 if contents.is_empty() {
173 panic!("No wallet files found in .seedelf directory");
174 }
175
176 let first_file: &fs::DirEntry = &contents[0];
178 let wallet_path: PathBuf = first_file.path();
179
180 let wallet_data: String = fs::read_to_string(&wallet_path).expect("Failed to read wallet file");
182
183 let encrypted_wallet: EncryptedData =
185 serde_json::from_str(&wallet_data).expect("Failed to parse wallet JSON");
186
187 println!(
189 "{}",
190 "\nEnter The Password To Decrypt The Wallet:".bright_purple()
191 );
192 let password: String = read_password().expect("Failed to read password");
193
194 let salt: SaltString =
196 SaltString::from_b64(&encrypted_wallet.salt).expect("Invalid salt format");
197 let mut output_key_material: [u8; 32] = [0u8; 32];
198 let _ = Argon2::default().hash_password_into(
199 password.as_bytes(),
200 salt.to_string().as_bytes(),
201 &mut output_key_material,
202 );
203
204 let key = Key::<Aes256Gcm>::from_slice(&output_key_material);
205 let cipher = Aes256Gcm::new(key);
206
207 let nonce_bytes = STANDARD
209 .decode(&encrypted_wallet.nonce)
210 .expect("Failed to decode nonce");
211 let nonce = Nonce::from_slice(&nonce_bytes);
212
213 let encrypted_bytes = STANDARD
214 .decode(&encrypted_wallet.data)
215 .expect("Failed to decode encrypted data");
216
217 match cipher.decrypt(nonce, encrypted_bytes.as_ref()) {
219 Ok(decrypted_data) => {
220 let wallet: Wallet = serde_json::from_slice(&decrypted_data)
222 .expect("Failed to parse decrypted wallet JSON");
223
224 let private_key_bytes: Vec<u8> =
226 hex::decode(wallet.private_key).expect("Failed to decode private key hex");
227
228 Scalar::from_repr(private_key_bytes.try_into().expect("Invalid key length"))
230 .expect("Failed to reconstruct Scalar from bytes")
231 }
232 Err(_) => {
233 eprintln!("{}", "Failed To Decrypt; Try Again!".red());
234 load_wallet()
235 }
236 }
237}
238
239pub fn password_complexity_check(password: String) -> bool {
240 if password.len() < 14 {
242 return false;
243 }
244
245 if !password.chars().any(|c| c.is_uppercase()) {
247 return false;
248 }
249
250 if !password.chars().any(|c| c.is_lowercase()) {
252 return false;
253 }
254
255 if !password.chars().any(|c| c.is_ascii_digit()) {
257 return false;
258 }
259
260 if !password
262 .chars()
263 .any(|c| r#"~!@#$%^&*()_-+=<>?/|{}[]:;"'.,"#.contains(c))
264 {
265 return false;
266 }
267 true
268}