1use core::str;
2use std::{
3 fs::{self},
4 io::{self, Read},
5};
6
7use aes::{Aes128, cipher::KeyIvInit};
8use anyhow::Result;
9use bip39::Mnemonic;
10use ctr::{Ctr128BE, cipher::StreamCipher};
11use hmac::{Hmac, Mac};
12use multiversx_chain_core::{std::Bech32Address, types::Address};
13use pbkdf2::pbkdf2;
14use scrypt::{Params, scrypt};
15use serde_json::json;
16use sha2::{Digest, Sha256, Sha512};
17use sha3::Keccak256;
18use zeroize::Zeroize;
19
20use crate::{
21 crypto::{
22 private_key::{PRIVATE_KEY_LENGTH, PrivateKey},
23 public_key::PublicKey,
24 },
25 data::{keystore::*, transaction::Transaction},
26 utils::*,
27};
28
29const EGLD_COIN_TYPE: u32 = 508;
30const HARDENED: u32 = 0x80000000;
31const CIPHER_ALGORITHM_AES_128_CTR: &str = "aes-128-ctr";
32const KDF_SCRYPT: &str = "scrypt";
33
34type HmacSha512 = Hmac<Sha512>;
35type HmacSha256 = Hmac<Sha256>;
36
37#[derive(Copy, Clone, Debug)]
38pub struct Wallet {
39 priv_key: PrivateKey,
40}
41
42impl Wallet {
43 fn seed_from_mnemonic(mnemonic: Mnemonic, password: &str) -> [u8; 64] {
44 let mut salt = String::with_capacity(8 + password.len());
45 salt.push_str("mnemonic");
46 salt.push_str(password);
47
48 let mut seed = [0u8; 64];
49
50 let _ = pbkdf2::<Hmac<Sha512>>(
51 mnemonic.to_string().as_bytes(),
52 salt.as_bytes(),
53 2048,
54 &mut seed,
55 );
56
57 salt.zeroize();
58
59 seed
60 }
61
62 pub fn get_private_key_from_mnemonic(
63 mnemonic: Mnemonic,
64 account: u32,
65 address_index: u32,
66 ) -> PrivateKey {
67 let seed = Self::seed_from_mnemonic(mnemonic, "");
68
69 let serialized_key_len = 32;
70 let hardened_child_padding: u8 = 0;
71
72 let mut digest =
73 HmacSha512::new_from_slice(b"ed25519 seed").expect("HMAC can take key of any size");
74 HmacSha512::new_from_slice(b"ed25519 seed").expect("HMAC can take key of any size");
75 digest.update(&seed);
76 let intermediary: Vec<u8> = digest.finalize().into_bytes().into_iter().collect();
77 let mut key = intermediary[..serialized_key_len].to_vec();
78 let mut chain_code = intermediary[serialized_key_len..].to_vec();
79
80 for child_idx in [
81 44 | HARDENED,
82 EGLD_COIN_TYPE | HARDENED,
83 account | HARDENED, HARDENED,
85 address_index | HARDENED, ] {
87 let mut buff = [vec![hardened_child_padding], key.clone()].concat();
88 buff.push((child_idx >> 24) as u8);
89 buff.push((child_idx >> 16) as u8);
90 buff.push((child_idx >> 8) as u8);
91 buff.push(child_idx as u8);
92
93 digest =
94 HmacSha512::new_from_slice(&chain_code).expect("HMAC can take key of any size");
95 HmacSha512::new_from_slice(&chain_code).expect("HMAC can take key of any size");
96 digest.update(&buff);
97 let intermediary: Vec<u8> = digest.finalize().into_bytes().into_iter().collect();
98 key = intermediary[..serialized_key_len].to_vec();
99 chain_code = intermediary[serialized_key_len..].to_vec();
100 }
101
102 PrivateKey::from_bytes(key.as_slice()).unwrap()
103 }
104
105 pub fn get_wallet_keys_mnemonic(mnemonic_str: String) -> (String, String) {
106 let mnemonic = Mnemonic::parse(mnemonic_str.replace('\n', "")).unwrap();
107 let private_key = Self::get_private_key_from_mnemonic(mnemonic, 0u32, 0u32);
108 let public_key = PublicKey::from(&private_key);
109
110 let public_key_str: &str = &public_key.to_string();
111 let private_key_str: &str = &private_key.to_string();
112
113 (private_key_str.to_string(), public_key_str.to_string())
114 }
115
116 pub fn from_private_key(priv_key: &str) -> Result<Self> {
117 let priv_key = PrivateKey::from_hex_str(priv_key)?;
118 Ok(Self { priv_key })
119 }
120
121 pub fn from_pem_file(file_path: &str) -> Result<Self> {
122 let contents = std::fs::read_to_string(file_path).unwrap();
123 Self::from_pem_file_contents(contents)
124 }
125
126 pub fn from_pem_file_contents(contents: String) -> Result<Self> {
127 let x = pem::parse(contents)?;
128 let x = x.contents()[..PRIVATE_KEY_LENGTH].to_vec();
129 let priv_key_str = std::str::from_utf8(x.as_slice())?;
130 let pri_key = PrivateKey::from_hex_str(priv_key_str)?;
131 Ok(Self { priv_key: pri_key })
132 }
133
134 pub fn get_shard(&self) -> u8 {
135 let address = self.to_address();
136 let address_bytes = address.as_bytes();
137 address_bytes[address_bytes.len() - 1] % 3
138 }
139
140 pub fn get_pem_decoded_content(file: &str) -> Vec<u8> {
141 let pem_content = fs::read_to_string(file).unwrap();
142 let lines: Vec<&str> = pem_content.split('\n').collect();
143 let pem_encoded_keys = format!("{}{}{}", lines[1], lines[2], lines[3]);
144 base64_decode(pem_encoded_keys)
145 }
146
147 pub fn get_wallet_keys_pem(file: &str) -> (String, String) {
148 let pem_decoded_keys = Self::get_pem_decoded_content(file);
149 let (private_key, public_key) = pem_decoded_keys.split_at(pem_decoded_keys.len() / 2);
150 let private_key_str = String::from_utf8(private_key.to_vec()).unwrap();
151 let public_key_str = String::from_utf8(public_key.to_vec()).unwrap();
152
153 (private_key_str, public_key_str)
154 }
155
156 pub fn from_keystore_secret(file_path: &str, insert_password: InsertPassword) -> Result<Self> {
157 let decryption_params = match insert_password {
158 InsertPassword::Plaintext(password) => {
159 Self::validate_keystore_password(file_path, password.to_string()).unwrap_or_else(
160 |e| {
161 panic!("Error: {:?}", e);
162 },
163 )
164 }
165 InsertPassword::StandardInput => {
166 Self::validate_keystore_password(file_path, Self::get_keystore_password())
167 .unwrap_or_else(|e| {
168 panic!("Error: {:?}", e);
169 })
170 }
171 };
172 let priv_key = PrivateKey::from_hex_str(
173 hex::encode(Self::decrypt_secret_key(decryption_params)).as_str(),
174 )?;
175 Ok(Self { priv_key })
176 }
177
178 pub fn get_private_key_from_keystore_secret(
179 file_path: &str,
180 password: &str,
181 ) -> Result<PrivateKey> {
182 let decyption_params = Self::validate_keystore_password(file_path, password.to_string())
183 .unwrap_or_else(|e| {
184 panic!("Error: {:?}", e);
185 });
186 let priv_key = PrivateKey::from_hex_str(
187 hex::encode(Self::decrypt_secret_key(decyption_params)).as_str(),
188 )?;
189 Ok(priv_key)
190 }
191
192 #[deprecated(
193 since = "0.54.0",
194 note = "Renamed to `to_address`, type changed to multiversx_chain_core::types::Address"
195 )]
196 pub fn address(&self) -> Bech32Address {
197 self.to_address().to_bech32_default()
198 }
199
200 pub fn to_address(&self) -> Address {
201 PublicKey::from(&self.priv_key).to_address()
202 }
203
204 pub fn sign_tx(&self, unsign_tx: &Transaction) -> [u8; 64] {
205 let mut unsign_tx = unsign_tx.clone();
206 unsign_tx.signature = None;
207
208 let mut tx_bytes = json!(unsign_tx).to_string().as_bytes().to_vec();
209
210 let should_sign_on_tx_hash = unsign_tx.version >= 2 && unsign_tx.options & 1 > 0;
211 if should_sign_on_tx_hash {
212 let mut h = Keccak256::new();
213 h.update(tx_bytes);
214 tx_bytes = h.finalize().to_vec();
215 }
216
217 self.priv_key.sign(tx_bytes)
218 }
219
220 pub fn get_keystore_password() -> String {
221 println!(
222 "Insert password. Press 'Ctrl-D' (Linux / MacOS) or 'Ctrl-Z' (Windows) when done."
223 );
224 let mut password = String::new();
225 io::stdin().read_to_string(&mut password).unwrap();
226 password = password.trim().to_string();
227 password
228 }
229
230 pub fn validate_keystore_password(
231 path: &str,
232 password: String,
233 ) -> Result<DecryptionParams, WalletError> {
234 let json_body = fs::read_to_string(path).unwrap();
235 let keystore: Keystore = serde_json::from_str(&json_body).unwrap();
236 let ciphertext = hex::decode(&keystore.crypto.ciphertext).unwrap();
237
238 let cipher = &keystore.crypto.cipher;
239 if cipher != CIPHER_ALGORITHM_AES_128_CTR {
240 return Err(WalletError::InvalidCipher);
241 }
242
243 let iv = hex::decode(&keystore.crypto.cipherparams.iv).unwrap();
244 let salt = hex::decode(&keystore.crypto.kdfparams.salt).unwrap();
245 let json_mac = hex::decode(&keystore.crypto.mac).unwrap();
246
247 let kdf = &keystore.crypto.kdf;
248 if kdf != KDF_SCRYPT {
249 return Err(WalletError::InvalidKdf);
250 }
251 let n = keystore.crypto.kdfparams.n as f64;
252 let r = keystore.crypto.kdfparams.r as u64;
253 let p = keystore.crypto.kdfparams.p as u64;
254 let dklen = keystore.crypto.kdfparams.dklen as usize;
255
256 let params = Params::new(n.log2() as u8, r as u32, p as u32, dklen).unwrap();
257
258 let mut derived_key = vec![0u8; 32];
259 scrypt(password.as_bytes(), &salt, ¶ms, &mut derived_key).unwrap();
260
261 let derived_key_first_half = derived_key[0..16].to_vec();
262 let derived_key_second_half = derived_key[16..32].to_vec();
263
264 let mut input_mac = HmacSha256::new_from_slice(&derived_key_second_half).unwrap();
265 input_mac.update(&ciphertext);
266 let computed_mac = input_mac.finalize().into_bytes();
267
268 if computed_mac.to_vec() == json_mac {
269 println!("Password is correct");
270 Ok(DecryptionParams {
271 derived_key_first_half,
272 iv,
273 data: ciphertext,
274 })
275 } else {
276 println!("Password is incorrect");
277 Err(WalletError::InvalidPassword)
278 }
279 }
280
281 pub fn decrypt_secret_key(decryption_params: DecryptionParams) -> Vec<u8> {
282 let mut cipher = Ctr128BE::<Aes128>::new(
283 decryption_params.derived_key_first_half.as_slice().into(),
284 decryption_params.iv.as_slice().into(),
285 );
286 let mut decrypted = decryption_params.data.to_vec();
287 cipher.apply_keystream(&mut decrypted);
288
289 decrypted
290 }
291
292 #[cfg(feature = "wallet-full")]
296 pub fn encrypt_keystore(
297 data: &[u8],
298 hrp: &str,
299 address: &Address,
300 public_key: &str,
301 password: &str,
302 ) -> String {
303 use rand::RngCore;
304
305 let params = Params::new((KDF_N as f64).log2() as u8, KDF_R, KDF_P, KDF_DKLEN).unwrap();
306 let mut rand_salt: [u8; 32] = [0u8; 32];
307 rand::rng().fill_bytes(&mut rand_salt);
308 let salt_hex = hex::encode(rand_salt);
309
310 let mut rand_iv: [u8; 16] = [0u8; 16];
311 rand::rng().fill_bytes(&mut rand_iv);
312 let iv_hex = hex::encode(rand_iv);
313
314 let mut derived_key = vec![0u8; 32];
315 scrypt(password.as_bytes(), &rand_salt, ¶ms, &mut derived_key).unwrap();
316
317 let derived_key_first_half = derived_key[0..16].to_vec();
318 let derived_key_second_half = derived_key[16..32].to_vec();
319
320 let decryption_params = DecryptionParams {
321 derived_key_first_half,
322 iv: rand_iv.to_vec(),
323 data: data.to_vec(),
324 };
325
326 let ciphertext = Self::decrypt_secret_key(decryption_params);
327
328 let mut h = HmacSha256::new_from_slice(&derived_key_second_half).unwrap();
329 h.update(&ciphertext);
330 let mac = h.finalize().into_bytes();
331 let keystore = Keystore {
332 crypto: Crypto {
333 cipher: CIPHER_ALGORITHM_AES_128_CTR.to_string(),
334 cipherparams: CryptoParams { iv: iv_hex },
335 ciphertext: hex::encode(&ciphertext),
336 kdf: KDF_SCRYPT.to_string(),
337 kdfparams: KdfParams {
338 salt: salt_hex,
339 n: KDF_N,
340 r: KDF_R,
341 p: KDF_P,
342 dklen: KDF_DKLEN as u32,
343 },
344 mac: hex::encode(mac),
345 },
346 id: uuid::Uuid::new_v4().to_string(),
347 version: KEYSTORE_VERSION,
348 kind: "secretKey".to_string(),
349 address: public_key.to_string(),
350 bech32: address.to_bech32(hrp).bech32,
351 };
352
353 let mut keystore_json: String = serde_json::to_string_pretty(&keystore).unwrap();
354 keystore_json.push('\n');
355 keystore_json
356 }
357
358 pub fn generate_pem_content(
359 hrp: &str,
360 address: &Address,
361 private_key: &str,
362 public_key: &str,
363 ) -> String {
364 let concat_keys = format!("{}{}", private_key, public_key);
365 let concat_keys_b64 = base64_encode(concat_keys);
366
367 let formatted_key = concat_keys_b64
369 .as_bytes()
370 .chunks(64)
371 .map(|chunk| std::str::from_utf8(chunk).unwrap())
372 .collect::<Vec<&str>>()
373 .join("\n");
374
375 let address_bech32 = Bech32Address::encode_address(hrp, address.clone());
376 let pem_content = format!(
377 "-----BEGIN PRIVATE KEY for {address_bech32}-----\n{formatted_key}\n-----END PRIVATE KEY for {address_bech32}-----\n"
378 );
379
380 pem_content
381 }
382}