1use super::{Currency, DewifContent, DewifPayload, ExpectedCurrency};
19use crate::keys::{KeyPair, Signator};
20use crate::seeds::{Seed32, SEED_32_SIZE_IN_BYTES};
21use crate::{
22 keys::{
23 ed25519::{PublicKey, PUBKEY_DATAS_SIZE_IN_BYTES},
24 KeysAlgo,
25 },
26 mnemonic::{Language, Mnemonic, MnemonicError},
27};
28use byteorder::ByteOrder;
29use std::{
30 convert::{TryFrom, TryInto},
31 hint::unreachable_unchecked,
32};
33use thiserror::Error;
34
35#[derive(Clone, Debug, Error)]
37pub enum DewifReadError {
38 #[error("DEWIF file content is corrupted.")]
40 CorruptedContent,
41 #[error("Invalid base 64 string: {0}.")]
43 InvalidBase64Str(base64::DecodeError),
44 #[error("Invalid format.")]
46 InvalidFormat,
47 #[error("Too short content.")]
49 TooShortContent,
50 #[error("Too long content.")]
52 TooLongContent,
53 #[error("Unexpected currency '{actual}', expected: '{expected}'.")]
55 UnexpectedCurrency {
56 expected: ExpectedCurrency,
58 actual: Currency,
60 },
61 #[error("unknown algorithm")]
63 UnknownAlgo,
64 #[error("Unspecified rand error")]
66 UnspecifiedRandError,
67 #[error("Version {actual} is not supported. Supported versions: [1, 2].")]
69 UnsupportedVersion {
70 actual: u32,
72 },
73}
74
75pub fn read_dewif_log_n(
77 expected_currency: ExpectedCurrency,
78 file_content: &str,
79) -> Result<u8, DewifReadError> {
80 let bytes = base64::decode(file_content).map_err(DewifReadError::InvalidBase64Str)?;
81
82 if bytes.len() < 9 {
83 return Err(DewifReadError::TooShortContent);
84 }
85
86 let version = byteorder::BigEndian::read_u32(&bytes[0..4]);
87 let currency = Currency::from(byteorder::BigEndian::read_u32(&bytes[4..8]));
88
89 if !expected_currency.is_valid(currency) {
90 return Err(DewifReadError::UnexpectedCurrency {
91 expected: expected_currency,
92 actual: currency,
93 });
94 }
95
96 match version {
97 1 => Ok(bytes[8]),
98 other_version => Err(DewifReadError::UnsupportedVersion {
99 actual: other_version,
100 }),
101 }
102}
103
104pub fn read_dewif_meta(file_content: &str) -> Result<super::DewifMeta, DewifReadError> {
106 let bytes = base64::decode(file_content).map_err(DewifReadError::InvalidBase64Str)?;
107
108 if bytes.len() < 10 {
109 return Err(DewifReadError::TooShortContent);
110 }
111
112 let version = byteorder::BigEndian::read_u32(&bytes[0..4]);
113 let currency = Currency::from(byteorder::BigEndian::read_u32(&bytes[4..8]));
114
115 let log_n = bytes[8];
116 let algo = KeysAlgo::from_u8(bytes[9]).map_err(|_| DewifReadError::UnknownAlgo)?;
117
118 Ok(super::DewifMeta {
119 algo,
120 currency,
121 log_n,
122 version,
123 })
124}
125
126pub fn read_dewif_content(
128 expected_currency: ExpectedCurrency,
129 file_content: &str,
130 passphrase: &str,
131) -> Result<DewifContent, DewifReadError> {
132 let meta = read_dewif_meta(file_content)?;
133
134 let mut bytes = base64::decode(file_content).map_err(DewifReadError::InvalidBase64Str)?;
135
136 let version = meta.version;
137 let currency = meta.currency;
138
139 if !expected_currency.is_valid(currency) {
140 return Err(DewifReadError::UnexpectedCurrency {
141 expected: expected_currency,
142 actual: currency,
143 });
144 }
145
146 let payload = match version {
147 1 => read_dewif_v1(&mut bytes[8..], passphrase)?,
148 other_version => {
149 return Err(DewifReadError::UnsupportedVersion {
150 actual: other_version,
151 });
152 }
153 };
154 Ok(DewifContent { meta, payload })
155}
156
157fn read_dewif_v1(bytes: &mut [u8], passphrase: &str) -> Result<DewifPayload, DewifReadError> {
158 match bytes.len() {
159 len if len < super::V1_BIP32_ED25519_DATA_LEN => {
160 return Err(DewifReadError::TooShortContent)
161 }
162 len if len > super::V1_ED25519_DATA_LEN => return Err(DewifReadError::TooLongContent),
163 _ => (),
164 }
165
166 let log_n = bytes[0];
168 let algo = KeysAlgo::from_u8(bytes[1]).map_err(|_| DewifReadError::UnknownAlgo)?;
170 let nonce = super::read_nonce(&bytes[2..14]);
172
173 match algo {
174 KeysAlgo::Ed25519 => {
175 let mut decrypted_bytes = [0u8; 64];
177 crate::xor_cipher::xor_cipher(
178 &bytes[super::V1_CLEAR_HEADERS_LEN..],
179 super::gen_xor_seed64(log_n, nonce, passphrase).as_ref(),
180 &mut decrypted_bytes,
181 );
182
183 Ok(DewifPayload::Ed25519(bytes_to_checked_keypair(
185 &decrypted_bytes,
186 )?))
187 }
188 KeysAlgo::Bip32Ed25519 => {
189 let mut decrypted_bytes = [0u8; 42];
191 crate::xor_cipher::xor_cipher(
192 &bytes[super::V1_CLEAR_HEADERS_LEN..(super::V1_CLEAR_HEADERS_LEN + 42)],
193 super::gen_xor_seed42(log_n, nonce, passphrase).as_ref(),
194 &mut decrypted_bytes,
195 );
196
197 let mnemonic = get_dewif_mnemonic(&decrypted_bytes)
199 .map_err(|_| DewifReadError::CorruptedContent)?;
200 let checksum = super::compute_checksum(&nonce, decrypted_bytes[0], mnemonic.entropy());
201 let expected_checksum = &decrypted_bytes[34..];
202 if &checksum[..] != expected_checksum {
203 Err(DewifReadError::CorruptedContent)
204 } else {
205 Ok(DewifPayload::Bip32Ed25519(mnemonic))
206 }
207 }
208 }
209}
210
211pub(super) fn get_dewif_seed_unchecked(bytes: &mut [u8], passphrase: &str) -> Seed32 {
213 let log_n = bytes[0];
215 let nonce = super::read_nonce(&bytes[2..14]);
217
218 let mut decrypted_bytes = [0u8; 64];
220 crate::xor_cipher::xor_cipher(
221 &bytes[super::V1_CLEAR_HEADERS_LEN..],
222 super::gen_xor_seed64(log_n, nonce, passphrase).as_ref(),
223 &mut decrypted_bytes,
224 );
225
226 Seed32::new(
228 (&decrypted_bytes[..SEED_32_SIZE_IN_BYTES])
229 .try_into()
230 .unwrap_or_else(|_| unsafe { unreachable_unchecked() }),
231 )
232}
233
234fn bytes_to_checked_keypair<
235 S: Signator<PublicKey = PublicKey>,
236 KP: KeyPair<Seed = Seed32, Signator = S>,
237>(
238 bytes: &[u8],
239) -> Result<KP, DewifReadError> {
240 let seed = Seed32::new(
242 (&bytes[..SEED_32_SIZE_IN_BYTES])
243 .try_into()
244 .unwrap_or_else(|_| unsafe { unreachable_unchecked() }),
245 );
246 let expected_pubkey = PublicKey::try_from(&bytes[PUBKEY_DATAS_SIZE_IN_BYTES..])
247 .map_err(|_| DewifReadError::InvalidFormat)?;
248
249 let keypair = KP::from_seed(seed);
251
252 if keypair.public_key() != expected_pubkey {
254 Err(DewifReadError::CorruptedContent)
255 } else {
256 Ok(keypair)
257 }
258}
259
260#[cfg(feature = "bip32-ed25519")]
261fn get_dewif_mnemonic(decrypted_bytes: &[u8]) -> Result<Mnemonic, MnemonicError> {
262 let lang = Language::from_u8(decrypted_bytes[0])?;
264
265 let mnemonic_entropy_len = decrypted_bytes[1];
267
268 Mnemonic::from_entropy(
269 &decrypted_bytes[2..(2 + mnemonic_entropy_len as usize)],
270 lang,
271 )
272}
273
274#[cfg(test)]
275mod tests {
276 use crate::dewif::DewifMeta;
277
278 use super::*;
279 use unwrap::unwrap;
280
281 #[test]
282 fn read_unsupported_version() -> Result<(), ()> {
283 if let Err(DewifReadError::UnsupportedVersion { .. }) = read_dewif_content(
284 ExpectedCurrency::Any,
285 "ABAAARAAAAEMAAqqbWsirdvN0W7IkpmKdG/Zbt4ZszPx9VcWUu0o4cdxIZ4HHUybCVbyVmQL9Wid8KE6FCWeMRtr5OKAUKYwsNI=",
286 "toto"
287 ) {
288 Ok(())
289 } else {
290 panic!("Read must be fail with error UnsupportedVersion.")
291 }
292 }
293
294 #[test]
295 fn read_too_short_content() -> Result<(), ()> {
296 if let Err(DewifReadError::TooShortContent) =
297 read_dewif_content(ExpectedCurrency::Any, "AAA", "toto")
298 {
299 Ok(())
300 } else {
301 panic!("Read must be fail with error TooShortContent.")
302 }
303 }
304
305 #[test]
306 fn read_unexpected_currency() -> Result<(), ()> {
307 if let Err(DewifReadError::UnexpectedCurrency { .. }) = read_dewif_content(
308 ExpectedCurrency::Specific(Currency::from(42)),
309 "AAAAARAAAAEMAAqqbWsirdvN0W7IkpmKdG/Zbt4ZszPx9VcWUu0o4cdxIZ4HHUybCVbyVmQL9Wid8KE6FCWeMRtr5OKAUKYwsNI=",
310 "toto titi tata"
311 ) {
312 Ok(())
313 } else {
314 panic!("Read must be fail with error UnexpectedCurrency.")
315 }
316 }
317
318 #[test]
319 fn read_v1_ok() {
320 use crate::dewif::Currency;
321 use std::str::FromStr;
322
323 let dewif_file_content = "AAAAARAAAAEMACoqKioqKioqKioqKufSmkhlv1gbkEomswG4hQ2uMIVh+YOEym0ZRyNRwX226lsjB2UT2cnWLR11Wf3xm8Dm2lLB4IAfxd+iFiza7h4=";
325
326 let encryption_passphrase = "toto titi tata";
328
329 let expected_currency = ExpectedCurrency::Specific(unwrap!(Currency::from_str("g1-test")));
331
332 assert_eq!(
335 unwrap!(read_dewif_log_n(expected_currency, dewif_file_content)),
336 12u8
337 );
338 assert_eq!(
339 unwrap!(read_dewif_meta(dewif_file_content)),
340 DewifMeta {
341 algo: KeysAlgo::Ed25519,
342 currency: unwrap!(Currency::from_str("g1-test")),
343 log_n: 12u8,
344 version: 1
345 }
346 );
347 if let DewifContent {
348 payload: DewifPayload::Ed25519(key_pair),
349 ..
350 } = unwrap!(read_dewif_content(
351 expected_currency,
352 dewif_file_content,
353 encryption_passphrase
354 )) {
355 assert_eq!(
356 "4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS",
357 &key_pair.public_key().to_string()
358 );
359
360 let signator = key_pair.generate_signator();
364
365 let sig = signator.sign(b"message");
367
368 assert_eq!(
369 "JPurBgnHExHND1woow9nB7xVQjKkdHGs1znQbgv0ttZwOz16OlOCDDfvXfKE8e0xUfs2u7winav8IDwo7d1EBQ==",
370 &sig.to_string()
371 )
372 } else {
373 panic!("corrupted dewif");
374 }
375 }
376
377 #[test]
378 fn read_v1_bip32_ok() {
379 use crate::dewif::Currency;
380 use crate::keys::{KeyPair, PublicKey, Signator};
381 use std::str::FromStr;
382
383 let dewif_file_content =
385 "AAAAARAAAAEOASoqKioqKioqKioqKkIx/qkP1PWhtDNca4MdsvxPWAtvCd7nYriwMOHKxIFO8GJy9ElNngbSVQ==";
386
387 let encryption_passphrase = "toto titi tata";
389
390 let expected_currency = ExpectedCurrency::Specific(unwrap!(Currency::from_str("g1-test")));
392
393 assert_eq!(
396 unwrap!(read_dewif_log_n(expected_currency, dewif_file_content)),
397 14u8
398 );
399 assert_eq!(
400 unwrap!(read_dewif_meta(dewif_file_content)),
401 DewifMeta {
402 algo: KeysAlgo::Bip32Ed25519,
403 currency: unwrap!(Currency::from_str("g1-test")),
404 log_n: 14u8,
405 version: 1
406 }
407 );
408 if let DewifContent {
409 payload: DewifPayload::Bip32Ed25519(mnemonic),
410 ..
411 } = unwrap!(read_dewif_content(
412 expected_currency,
413 dewif_file_content,
414 encryption_passphrase
415 )) {
416 let key_pair = crate::keys::ed25519::bip32::KeyPair::from_mnemonic(&mnemonic);
417
418 assert_eq!(
419 "9TgSNiJPFtQV89Wt2GPnoozpSWTJzAxERpmTQr5Lhv7G",
420 &key_pair.public_key().to_string()
421 );
422
423 let signator = key_pair.generate_signator();
427
428 let sig = signator.sign(b"message");
430
431 assert_eq!(
432 "N1+7Dzjde71hBCkoqSWRc3Ywn4+z7FChKjCgG8OtIlki4BH9w6QLXQ8Pkb7uyoCa9N9VuUgtJDgYSn09ll6yCg==",
433 &sig.to_string()
434 );
435 assert!(key_pair.public_key().verify(b"message", &sig).is_ok());
436 } else {
437 panic!("corrupted dewif");
438 }
439 }
440}