1mod currency;
111mod read;
112mod write;
113
114pub use currency::{Currency, ExpectedCurrency, G1_CURRENCY, G1_TEST_CURRENCY};
115pub use read::{read_dewif_content, read_dewif_log_n, read_dewif_meta, DewifReadError};
116pub use write::create_dewif_v1;
117#[allow(deprecated)]
118pub use write::create_dewif_v1_legacy;
119
120#[cfg(feature = "bip32-ed25519")]
121use crate::keys::{KeyPair as _, KeysAlgo};
122use crate::scrypt::{params::ScryptParams, scrypt};
123use crate::seeds::{Seed42, Seed64};
124use crate::{hashs::Hash, rand::UnspecifiedRandError};
125
126const HEADERS_LEN: usize = 8;
127
128static VERSION_V1: &[u8] = &[0, 0, 0, 1];
130const V1_CHECKSUM_LEN: usize = 8;
131const V1_NONCE_LEN: usize = 12;
132const V1_CLEAR_HEADERS_LEN: usize = 2 + V1_NONCE_LEN;
133
134const V1_ED25519_ENCRYPTED_BYTES_LEN: usize = 64;
136const V1_ED25519_DATA_LEN: usize = V1_ED25519_ENCRYPTED_BYTES_LEN + V1_CLEAR_HEADERS_LEN; const V1_ED25519_BYTES_LEN: usize = HEADERS_LEN + V1_ED25519_DATA_LEN; const V1_ED25519_UNENCRYPTED_BYTES_LEN: usize =
139 V1_ED25519_BYTES_LEN - V1_ED25519_ENCRYPTED_BYTES_LEN; const V1_BIP32_ED25519_ENCRYPTED_BYTES_LEN: usize = 42;
143const V1_BIP32_ED25519_DATA_LEN: usize =
144 V1_BIP32_ED25519_ENCRYPTED_BYTES_LEN + V1_CLEAR_HEADERS_LEN;
145const V1_BIP32_ED25519_BYTES_LEN: usize = HEADERS_LEN + V1_BIP32_ED25519_DATA_LEN;
146const V1_BIP32_ED25519_UNENCRYPTED_BYTES_LEN: usize =
147 V1_BIP32_ED25519_BYTES_LEN - V1_BIP32_ED25519_ENCRYPTED_BYTES_LEN;
148
149type Checksum = [u8; V1_CHECKSUM_LEN];
150type Nonce = [u8; V1_NONCE_LEN];
151
152#[derive(Debug, PartialEq)]
153pub struct DewifContent {
155 pub meta: DewifMeta,
157 pub payload: DewifPayload,
159}
160
161#[derive(Clone, Copy, Debug, PartialEq)]
163pub struct DewifMeta {
164 pub algo: KeysAlgo,
166 pub currency: Currency,
168 pub log_n: u8,
170 pub version: u32,
172}
173
174#[derive(Debug, PartialEq)]
175pub enum DewifPayload {
177 Ed25519(crate::keys::ed25519::Ed25519KeyPair),
179 Bip32Ed25519(crate::mnemonic::Mnemonic),
181}
182
183pub fn change_dewif_passphrase(
185 file_content: &str,
186 old_passphrase: &str,
187 new_passphrase: &str,
188) -> Result<String, DewifReadError> {
189 let DewifContent {
190 meta:
191 DewifMeta {
192 algo: _,
193 currency,
194 log_n,
195 version,
196 },
197 payload,
198 } = read_dewif_content(ExpectedCurrency::Any, file_content, old_passphrase)?;
199 if version == 1 {
200 let mut bytes = base64::decode(file_content).map_err(DewifReadError::InvalidBase64Str)?;
201 match payload {
202 DewifPayload::Ed25519(kp) => {
203 let seed = read::get_dewif_seed_unchecked(&mut bytes[8..], old_passphrase);
204 write::write_dewif_v1_ed25519(
205 currency,
206 log_n,
207 new_passphrase,
208 &kp.public_key(),
209 &seed,
210 )
211 .map_err(|_| DewifReadError::UnspecifiedRandError)
212 }
213 #[cfg(feature = "bip32-ed25519")]
214 DewifPayload::Bip32Ed25519(mnemonic) => {
215 write::write_dewif_v1_bip_ed25519(currency, log_n, new_passphrase, &mnemonic)
216 .map_err(|_| DewifReadError::UnspecifiedRandError)
217 }
218 }
219 } else {
220 Err(DewifReadError::UnsupportedVersion { actual: version })
221 }
222}
223
224fn compute_checksum(nonce: &Nonce, language_code: u8, mnemonic_entropy: &[u8]) -> Checksum {
225 let mut cs_bytes = [0u8; V1_CHECKSUM_LEN];
226 let hash = crate::hashs::Hash::compute_multipart(&[
227 &nonce[..],
228 &[language_code, mnemonic_entropy.len() as u8],
229 mnemonic_entropy,
230 ]);
231 cs_bytes.copy_from_slice(&hash.0[..8]);
232 cs_bytes
233}
234
235#[cfg(not(test))]
236fn gen_nonce() -> Result<Nonce, UnspecifiedRandError> {
237 let mut nonce = [0u8; V1_NONCE_LEN];
238 crate::rand::gen_random_bytes(&mut nonce[..])?;
239 Ok(nonce)
240}
241#[cfg(test)]
242#[allow(clippy::unnecessary_wraps)]
243fn gen_nonce() -> Result<Nonce, UnspecifiedRandError> {
244 Ok([42u8; V1_NONCE_LEN])
245}
246
247fn gen_xor_seed42(log_n: u8, nonce: Nonce, passphrase: &str) -> Seed42 {
248 let salt = Hash::compute_multipart(&[b"dewif", &nonce[..], passphrase.as_bytes()]);
249 let mut seed_bytes = [0u8; 42];
250 scrypt(
251 passphrase.as_bytes(),
252 salt.as_ref(),
253 &ScryptParams { log_n, r: 16, p: 1 },
254 &mut seed_bytes,
255 );
256 Seed42::new(seed_bytes)
257}
258
259fn gen_xor_seed64(log_n: u8, nonce: Nonce, passphrase: &str) -> Seed64 {
260 let salt = Hash::compute_multipart(&[b"dewif", &nonce[..], passphrase.as_bytes()]);
261 let mut seed_bytes = [0u8; 64];
262 scrypt(
263 passphrase.as_bytes(),
264 salt.as_ref(),
265 &ScryptParams { log_n, r: 16, p: 1 },
266 &mut seed_bytes,
267 );
268 Seed64::new(seed_bytes)
269}
270
271fn read_nonce(bytes: &[u8]) -> Nonce {
272 let mut nonce = [0u8; V1_NONCE_LEN];
273 nonce.copy_from_slice(bytes);
274 nonce
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use crate::bases::b58::ToBase58 as _;
281 use crate::keys::ed25519::KeyPairFromSeed32Generator;
282 use crate::seeds::Seed32;
283 use unwrap::unwrap;
284
285 #[test]
286 fn dewif_v1() {
287 let written_keypair = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
288 let currency = Currency::from(G1_TEST_CURRENCY);
289
290 let dewif_content_str = unwrap!(write::write_dewif_v1_ed25519(
291 currency,
292 12,
293 "toto",
294 &written_keypair.public_key(),
295 &written_keypair.seed(),
296 ));
297
298 let dewif_content = unwrap!(read_dewif_content(
299 ExpectedCurrency::Specific(currency),
300 &dewif_content_str,
301 "toto"
302 ));
303
304 assert_eq!(
305 DewifPayload::Ed25519(written_keypair.clone()),
306 dewif_content.payload,
307 );
308
309 let new_dewif_content =
311 unwrap!(change_dewif_passphrase(&dewif_content_str, "toto", "titi"));
312
313 let dewif_content = unwrap!(read_dewif_content(
314 ExpectedCurrency::Specific(currency),
315 &new_dewif_content,
316 "titi"
317 ));
318
319 assert_eq!(
320 DewifPayload::Ed25519(written_keypair),
321 dewif_content.payload,
322 );
323 }
324
325 #[test]
326 fn dewif_v1_corrupted() -> Result<(), ()> {
327 let written_keypair = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
328 let currency = Currency::from(G1_TEST_CURRENCY);
329
330 let mut dewif_content = unwrap!(write::write_dewif_v1_ed25519(
331 currency,
332 12,
333 "toto",
334 &written_keypair.public_key(),
335 &written_keypair.seed(),
336 ));
337
338 let dewif_bytes_mut = unsafe { dewif_content.as_bytes_mut() };
340 dewif_bytes_mut[13] = 0x52;
341
342 if let Err(DewifReadError::CorruptedContent) =
343 read_dewif_content(ExpectedCurrency::Specific(currency), &dewif_content, "toto")
344 {
345 Ok(())
346 } else {
347 panic!("dewif content must be corrupted.")
348 }
349 }
350
351 #[test]
352 fn dewif_v1_bip32() -> Result<(), crate::mnemonic::MnemonicError> {
353 let mnemonic = crate::mnemonic::Mnemonic::from_phrase(
354 "crop cash unable insane eight faith inflict route frame loud box vibrant",
355 crate::mnemonic::Language::English,
356 )?;
357 let mnemonic_copy = crate::mnemonic::Mnemonic::from_phrase(
358 "crop cash unable insane eight faith inflict route frame loud box vibrant",
359 crate::mnemonic::Language::English,
360 )?;
361 let currency = Currency::from(G1_TEST_CURRENCY);
362
363 let dewif_content_str = unwrap!(write::write_dewif_v1_bip_ed25519(
364 currency, 12, "toto", &mnemonic
365 ));
366
367 let dewif_content = unwrap!(read_dewif_content(
368 ExpectedCurrency::Specific(currency),
369 &dewif_content_str,
370 "toto"
371 ));
372
373 assert_eq!(DewifPayload::Bip32Ed25519(mnemonic), dewif_content.payload,);
374
375 let new_dewif_content_str =
377 unwrap!(change_dewif_passphrase(&dewif_content_str, "toto", "titi"));
378
379 let new_dewif_content = unwrap!(read_dewif_content(
380 ExpectedCurrency::Specific(currency),
381 &new_dewif_content_str,
382 "titi"
383 ));
384
385 assert_eq!(
386 DewifPayload::Bip32Ed25519(mnemonic_copy),
387 new_dewif_content.payload,
388 );
389
390 Ok(())
391 }
392
393 #[test]
394 #[allow(deprecated)]
395 fn dewif_v1_legacy() -> Result<(), DewifReadError> {
396 let currency = Currency::from(G1_CURRENCY);
397 let dewif = unwrap!(create_dewif_v1_legacy(
398 currency,
399 12,
400 "pass".to_owned(),
401 "salt".to_owned(),
402 "toto"
403 ));
404
405 if let DewifContent {
406 payload: DewifPayload::Ed25519(key_pair),
407 ..
408 } = read_dewif_content(ExpectedCurrency::Specific(currency), &dewif, "toto")?
409 {
410 assert_eq!(
411 "3YumN7F7D8c2hmkHLHf3ZD8wc3tBHiECEK9zLPkaJtAF",
412 &key_pair.public_key().to_base58()
413 );
414 } else {
415 panic!("corrupted dewif");
416 }
417
418 Ok(())
419 }
420}