Skip to main content

ng_wallet/
lib.rs

1// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
2// All rights reserved.
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
5// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
6// at your option. All files in the project carrying such
7// notice may not be copied, modified, or distributed except
8// according to those terms.
9
10#[macro_use]
11extern crate lazy_static;
12
13pub mod types;
14
15pub mod bip39;
16
17pub mod emojis;
18
19pub mod permissions;
20
21use std::{collections::HashMap, io::Cursor};
22
23use aes_gcm_siv::{
24    aead::{heapless::Vec as HeaplessVec, AeadInPlace, KeyInit},
25    Aes256GcmSiv, Nonce,
26};
27use argon2::{Algorithm, Argon2, AssociatedData, ParamsBuilder, Version};
28use chacha20poly1305::XChaCha20Poly1305;
29use image::{imageops::FilterType, io::Reader as ImageReader, ImageOutputFormat};
30use ng_net::types::Locator;
31use rand::distributions::{Distribution, Uniform};
32use rand::prelude::*;
33use safe_transmute::transmute_to_bytes;
34use serde_bare::{from_slice, to_vec};
35#[cfg(debug_assertions)]
36use web_time::Instant;
37use zeroize::Zeroize;
38
39use ng_repo::types::*;
40use ng_repo::utils::{generate_keypair, now_timestamp, sign, verify};
41use ng_repo::{log::*, types::PrivKey};
42
43use ng_verifier::{site::SiteV0, verifier::Verifier};
44
45use crate::bip39::bip39_wordlist;
46use crate::types::*;
47
48impl Wallet {
49    pub fn id(&self) -> WalletId {
50        match self {
51            Wallet::V0(v0) => v0.id,
52            _ => unimplemented!(),
53        }
54    }
55    pub fn content_as_bytes(&self) -> Vec<u8> {
56        match self {
57            Wallet::V0(v0) => serde_bare::to_vec(&v0.content).unwrap(),
58            _ => unimplemented!(),
59        }
60    }
61    pub fn sig(&self) -> Sig {
62        match self {
63            Wallet::V0(v0) => v0.sig,
64            _ => unimplemented!(),
65        }
66    }
67    pub fn pazzle_length(&self) -> u8 {
68        match self {
69            Wallet::V0(v0) => v0.content.pazzle_length,
70            _ => unimplemented!(),
71        }
72    }
73    pub fn name(&self) -> String {
74        match self {
75            Wallet::V0(v0) => v0.id.to_string(),
76            _ => unimplemented!(),
77        }
78    }
79
80    /// `nonce` : The current nonce used for encrypting this wallet by the user on this device.
81    /// It should be incremented BEFORE encrypting the wallet again
82    /// when some new operations have been added to the log of the Wallet.
83    /// The nonce is by PeerId. It is saved together with the PeerId in the SessionPeerStorage.
84    /// If the session is not saved (in-memory) it is lost, but it is fine, as the PeerId is also lost, and a new one
85    /// will be generated for the next session.
86    pub fn encrypt(
87        &self,
88        wallet_log: &WalletLog,
89        master_key: &[u8; 32],
90        peer_id: PubKey,
91        nonce: u64,
92        wallet_privkey: PrivKey,
93    ) -> Result<Self, NgWalletError> {
94        let timestamp = now_timestamp();
95        let wallet_id = self.id();
96        let encrypted =
97            enc_wallet_log(wallet_log, master_key, peer_id, nonce, timestamp, wallet_id)?;
98
99        let mut wallet_content = match self {
100            Wallet::V0(v0) => v0.content.clone(),
101            _ => unimplemented!(),
102        };
103
104        wallet_content.timestamp = timestamp;
105        wallet_content.peer_id = peer_id;
106        wallet_content.nonce = nonce;
107        wallet_content.encrypted = encrypted;
108
109        let ser_wallet = serde_bare::to_vec(&wallet_content).unwrap();
110
111        let sig = sign(&wallet_privkey, &wallet_id, &ser_wallet).unwrap();
112
113        let wallet_v0 = WalletV0 {
114            // ID
115            id: wallet_id,
116            // Content
117            content: wallet_content,
118            // Signature over content by wallet's private key
119            sig,
120        };
121
122        // let content = BootstrapContentV0 { servers: vec![] };
123        // let ser = serde_bare::to_vec(&content).unwrap();
124        // let sig = sign(wallet_key, wallet_id, &ser).unwrap();
125
126        // let bootstrap = Bootstrap::V0(BootstrapV0 {
127        //     id: wallet_id,
128        //     content,
129        //     sig,
130        // });
131
132        Ok(Wallet::V0(wallet_v0))
133    }
134}
135
136pub fn enc_master_key(
137    master_key: &[u8; 32],
138    key: &[u8; 32],
139    nonce: u8,
140    wallet_id: WalletId,
141) -> Result<[u8; 48], NgWalletError> {
142    let cipher = Aes256GcmSiv::new(key.into());
143    let mut nonce_buffer = [0u8; 12];
144    nonce_buffer[0] = nonce;
145    let nonce = Nonce::from_slice(&nonce_buffer);
146
147    let mut buffer: HeaplessVec<u8, 48> = HeaplessVec::new(); // Note: buffer needs 16-bytes overhead for auth tag
148    buffer
149        .extend_from_slice(master_key)
150        .map_err(|_| NgWalletError::InternalError)?;
151
152    // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext
153    cipher
154        .encrypt_in_place(nonce, &to_vec(&wallet_id).unwrap(), &mut buffer)
155        .map_err(|_e| NgWalletError::EncryptionError)?;
156
157    // `buffer` now contains the encrypted master key
158    // log_debug!("cipher {:?}", buffer);
159    Ok(buffer.into_array::<48>().unwrap())
160}
161
162pub fn dec_master_key(
163    ciphertext: [u8; 48],
164    key: &[u8; 32],
165    nonce: u8,
166    wallet_id: WalletId,
167) -> Result<[u8; 32], NgWalletError> {
168    let cipher = Aes256GcmSiv::new(key.into());
169    let mut nonce_buffer = [0u8; 12];
170    nonce_buffer[0] = nonce;
171    let nonce = Nonce::from_slice(&nonce_buffer);
172
173    let mut buffer: HeaplessVec<u8, 48> = HeaplessVec::from_slice(&ciphertext).unwrap(); // Note: buffer needs 16-bytes overhead for auth tag
174
175    // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext
176    cipher
177        .decrypt_in_place(nonce, &to_vec(&wallet_id).unwrap(), &mut buffer)
178        .map_err(|_e| NgWalletError::DecryptionError)?;
179    Ok(buffer.into_array::<32>().unwrap())
180}
181
182fn gen_nonce(peer_id: PubKey, nonce: u64) -> [u8; 24] {
183    let mut buffer = Vec::with_capacity(24);
184    buffer.extend_from_slice(&peer_id.slice()[0..16]);
185    buffer.extend_from_slice(&nonce.to_be_bytes());
186    buffer.try_into().unwrap()
187}
188
189fn gen_associated_data(timestamp: Timestamp, wallet_id: WalletId) -> Vec<u8> {
190    let ser_wallet = to_vec(&wallet_id).unwrap();
191    [ser_wallet, timestamp.to_be_bytes().to_vec()].concat()
192}
193
194pub fn enc_wallet_log(
195    log: &WalletLog,
196    master_key: &[u8; 32],
197    peer_id: PubKey,
198    nonce: u64,
199    timestamp: Timestamp,
200    wallet_id: WalletId,
201) -> Result<Vec<u8>, NgWalletError> {
202    let ser_log = to_vec(log).map_err(|_e| NgWalletError::InternalError)?;
203
204    let nonce_buffer: [u8; 24] = gen_nonce(peer_id, nonce);
205
206    let cipher = XChaCha20Poly1305::new(master_key.into());
207
208    let mut buffer: Vec<u8> = Vec::with_capacity(ser_log.len() + 16); // Note: buffer needs 16-bytes overhead for auth tag
209    buffer.extend_from_slice(&ser_log);
210
211    // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext
212    cipher
213        .encrypt_in_place(
214            &nonce_buffer.into(),
215            &gen_associated_data(timestamp, wallet_id),
216            &mut buffer,
217        )
218        .map_err(|_e| NgWalletError::EncryptionError)?;
219
220    // `buffer` now contains the message ciphertext
221    // log_debug!("encrypted_block ciphertext {:?}", buffer);
222
223    Ok(buffer)
224}
225
226// pub fn dec_session(key: PrivKey, vec: &Vec<u8>) -> Result<SessionWalletStorageV0, NgWalletError> {
227//     let session_ser = crypto_box::seal_open(&(*key.to_dh().slice()).into(), vec)
228//         .map_err(|_| NgWalletError::DecryptionError)?;
229//     let session: SessionWalletStorage =
230//         serde_bare::from_slice(&session_ser).map_err(|_| NgWalletError::SerializationError)?;
231//     let SessionWalletStorage::V0(v0) = session;
232//     Ok(v0)
233// }
234
235// pub fn create_new_session(
236//     wallet_id: PubKey,
237//     user: PubKey,
238// ) -> Result<(SessionWalletStorageV0, Vec<u8>), NgWalletError> {
239//     let peer = generate_keypair();
240//     let mut sws = SessionWalletStorageV0::new();
241//     let sps = SessionPeerStorageV0 {
242//         user,
243//         peer_key: peer.0,
244//         last_wallet_nonce: 0,
245//     };
246//     sws.users.insert(user.to_string(), sps);
247//     let sws_ser = serde_bare::to_vec(&SessionWalletStorage::V0(sws.clone())).unwrap();
248//     let mut rng = crypto_box::aead::OsRng {};
249//     let cipher = crypto_box::seal(&mut rng, &wallet_id.to_dh_slice().into(), &sws_ser)
250//         .map_err(|_| NgWalletError::EncryptionError)?;
251//     Ok((sws, cipher))
252// }
253
254pub fn dec_encrypted_block(
255    mut ciphertext: Vec<u8>,
256    master_key: [u8; 32],
257    peer_id: PubKey,
258    nonce: u64,
259    timestamp: Timestamp,
260    wallet_id: WalletId,
261) -> Result<SensitiveWalletV0, NgWalletError> {
262    let nonce_buffer: [u8; 24] = gen_nonce(peer_id, nonce);
263
264    let cipher = XChaCha20Poly1305::new(master_key.as_ref().into());
265
266    // Decrypt `ciphertext` in-place, replacing its ciphertext context with the original plaintext
267    cipher
268        .decrypt_in_place(
269            &nonce_buffer.into(),
270            &gen_associated_data(timestamp, wallet_id),
271            &mut ciphertext,
272        )
273        .map_err(|_e| NgWalletError::DecryptionError)?;
274
275    let decrypted_log =
276        from_slice::<WalletLog>(&ciphertext).map_err(|_e| NgWalletError::DecryptionError)?;
277
278    //master_key.zeroize(); // this is now done in the SensitiveWalletV0
279
280    // `ciphertext` now contains the decrypted block
281    //log_debug!("decrypted_block {:?}", ciphertext);
282    ciphertext.zeroize();
283
284    match decrypted_log {
285        WalletLog::V0(v0) => v0.reduce(master_key),
286    }
287}
288
289// FIXME: An important note on the cost parameters !!!
290// here they are set to quite high values because the code gets optimized (unfortunately) so the cost params take that into account.
291// on native apps in debug mode (dev mode), the rust code is not optimized and we get a timing above 1 min, which is way too much
292// once compiled for release (prod), the timing goes down to 8 sec on native apps because of the Rust optimization.
293// on the WASM32 target, the wasm-pack has optimization disabled (wasm-opt = false) but we suspect the optimization happens on the V8 runtime, in the browser or node.
294// we get 10 secs on the same machine for web based app. which is acceptable.
295// we should have a look at https://blog.trailofbits.com/2022/01/26/part-1-the-life-of-an-optimization-barrier/
296// and https://blog.trailofbits.com/2022/02/01/part-2-rusty-crypto/
297// the memory size could be too high for iOS which seems to have a limit of 120MB in total for the whole app.
298// we haven't test it yet. https://community.bitwarden.com/t/recommended-settings-for-argon2/50901/16?page=4
299pub fn derive_key_from_pass(mut pass: Vec<u8>, salt: [u8; 16], wallet_id: WalletId) -> [u8; 32] {
300    let params = ParamsBuilder::new()
301        .m_cost(40 * 1024)
302        .t_cost(40)
303        .p_cost(1)
304        .data(AssociatedData::new(wallet_id.slice()).unwrap())
305        .output_len(32)
306        .build()
307        .unwrap();
308    let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
309    let mut out = [0u8; 32];
310    argon.hash_password_into(&pass, &salt, &mut out).unwrap();
311    pass.zeroize();
312    out
313}
314
315pub fn open_wallet_with_pazzle(
316    wallet: &Wallet,
317    mut pazzle: Vec<u8>,
318    mut pin: [u8; 4],
319) -> Result<SensitiveWallet, NgWalletError> {
320    // each digit shouldnt be greater than 9
321    if pin[0] > 9 || pin[1] > 9 || pin[2] > 9 || pin[3] > 9 {
322        return Err(NgWalletError::InvalidPin);
323    }
324
325    //log_info!("pazzle={:?}", pazzle);
326
327    #[cfg(debug_assertions)]
328    let opening_pazzle = Instant::now();
329
330    verify(&wallet.content_as_bytes(), wallet.sig(), wallet.id())
331        .map_err(|_e| NgWalletError::InvalidSignature)?;
332
333    match wallet {
334        Wallet::V0(v0) => {
335            pazzle.extend_from_slice(&pin);
336            let mut pazzle_key = derive_key_from_pass(pazzle, v0.content.salt_pazzle, v0.id);
337            // pazzle is zeroized in derive_key_from_pass
338            pin.zeroize();
339
340            let master_key = dec_master_key(
341                v0.content.enc_master_key_pazzle,
342                &pazzle_key,
343                v0.content.master_nonce,
344                v0.id,
345            )?;
346            pazzle_key.zeroize();
347
348            #[cfg(debug_assertions)]
349            log_debug!(
350                "opening of wallet with pazzle took: {} ms",
351                opening_pazzle.elapsed().as_millis()
352            );
353            let cipher = v0.content.encrypted.clone();
354            Ok(SensitiveWallet::V0(dec_encrypted_block(
355                cipher,
356                master_key,
357                v0.content.peer_id,
358                v0.content.nonce,
359                v0.content.timestamp,
360                v0.id,
361            )?))
362        }
363        _ => unimplemented!(),
364    }
365}
366
367pub fn open_wallet_with_mnemonic(
368    wallet: &Wallet,
369    mut mnemonic: [u16; 12],
370    mut pin: [u8; 4],
371) -> Result<SensitiveWallet, NgWalletError> {
372    verify(&wallet.content_as_bytes(), wallet.sig(), wallet.id())
373        .map_err(|_e| NgWalletError::InvalidSignature)?;
374
375    match wallet {
376        Wallet::V0(v0) => {
377            let mut mnemonic_key = derive_key_from_pass(
378                [transmute_to_bytes(&mnemonic), &pin].concat(),
379                v0.content.salt_mnemonic,
380                v0.id,
381            );
382            mnemonic.zeroize();
383            pin.zeroize();
384
385            let master_key = dec_master_key(
386                v0.content.enc_master_key_mnemonic,
387                &mnemonic_key,
388                v0.content.master_nonce,
389                v0.id,
390            )?;
391            mnemonic_key.zeroize();
392
393            Ok(SensitiveWallet::V0(dec_encrypted_block(
394                v0.content.encrypted.clone(),
395                master_key,
396                v0.content.peer_id,
397                v0.content.nonce,
398                v0.content.timestamp,
399                v0.id,
400            )?))
401        }
402        _ => unimplemented!(),
403    }
404}
405
406pub fn display_mnemonic(mnemonic: &[u16; 12]) -> Vec<String> {
407    let res: Vec<String> = mnemonic
408        .into_iter()
409        .map(|i| String::from(bip39_wordlist[*i as usize]))
410        .collect();
411    res
412}
413
414pub fn gen_shuffle_for_pazzle_opening(pazzle_length: u8) -> ShuffledPazzle {
415    let mut rng = rand::thread_rng();
416    let mut category_indices: Vec<u8> = (0..pazzle_length).collect();
417    //log_debug!("{:?}", category_indices);
418    category_indices.shuffle(&mut rng);
419    //log_debug!("{:?}", category_indices);
420
421    let mut emoji_indices: Vec<Vec<u8>> = Vec::with_capacity(pazzle_length.into());
422    for _ in 0..pazzle_length {
423        let mut idx: Vec<u8> = (0..15).collect();
424        //log_debug!("{:?}", idx);
425        idx.shuffle(&mut rng);
426        //log_debug!("{:?}", idx);
427        emoji_indices.push(idx)
428    }
429    ShuffledPazzle {
430        category_indices,
431        emoji_indices,
432    }
433}
434
435pub fn gen_shuffle_for_pin() -> Vec<u8> {
436    let mut rng = rand::thread_rng();
437    let mut digits: Vec<u8> = (0..10).collect();
438    //log_debug!("{:?}", digits);
439    digits.shuffle(&mut rng);
440    //log_debug!("{:?}", digits);
441    digits
442}
443
444/// creates a Wallet from a pin, a security text and image
445/// and returns the Wallet, the pazzle and the mnemonic
446pub fn create_wallet_first_step_v0(
447    params: CreateWalletV0,
448) -> Result<CreateWalletIntermediaryV0, NgWalletError> {
449    // pazzle_length can only be 9, 12, or 15
450    if params.pazzle_length != 9
451        //&& params.pazzle_length != 12
452        //&& params.pazzle_length != 15
453        && params.pazzle_length != 0
454    {
455        return Err(NgWalletError::InvalidPazzleLength);
456    }
457
458    // check validity of PIN
459
460    // shouldn't start with 0
461    // if params.pin[0] == 0 {
462    //     return Err(NgWalletError::InvalidPin);
463    // }
464
465    // each digit shouldnt be greater than 9
466    if params.pin[0] > 9 || params.pin[1] > 9 || params.pin[2] > 9 || params.pin[3] > 9 {
467        return Err(NgWalletError::InvalidPin);
468    }
469
470    // check for same digit doesnt appear 3 times
471    if (params.pin[0] == params.pin[1] && params.pin[0] == params.pin[2])
472        || (params.pin[0] == params.pin[1] && params.pin[0] == params.pin[3])
473        || (params.pin[0] == params.pin[2] && params.pin[0] == params.pin[3])
474        || (params.pin[1] == params.pin[2] && params.pin[1] == params.pin[3])
475    {
476        return Err(NgWalletError::InvalidPin);
477    }
478
479    // check for ascending series
480    if params.pin[1] == params.pin[0] + 1
481        && params.pin[2] == params.pin[1] + 1
482        && params.pin[3] == params.pin[2] + 1
483    {
484        return Err(NgWalletError::InvalidPin);
485    }
486
487    // check for descending series
488    if params.pin[3] >= 3
489        && params.pin[2] == params.pin[3] - 1
490        && params.pin[1] == params.pin[2] - 1
491        && params.pin[0] == params.pin[1] - 1
492    {
493        return Err(NgWalletError::InvalidPin);
494    }
495
496    // check validity of security text
497    let words: Vec<_> = params.security_txt.split_whitespace().collect();
498    let new_string = words.join(" ");
499    let count = new_string.chars().count();
500    if count < 10 || count > 100 {
501        return Err(NgWalletError::InvalidSecurityText);
502    }
503
504    // check validity of image
505    let decoded_img = ImageReader::new(Cursor::new(&params.security_img))
506        .with_guessed_format()
507        .map_err(|_e| NgWalletError::InvalidSecurityImage)?
508        .decode()
509        .map_err(|_e| NgWalletError::InvalidSecurityImage)?;
510
511    if decoded_img.height() < 150 || decoded_img.width() < 150 {
512        return Err(NgWalletError::InvalidSecurityImage);
513    }
514
515    let resized_img = if decoded_img.height() == 400 && decoded_img.width() == 400 {
516        decoded_img
517    } else {
518        decoded_img.resize_to_fill(400, 400, FilterType::Triangle)
519    };
520
521    let buffer: Vec<u8> = Vec::with_capacity(100000);
522    let mut cursor = Cursor::new(buffer);
523    resized_img
524        .write_to(&mut cursor, ImageOutputFormat::Jpeg(72))
525        .map_err(|_e| NgWalletError::InvalidSecurityImage)?;
526
527    // creating the wallet keys
528
529    let (wallet_privkey, wallet_id) = generate_keypair();
530
531    // TODO: should be derived from  OwnershipProof
532    let user_privkey = PrivKey::random_ed();
533
534    let user = user_privkey.to_pub();
535
536    let client = ClientV0::new_with_auto_open(user);
537
538    let intermediary = CreateWalletIntermediaryV0 {
539        wallet_privkey,
540        wallet_name: wallet_id.to_string(),
541        client,
542        user_privkey,
543        in_memory: !params.local_save,
544        security_img: cursor.into_inner(),
545        security_txt: new_string,
546        pazzle_length: params.pazzle_length,
547        pin: params.pin,
548        send_bootstrap: params.send_bootstrap,
549        send_wallet: params.send_wallet,
550        result_with_wallet_file: params.result_with_wallet_file,
551        core_bootstrap: params.core_bootstrap.clone(),
552        core_registration: params.core_registration,
553        additional_bootstrap: params.additional_bootstrap.clone(),
554        pdf: params.pdf,
555    };
556    Ok(intermediary)
557}
558
559pub async fn create_wallet_second_step_v0(
560    mut params: CreateWalletIntermediaryV0,
561    verifier: &mut Verifier,
562) -> Result<
563    (
564        CreateWalletResultV0,
565        SiteV0,
566        HashMap<String, Vec<BrokerInfoV0>>,
567    ),
568    NgWalletError,
569> {
570    #[cfg(debug_assertions)]
571    let creating_pazzle = Instant::now();
572
573    let mut site = SiteV0::create_personal(params.user_privkey.clone(), verifier)
574        .await
575        .map_err(|e| {
576            log_err!("create_personal failed with {e}");
577            NgWalletError::InternalError
578        })?;
579
580    let user = params.user_privkey.to_pub();
581
582    let wallet_id = params.wallet_privkey.to_pub();
583
584    let mut ran = thread_rng();
585
586    let mut category_indices: Vec<u8> = (0..params.pazzle_length).collect();
587    category_indices.shuffle(&mut ran);
588
589    let between = Uniform::try_from(0..15).unwrap();
590    let mut pazzle = vec![0u8; params.pazzle_length.into()];
591    for (ix, i) in pazzle.iter_mut().enumerate() {
592        //*i = ran.gen_range(0, 15) + (category_indices[ix] << 4);
593        *i = between.sample(&mut ran) + (category_indices[ix] << 4);
594    }
595
596    //log_debug!("pazzle {:?}", pazzle);
597    let between = Uniform::try_from(0..2048).unwrap();
598    let mut mnemonic = [0u16; 12];
599    for i in &mut mnemonic {
600        //*i = ran.gen_range(0, 2048);
601        *i = between.sample(&mut ran);
602    }
603
604    //log_debug!("mnemonic {:?}", display_mnemonic(&mnemonic));
605
606    //slice_as_array!(&mnemonic, [String; 12])
607    //.ok_or(NgWalletError::InternalError)?
608    //.clone(),
609
610    let create_op = WalletOpCreateV0 {
611        wallet_privkey: params.wallet_privkey.clone(),
612        // pazzle: pazzle.clone(),
613        // mnemonic,
614        // pin: params.pin,
615        personal_site: site.clone(),
616        save_recovery_kit: if params.send_wallet {
617            SaveToNGOne::Wallet
618        } else if params.send_bootstrap {
619            SaveToNGOne::Bootstrap
620        } else {
621            SaveToNGOne::No
622        },
623        //client: client.clone(),
624    };
625
626    //Creating a new peerId for this Client and User. we don't do that anymore
627    //let peer = generate_keypair();
628
629    let mut wallet_log = WalletLog::new_v0(create_op);
630
631    // adding some more operations in the log
632
633    // pub core_bootstrap: BootstrapContentV0,
634    // #[zeroize(skip)]
635    // pub core_registration: Option<[u8; 32]>,
636    // #[zeroize(skip)]
637    // pub additional_bootstrap: Option<BootstrapContentV0>,
638
639    let mut brokers: HashMap<String, Vec<BrokerInfoV0>> = HashMap::new();
640
641    let core_pubkey = params
642        .core_bootstrap
643        .get_first_peer_id()
644        .ok_or(NgWalletError::InvalidBootstrap)?;
645    wallet_log.add(WalletOperation::AddSiteCoreV0((
646        user,
647        core_pubkey,
648        params.core_registration,
649    )));
650
651    site.cores.push((core_pubkey, params.core_registration));
652
653    if let Some(additional) = &params.additional_bootstrap {
654        params.core_bootstrap.merge(additional);
655    }
656    let mut locator = Locator::empty();
657    for server in &params.core_bootstrap.servers {
658        locator.add(server.clone());
659
660        wallet_log.add(WalletOperation::AddBrokerServerV0(server.clone()));
661        wallet_log.add(WalletOperation::AddSiteBootstrapV0((user, server.peer_id)));
662        site.bootstraps.push(server.peer_id);
663
664        let broker = BrokerInfoV0::ServerV0(server.clone());
665        let key = broker.get_id().to_string();
666        let mut list = brokers.get_mut(&key);
667        if list.is_none() {
668            let new_list = vec![];
669            brokers.insert(key.clone(), new_list);
670            list = brokers.get_mut(&key);
671        }
672        list.unwrap().push(broker);
673    }
674    verifier.update_locator(locator);
675
676    let mut master_key = [0u8; 32];
677    getrandom::getrandom(&mut master_key).map_err(|_e| NgWalletError::InternalError)?;
678
679    let mut salt_pazzle = [0u8; 16];
680    let mut enc_master_key_pazzle = [0u8; 48];
681    if params.pazzle_length > 0 {
682        getrandom::getrandom(&mut salt_pazzle).map_err(|_e| NgWalletError::InternalError)?;
683
684        let mut pazzle_key = derive_key_from_pass(
685            [pazzle.clone(), params.pin.to_vec()].concat(),
686            salt_pazzle,
687            wallet_id,
688        );
689
690        enc_master_key_pazzle = enc_master_key(&master_key, &pazzle_key, 0, wallet_id)?;
691        pazzle_key.zeroize();
692    }
693
694    let mut salt_mnemonic = [0u8; 16];
695    getrandom::getrandom(&mut salt_mnemonic).map_err(|_e| NgWalletError::InternalError)?;
696
697    //log_debug!("salt_pazzle {:?}", salt_pazzle);
698    //log_debug!("salt_mnemonic {:?}", salt_mnemonic);
699
700    let mut mnemonic_key = derive_key_from_pass(
701        [transmute_to_bytes(&mnemonic), &params.pin].concat(),
702        salt_mnemonic,
703        wallet_id,
704    );
705
706    let enc_master_key_mnemonic = enc_master_key(&master_key, &mnemonic_key, 0, wallet_id)?;
707    mnemonic_key.zeroize();
708
709    let timestamp = now_timestamp();
710
711    let encrypted = enc_wallet_log(
712        &wallet_log,
713        &master_key,
714        // the peer_id used to generate the nonce at creation time is always zero
715        PubKey::nil(),
716        0,
717        timestamp,
718        wallet_id,
719    )?;
720    master_key.zeroize();
721
722    let wallet_content = WalletContentV0 {
723        security_img: params.security_img.clone(),
724        security_txt: params.security_txt.clone(),
725        pazzle_length: params.pazzle_length,
726        salt_pazzle,
727        salt_mnemonic,
728        enc_master_key_pazzle,
729        enc_master_key_mnemonic,
730        master_nonce: 0,
731        timestamp,
732        peer_id: PubKey::nil(),
733        nonce: 0,
734        encrypted,
735    };
736
737    let ser_wallet = serde_bare::to_vec(&wallet_content).unwrap();
738
739    let sig = sign(&params.wallet_privkey, &wallet_id, &ser_wallet).unwrap();
740
741    let wallet_v0 = WalletV0 {
742        // ID
743        id: wallet_id,
744        // Content
745        content: wallet_content,
746        // Signature over content by wallet's private key
747        sig,
748    };
749
750    // let content = BootstrapContentV0 { servers: vec![] };
751    // let ser = serde_bare::to_vec(&content).unwrap();
752    // let sig = sign(wallet_key, wallet_id, &ser).unwrap();
753
754    // let bootstrap = Bootstrap::V0(BootstrapV0 {
755    //     id: wallet_id,
756    //     content,
757    //     sig,
758    // });
759
760    #[cfg(debug_assertions)]
761    log_debug!(
762        "creating of wallet took: {} ms",
763        creating_pazzle.elapsed().as_millis()
764    );
765
766    let wallet = Wallet::V0(wallet_v0);
767    let wallet_file = match params.result_with_wallet_file {
768        false => vec![],
769        true => to_vec(&NgFile::V0(NgFileV0::Wallet(wallet.clone()))).unwrap(),
770    };
771    Ok((
772        CreateWalletResultV0 {
773            wallet: wallet,
774            wallet_file,
775            pazzle,
776            mnemonic: mnemonic.clone(),
777            mnemonic_str: display_mnemonic(&mnemonic),
778            wallet_name: params.wallet_name.clone(),
779            client: params.client.clone(),
780            user,
781            in_memory: params.in_memory,
782            session_id: 0,
783            pdf_file: vec![],
784        },
785        site,
786        brokers,
787    ))
788}
789
790#[cfg(test)]
791mod test {
792    use crate::emojis::display_pazzle_one;
793
794    use super::*;
795    use ng_net::types::BootstrapContentV0;
796    use std::fs::File;
797    use std::io::BufReader;
798    use std::io::Read;
799    use std::io::Write;
800    use std::time::Instant;
801
802    // #[test]
803    // fn random_pass() {
804    //     super::random_pass()
805    // }
806
807    #[test]
808    fn test_gen_shuffle() {
809        let _shuffle = gen_shuffle_for_pazzle_opening(9);
810        log_debug!("{:?}", _shuffle);
811        let _shuffle = gen_shuffle_for_pazzle_opening(12);
812        log_debug!("{:?}", _shuffle);
813        let _shuffle = gen_shuffle_for_pazzle_opening(15);
814        log_debug!("{:?}", _shuffle);
815        let _digits = gen_shuffle_for_pin();
816        log_debug!("{:?}", _digits);
817    }
818
819    #[async_std::test]
820    async fn create_wallet() {
821        // loading an image file from disk
822        let f = File::open("tests/valid_security_image.jpg")
823            .expect("open of tests/valid_security_image.jpg");
824        let mut reader = BufReader::new(f);
825        let mut img_buffer = Vec::new();
826        // Read file into vector.
827        reader
828            .read_to_end(&mut img_buffer)
829            .expect("read of valid_security_image.jpg");
830
831        let pin = [5, 2, 9, 1];
832
833        let _creation = Instant::now();
834
835        let res = create_wallet_first_step_v0(CreateWalletV0::new(
836            img_buffer,
837            "   know     yourself  ".to_string(),
838            pin,
839            9,
840            false,
841            false,
842            BootstrapContentV0::new_localhost(PubKey::nil()),
843            None,
844            None,
845            false,
846            "test".to_string(),
847        ))
848        .expect("create_wallet_first_step_v0");
849
850        let mut verifier = Verifier::new_dummy();
851        let (res, _, _) = create_wallet_second_step_v0(res, &mut verifier)
852            .await
853            .expect("create_wallet_second_step_v0");
854
855        log_info!(
856            "creation of wallet took: {} ms",
857            _creation.elapsed().as_millis()
858        );
859        log_debug!("-----------------------------");
860
861        let mut file = File::create("tests/wallet.ngw").expect("open wallet write file");
862        let ser_wallet = to_vec(&NgFile::V0(NgFileV0::Wallet(res.wallet.clone()))).unwrap();
863        let _ = file.write_all(&ser_wallet);
864
865        log_debug!("wallet id: {}", res.wallet.id());
866        log_debug!("pazzle {:?}", display_pazzle_one(&res.pazzle));
867        log_debug!("mnemonic {:?}", display_mnemonic(&res.mnemonic));
868        log_debug!("pin {:?}", pin);
869
870        if let Wallet::V0(v0) = &res.wallet {
871            log_debug!("security text: {:?}", v0.content.security_txt);
872
873            let mut file =
874                File::create("tests/generated_security_image.jpg").expect("open write file");
875            let _ = file.write_all(&v0.content.security_img);
876
877            let f = File::open("tests/generated_security_image.jpg.compare")
878                .expect("open of generated_security_image.jpg.compare");
879            let mut reader = BufReader::new(f);
880            let mut generated_security_image_compare = Vec::new();
881            // Read file into vector.
882            reader
883                .read_to_end(&mut generated_security_image_compare)
884                .expect("read of generated_security_image.jpg.compare");
885
886            assert_eq!(v0.content.security_img, generated_security_image_compare);
887
888            let _opening_mnemonic = Instant::now();
889
890            let _w = open_wallet_with_mnemonic(&Wallet::V0(v0.clone()), res.mnemonic, pin.clone())
891                .expect("open with mnemonic");
892            //log_debug!("encrypted part {:?}", w);
893
894            log_info!(
895                "opening of wallet with mnemonic took: {} ms",
896                _opening_mnemonic.elapsed().as_millis()
897            );
898
899            if v0.content.pazzle_length > 0 {
900                let _opening_pazzle = Instant::now();
901                let _w = open_wallet_with_pazzle(&Wallet::V0(v0.clone()), res.pazzle.clone(), pin)
902                    .expect("open with pazzle");
903                log_info!(
904                    "opening of wallet with pazzle took: {} ms",
905                    _opening_pazzle.elapsed().as_millis()
906                );
907            }
908            log_debug!("encrypted part {:?}", _w);
909        }
910    }
911}