// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers // All rights reserved. // Licensed under the Apache License, Version 2.0 // // or the MIT license , // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. // #[macro_use] // extern crate slice_as_array; #[macro_use] extern crate lazy_static; pub mod types; pub mod bip39; pub mod emojis; use ng_verifier::{site::SiteV0, verifier::Verifier}; use rand::distributions::{Distribution, Uniform}; use std::{collections::HashMap, io::Cursor, sync::Arc}; use crate::bip39::bip39_wordlist; use crate::types::*; use aes_gcm_siv::{ aead::{heapless::Vec as HeaplessVec, AeadInPlace, KeyInit}, Aes256GcmSiv, Nonce, }; use argon2::{Algorithm, Argon2, AssociatedData, ParamsBuilder, Version}; use chacha20poly1305::XChaCha20Poly1305; use zeroize::{Zeroize, ZeroizeOnDrop}; use image::{imageops::FilterType, io::Reader as ImageReader, ImageOutputFormat}; use safe_transmute::transmute_to_bytes; use ng_repo::types::*; use ng_repo::utils::{generate_keypair, now_timestamp, sign, verify}; use ng_repo::{log::*, types::PrivKey}; use rand::prelude::*; use serde_bare::{from_slice, to_vec}; use web_time::Instant; impl Wallet { pub fn id(&self) -> WalletId { match self { Wallet::V0(v0) => v0.id, _ => unimplemented!(), } } pub fn content_as_bytes(&self) -> Vec { match self { Wallet::V0(v0) => serde_bare::to_vec(&v0.content).unwrap(), _ => unimplemented!(), } } pub fn sig(&self) -> Sig { match self { Wallet::V0(v0) => v0.sig, _ => unimplemented!(), } } pub fn pazzle_length(&self) -> u8 { match self { Wallet::V0(v0) => v0.content.pazzle_length, _ => unimplemented!(), } } pub fn name(&self) -> String { match self { Wallet::V0(v0) => base64_url::encode(&v0.id.slice()), _ => unimplemented!(), } } /// `nonce` : The current nonce used for encrypting this wallet by the user on this device. /// It should be incremented BEFORE encrypting the wallet again /// when some new operations have been added to the log of the Wallet. /// The nonce is by PeerId. It is saved together with the PeerId in the SessionPeerStorage. /// 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 /// will be generated for the next session. pub fn encrypt( &self, wallet_log: &WalletLog, master_key: &[u8; 32], peer_id: PubKey, nonce: u64, wallet_privkey: PrivKey, ) -> Result { let timestamp = now_timestamp(); let wallet_id = self.id(); let encrypted = enc_wallet_log(wallet_log, master_key, peer_id, nonce, timestamp, wallet_id)?; let mut wallet_content = match self { Wallet::V0(v0) => v0.content.clone(), _ => unimplemented!(), }; wallet_content.timestamp = timestamp; wallet_content.peer_id = peer_id; wallet_content.nonce = nonce; wallet_content.encrypted = encrypted; let ser_wallet = serde_bare::to_vec(&wallet_content).unwrap(); let sig = sign(&wallet_privkey, &wallet_id, &ser_wallet).unwrap(); let wallet_v0 = WalletV0 { /// ID id: wallet_id, /// Content content: wallet_content, /// Signature over content by wallet's private key sig, }; // let content = BootstrapContentV0 { servers: vec![] }; // let ser = serde_bare::to_vec(&content).unwrap(); // let sig = sign(wallet_key, wallet_id, &ser).unwrap(); // let bootstrap = Bootstrap::V0(BootstrapV0 { // id: wallet_id, // content, // sig, // }); Ok(Wallet::V0(wallet_v0)) } } pub fn enc_master_key( master_key: &[u8; 32], key: &[u8; 32], nonce: u8, wallet_id: WalletId, ) -> Result<[u8; 48], NgWalletError> { let cipher = Aes256GcmSiv::new(key.into()); let mut nonce_buffer = [0u8; 12]; nonce_buffer[0] = nonce; let nonce = Nonce::from_slice(&nonce_buffer); let mut buffer: HeaplessVec = HeaplessVec::new(); // Note: buffer needs 16-bytes overhead for auth tag buffer .extend_from_slice(master_key) .map_err(|_| NgWalletError::InternalError)?; // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext cipher .encrypt_in_place(nonce, &to_vec(&wallet_id).unwrap(), &mut buffer) .map_err(|e| NgWalletError::EncryptionError)?; // `buffer` now contains the encrypted master key // log_debug!("cipher {:?}", buffer); Ok(buffer.into_array::<48>().unwrap()) } pub fn dec_master_key( ciphertext: [u8; 48], key: &[u8; 32], nonce: u8, wallet_id: WalletId, ) -> Result<[u8; 32], NgWalletError> { let cipher = Aes256GcmSiv::new(key.into()); let mut nonce_buffer = [0u8; 12]; nonce_buffer[0] = nonce; let nonce = Nonce::from_slice(&nonce_buffer); let mut buffer: HeaplessVec = HeaplessVec::from_slice(&ciphertext).unwrap(); // Note: buffer needs 16-bytes overhead for auth tag // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext cipher .decrypt_in_place(nonce, &to_vec(&wallet_id).unwrap(), &mut buffer) .map_err(|e| NgWalletError::DecryptionError)?; Ok(buffer.into_array::<32>().unwrap()) } fn gen_nonce(peer_id: PubKey, nonce: u64) -> [u8; 24] { let mut buffer = Vec::with_capacity(24); buffer.extend_from_slice(&peer_id.slice()[0..16]); buffer.extend_from_slice(&nonce.to_be_bytes()); buffer.try_into().unwrap() } fn gen_associated_data(timestamp: Timestamp, wallet_id: WalletId) -> Vec { let ser_wallet = to_vec(&wallet_id).unwrap(); [ser_wallet, timestamp.to_be_bytes().to_vec()].concat() } pub fn enc_wallet_log( log: &WalletLog, master_key: &[u8; 32], peer_id: PubKey, nonce: u64, timestamp: Timestamp, wallet_id: WalletId, ) -> Result, NgWalletError> { let ser_log = to_vec(log).map_err(|e| NgWalletError::InternalError)?; let nonce_buffer: [u8; 24] = gen_nonce(peer_id, nonce); let cipher = XChaCha20Poly1305::new(master_key.into()); let mut buffer: Vec = Vec::with_capacity(ser_log.len() + 16); // Note: buffer needs 16-bytes overhead for auth tag buffer.extend_from_slice(&ser_log); // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext cipher .encrypt_in_place( &nonce_buffer.into(), &gen_associated_data(timestamp, wallet_id), &mut buffer, ) .map_err(|e| NgWalletError::EncryptionError)?; // `buffer` now contains the message ciphertext // log_debug!("encrypted_block ciphertext {:?}", buffer); Ok(buffer) } // pub fn dec_session(key: PrivKey, vec: &Vec) -> Result { // let session_ser = crypto_box::seal_open(&(*key.to_dh().slice()).into(), vec) // .map_err(|_| NgWalletError::DecryptionError)?; // let session: SessionWalletStorage = // serde_bare::from_slice(&session_ser).map_err(|_| NgWalletError::SerializationError)?; // let SessionWalletStorage::V0(v0) = session; // Ok(v0) // } // pub fn create_new_session( // wallet_id: PubKey, // user: PubKey, // ) -> Result<(SessionWalletStorageV0, Vec), NgWalletError> { // let peer = generate_keypair(); // let mut sws = SessionWalletStorageV0::new(); // let sps = SessionPeerStorageV0 { // user, // peer_key: peer.0, // last_wallet_nonce: 0, // }; // sws.users.insert(user.to_string(), sps); // let sws_ser = serde_bare::to_vec(&SessionWalletStorage::V0(sws.clone())).unwrap(); // let mut rng = crypto_box::aead::OsRng {}; // let cipher = crypto_box::seal(&mut rng, &wallet_id.to_dh_slice().into(), &sws_ser) // .map_err(|_| NgWalletError::EncryptionError)?; // Ok((sws, cipher)) // } pub fn dec_encrypted_block( mut ciphertext: Vec, master_key: [u8; 32], peer_id: PubKey, nonce: u64, timestamp: Timestamp, wallet_id: WalletId, ) -> Result { let nonce_buffer: [u8; 24] = gen_nonce(peer_id, nonce); let cipher = XChaCha20Poly1305::new(master_key.as_ref().into()); // Decrypt `ciphertext` in-place, replacing its ciphertext context with the original plaintext cipher .decrypt_in_place( &nonce_buffer.into(), &gen_associated_data(timestamp, wallet_id), &mut ciphertext, ) .map_err(|e| NgWalletError::DecryptionError)?; let decrypted_log = from_slice::(&ciphertext).map_err(|e| NgWalletError::DecryptionError)?; //master_key.zeroize(); // this is now done in the SensitiveWalletV0 // `ciphertext` now contains the decrypted block //log_debug!("decrypted_block {:?}", ciphertext); ciphertext.zeroize(); match decrypted_log { WalletLog::V0(v0) => v0.reduce(master_key), } } // FIXME: An important note on the cost parameters !!! // here are set to quite high values because the code gets optimized (unfortunately) so the cost params take that into account. // 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 // once compiled for release (prod), the timing goes down to 8 sec on native apps because of the Rust optimization. // 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. // we get 10 secs on the same machine for web based app. which is acceptable. // we should have a look at https://blog.trailofbits.com/2022/01/26/part-1-the-life-of-an-optimization-barrier/ // and https://blog.trailofbits.com/2022/02/01/part-2-rusty-crypto/ // the memory size could be too high for iOS which seems to have a limit of 120MB in total for the whole app. // we haven't test it yet. https://community.bitwarden.com/t/recommended-settings-for-argon2/50901/16?page=4 pub fn derive_key_from_pass(mut pass: Vec, salt: [u8; 16], wallet_id: WalletId) -> [u8; 32] { let params = ParamsBuilder::new() .m_cost(100 * 1024) .t_cost(24) .p_cost(1) .data(AssociatedData::new(wallet_id.slice()).unwrap()) .output_len(32) .build() .unwrap(); let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params); let mut out = [0u8; 32]; argon.hash_password_into(&pass, &salt, &mut out).unwrap(); pass.zeroize(); out } pub fn open_wallet_with_pazzle( wallet: &Wallet, mut pazzle: Vec, mut pin: [u8; 4], ) -> Result { // each digit shouldnt be greater than 9 if pin[0] > 9 || pin[1] > 9 || pin[2] > 9 || pin[3] > 9 { return Err(NgWalletError::InvalidPin); } log_info!("pazzle={:?}", pazzle); let opening_pazzle = Instant::now(); verify(&wallet.content_as_bytes(), wallet.sig(), wallet.id()) .map_err(|e| NgWalletError::InvalidSignature)?; match wallet { Wallet::V0(v0) => { pazzle.extend_from_slice(&pin); let mut pazzle_key = derive_key_from_pass(pazzle, v0.content.salt_pazzle, v0.id); // pazzle is zeroized in derive_key_from_pass pin.zeroize(); let master_key = dec_master_key( v0.content.enc_master_key_pazzle, &pazzle_key, v0.content.master_nonce, v0.id, )?; pazzle_key.zeroize(); log_debug!( "opening of wallet with pazzle took: {} ms", opening_pazzle.elapsed().as_millis() ); let cipher = v0.content.encrypted.clone(); Ok(SensitiveWallet::V0(dec_encrypted_block( cipher, master_key, v0.content.peer_id, v0.content.nonce, v0.content.timestamp, v0.id, )?)) } _ => unimplemented!(), } } pub fn open_wallet_with_mnemonic( wallet: Wallet, mut mnemonic: [u16; 12], mut pin: [u8; 4], ) -> Result { verify(&wallet.content_as_bytes(), wallet.sig(), wallet.id()) .map_err(|e| NgWalletError::InvalidSignature)?; match wallet { Wallet::V0(v0) => { let mut mnemonic_key = derive_key_from_pass( [transmute_to_bytes(&mnemonic), &pin].concat(), v0.content.salt_mnemonic, v0.id, ); mnemonic.zeroize(); pin.zeroize(); let master_key = dec_master_key( v0.content.enc_master_key_mnemonic, &mnemonic_key, v0.content.master_nonce, v0.id, )?; mnemonic_key.zeroize(); Ok(SensitiveWallet::V0(dec_encrypted_block( v0.content.encrypted, master_key, v0.content.peer_id, v0.content.nonce, v0.content.timestamp, v0.id, )?)) } _ => unimplemented!(), } } pub fn display_mnemonic(mnemonic: &[u16; 12]) -> Vec { let res: Vec = mnemonic .into_iter() .map(|i| String::from(bip39_wordlist[*i as usize])) .collect(); res } use crate::emojis::{EMOJIS, EMOJI_CAT}; pub fn display_pazzle(pazzle: &Vec) -> Vec { let res: Vec = pazzle .into_iter() .map(|i| { let cat = i >> 4; let idx = i & 15; let cat_str = EMOJI_CAT[cat as usize]; String::from(format!( "{}:{}", cat_str, EMOJIS.get(cat_str).unwrap()[idx as usize].code )) }) .collect(); res } pub fn gen_shuffle_for_pazzle_opening(pazzle_length: u8) -> ShuffledPazzle { let mut rng = rand::thread_rng(); let mut category_indices: Vec = (0..pazzle_length).collect(); //log_debug!("{:?}", category_indices); category_indices.shuffle(&mut rng); //log_debug!("{:?}", category_indices); let mut emoji_indices: Vec> = Vec::with_capacity(pazzle_length.into()); for _ in 0..pazzle_length { let mut idx: Vec = (0..15).collect(); //log_debug!("{:?}", idx); idx.shuffle(&mut rng); //log_debug!("{:?}", idx); emoji_indices.push(idx) } ShuffledPazzle { category_indices, emoji_indices, } } pub fn gen_shuffle_for_pin() -> Vec { let mut rng = rand::thread_rng(); let mut digits: Vec = (0..10).collect(); //log_debug!("{:?}", digits); digits.shuffle(&mut rng); //log_debug!("{:?}", digits); digits } /// creates a Wallet from a pin, a security text and image (with option to send the bootstrap and wallet to nextgraph.one) /// and returns the Wallet, the pazzle and the mnemonic pub fn create_wallet_first_step_v0( params: CreateWalletV0, ) -> Result { // pazzle_length can only be 9, 12, or 15 if params.pazzle_length != 9 //&& params.pazzle_length != 12 //&& params.pazzle_length != 15 && params.pazzle_length != 0 { return Err(NgWalletError::InvalidPazzleLength); } // check validity of PIN // shouldn't start with 0 // if params.pin[0] == 0 { // return Err(NgWalletError::InvalidPin); // } // each digit shouldnt be greater than 9 if params.pin[0] > 9 || params.pin[1] > 9 || params.pin[2] > 9 || params.pin[3] > 9 { return Err(NgWalletError::InvalidPin); } // check for same digit doesnt appear 3 times if (params.pin[0] == params.pin[1] && params.pin[0] == params.pin[2]) || (params.pin[0] == params.pin[1] && params.pin[0] == params.pin[3]) || (params.pin[0] == params.pin[2] && params.pin[0] == params.pin[3]) || (params.pin[1] == params.pin[2] && params.pin[1] == params.pin[3]) { return Err(NgWalletError::InvalidPin); } // check for ascending series if params.pin[1] == params.pin[0] + 1 && params.pin[2] == params.pin[1] + 1 && params.pin[3] == params.pin[2] + 1 { return Err(NgWalletError::InvalidPin); } // check for descending series if params.pin[3] >= 3 && params.pin[2] == params.pin[3] - 1 && params.pin[1] == params.pin[2] - 1 && params.pin[0] == params.pin[1] - 1 { return Err(NgWalletError::InvalidPin); } // check validity of security text let words: Vec<_> = params.security_txt.split_whitespace().collect(); let new_string = words.join(" "); let count = new_string.chars().count(); if count < 10 || count > 100 { return Err(NgWalletError::InvalidSecurityText); } // check validity of image let decoded_img = ImageReader::new(Cursor::new(¶ms.security_img)) .with_guessed_format() .map_err(|e| NgWalletError::InvalidSecurityImage)? .decode() .map_err(|e| NgWalletError::InvalidSecurityImage)?; if decoded_img.height() < 150 || decoded_img.width() < 150 { return Err(NgWalletError::InvalidSecurityImage); } let resized_img = if decoded_img.height() == 400 && decoded_img.width() == 400 { decoded_img } else { decoded_img.resize_to_fill(400, 400, FilterType::Triangle) }; let buffer: Vec = Vec::with_capacity(100000); let mut cursor = Cursor::new(buffer); resized_img .write_to(&mut cursor, ImageOutputFormat::Jpeg(72)) .map_err(|e| NgWalletError::InvalidSecurityImage)?; // creating the wallet keys let (wallet_privkey, wallet_id) = generate_keypair(); // TODO: should be derived from OwnershipProof let user_privkey = PrivKey::random_ed(); let user = user_privkey.to_pub(); let client = ClientV0::new_with_auto_open(user); let intermediary = CreateWalletIntermediaryV0 { wallet_privkey, wallet_name: base64_url::encode(&wallet_id.slice()), client, user_privkey, in_memory: !params.local_save, security_img: cursor.into_inner(), security_txt: new_string, pazzle_length: params.pazzle_length, pin: params.pin, send_bootstrap: params.send_bootstrap, send_wallet: params.send_wallet, result_with_wallet_file: params.result_with_wallet_file, core_bootstrap: params.core_bootstrap.clone(), core_registration: params.core_registration, additional_bootstrap: params.additional_bootstrap.clone(), }; Ok(intermediary) } pub async fn create_wallet_second_step_v0( mut params: CreateWalletIntermediaryV0, verifier: &mut Verifier, ) -> Result< ( CreateWalletResultV0, SiteV0, HashMap>, ), NgWalletError, > { let creating_pazzle = Instant::now(); let mut site = SiteV0::create_personal(params.user_privkey.clone(), verifier) .await .map_err(|e| { log_err!("{e}"); NgWalletError::InternalError })?; let user = params.user_privkey.to_pub(); let wallet_id = params.wallet_privkey.to_pub(); let mut ran = thread_rng(); let mut category_indices: Vec = (0..params.pazzle_length).collect(); category_indices.shuffle(&mut ran); let between = Uniform::try_from(0..15).unwrap(); let mut pazzle = vec![0u8; params.pazzle_length.into()]; for (ix, i) in pazzle.iter_mut().enumerate() { //*i = ran.gen_range(0, 15) + (category_indices[ix] << 4); *i = between.sample(&mut ran) + (category_indices[ix] << 4); } //log_debug!("pazzle {:?}", pazzle); let between = Uniform::try_from(0..2048).unwrap(); let mut mnemonic = [0u16; 12]; for i in &mut mnemonic { //*i = ran.gen_range(0, 2048); *i = between.sample(&mut ran); } //log_debug!("mnemonic {:?}", display_mnemonic(&mnemonic)); //slice_as_array!(&mnemonic, [String; 12]) //.ok_or(NgWalletError::InternalError)? //.clone(), let create_op = WalletOpCreateV0 { wallet_privkey: params.wallet_privkey.clone(), // pazzle: pazzle.clone(), // mnemonic, // pin: params.pin, personal_site: site.clone(), save_to_ng_one: if params.send_wallet { SaveToNGOne::Wallet } else if params.send_bootstrap { SaveToNGOne::Bootstrap } else { SaveToNGOne::No }, //client: client.clone(), }; //Creating a new peerId for this Client and User. we don't do that anymore //let peer = generate_keypair(); let mut wallet_log = WalletLog::new_v0(create_op); // adding some more operations in the log // pub core_bootstrap: BootstrapContentV0, // #[zeroize(skip)] // pub core_registration: Option<[u8; 32]>, // #[zeroize(skip)] // pub additional_bootstrap: Option, let mut brokers: HashMap> = HashMap::new(); let core_pubkey = params .core_bootstrap .get_first_peer_id() .ok_or(NgWalletError::InvalidBootstrap)?; wallet_log.add(WalletOperation::AddSiteCoreV0(( user, core_pubkey, params.core_registration, ))); site.cores.push((core_pubkey, params.core_registration)); if let Some(additional) = ¶ms.additional_bootstrap { params.core_bootstrap.merge(additional); } for server in ¶ms.core_bootstrap.servers { wallet_log.add(WalletOperation::AddBrokerServerV0(server.clone())); wallet_log.add(WalletOperation::AddSiteBootstrapV0((user, server.peer_id))); site.bootstraps.push(server.peer_id); let broker = BrokerInfoV0::ServerV0(server.clone()); let key = broker.get_id().to_string(); let mut list = brokers.get_mut(&key); if list.is_none() { let new_list = vec![]; brokers.insert(key.clone(), new_list); list = brokers.get_mut(&key); } list.unwrap().push(broker); } let mut master_key = [0u8; 32]; getrandom::getrandom(&mut master_key).map_err(|e| NgWalletError::InternalError)?; let mut salt_pazzle = [0u8; 16]; let mut enc_master_key_pazzle = [0u8; 48]; if params.pazzle_length > 0 { getrandom::getrandom(&mut salt_pazzle).map_err(|e| NgWalletError::InternalError)?; let mut pazzle_key = derive_key_from_pass( [pazzle.clone(), params.pin.to_vec()].concat(), salt_pazzle, wallet_id, ); enc_master_key_pazzle = enc_master_key(&master_key, &pazzle_key, 0, wallet_id)?; pazzle_key.zeroize(); } let mut salt_mnemonic = [0u8; 16]; getrandom::getrandom(&mut salt_mnemonic).map_err(|e| NgWalletError::InternalError)?; //log_debug!("salt_pazzle {:?}", salt_pazzle); //log_debug!("salt_mnemonic {:?}", salt_mnemonic); let mut mnemonic_key = derive_key_from_pass( [transmute_to_bytes(&mnemonic), ¶ms.pin].concat(), salt_mnemonic, wallet_id, ); let enc_master_key_mnemonic = enc_master_key(&master_key, &mnemonic_key, 0, wallet_id)?; mnemonic_key.zeroize(); let timestamp = now_timestamp(); let encrypted = enc_wallet_log( &wallet_log, &master_key, // the peer_id used to generate the nonce at creation time is always zero PubKey::nil(), 0, timestamp, wallet_id, )?; master_key.zeroize(); let wallet_content = WalletContentV0 { security_img: params.security_img.clone(), security_txt: params.security_txt.clone(), pazzle_length: params.pazzle_length, salt_pazzle, salt_mnemonic, enc_master_key_pazzle, enc_master_key_mnemonic, master_nonce: 0, timestamp, peer_id: PubKey::nil(), nonce: 0, encrypted, }; let ser_wallet = serde_bare::to_vec(&wallet_content).unwrap(); let sig = sign(¶ms.wallet_privkey, &wallet_id, &ser_wallet).unwrap(); let wallet_v0 = WalletV0 { /// ID id: wallet_id, /// Content content: wallet_content, /// Signature over content by wallet's private key sig, }; // let content = BootstrapContentV0 { servers: vec![] }; // let ser = serde_bare::to_vec(&content).unwrap(); // let sig = sign(wallet_key, wallet_id, &ser).unwrap(); // let bootstrap = Bootstrap::V0(BootstrapV0 { // id: wallet_id, // content, // sig, // }); log_debug!( "creating of wallet took: {} ms", creating_pazzle.elapsed().as_millis() ); let wallet = Wallet::V0(wallet_v0); let wallet_file = match params.result_with_wallet_file { false => vec![], true => to_vec(&NgFile::V0(NgFileV0::Wallet(wallet.clone()))).unwrap(), }; Ok(( CreateWalletResultV0 { wallet: wallet, wallet_file, pazzle, mnemonic: mnemonic.clone(), wallet_name: params.wallet_name.clone(), client: params.client.clone(), user, in_memory: params.in_memory, session_id: 0, }, site, brokers, )) } #[cfg(test)] mod test { use super::*; use ng_net::types::BootstrapContentV0; use std::fs::File; use std::io::BufReader; use std::io::Read; use std::io::Write; use std::time::Instant; // #[test] // fn random_pass() { // super::random_pass() // } #[test] fn test_gen_shuffle() { let shuffle = gen_shuffle_for_pazzle_opening(9); log_debug!("{:?}", shuffle); let shuffle = gen_shuffle_for_pazzle_opening(12); log_debug!("{:?}", shuffle); let shuffle = gen_shuffle_for_pazzle_opening(15); log_debug!("{:?}", shuffle); let digits = gen_shuffle_for_pin(); let digits = gen_shuffle_for_pin(); } #[async_std::test] async fn create_wallet() { // loading an image file from disk let f = File::open("tests/valid_security_image.jpg") .expect("open of tests/valid_security_image.jpg"); let mut reader = BufReader::new(f); let mut img_buffer = Vec::new(); // Read file into vector. reader .read_to_end(&mut img_buffer) .expect("read of valid_security_image.jpg"); let pin = [5, 2, 9, 1]; let creation = Instant::now(); let res = create_wallet_first_step_v0(CreateWalletV0::new( img_buffer, " know yourself ".to_string(), pin, 9, false, false, BootstrapContentV0::new_empty(), None, None, )) .expect("create_wallet_first_step_v0"); let mut verifier = Verifier::new_dummy(); let (res, _, _) = create_wallet_second_step_v0(res, &mut verifier) .await .expect("create_wallet_second_step_v0"); log_debug!( "creation of wallet took: {} ms", creation.elapsed().as_millis() ); log_debug!("-----------------------------"); let mut file = File::create("tests/wallet.ngw").expect("open wallet write file"); let ser_wallet = to_vec(&NgFile::V0(NgFileV0::Wallet(res.wallet.clone()))).unwrap(); file.write_all(&ser_wallet); log_debug!( "wallet id: {:?}", base64_url::encode(&res.wallet.id().slice()) ); log_debug!("pazzle {:?}", display_pazzle(&res.pazzle)); log_debug!("mnemonic {:?}", display_mnemonic(&res.mnemonic)); log_debug!("pin {:?}", pin); if let Wallet::V0(v0) = &res.wallet { log_debug!("security text: {:?}", v0.content.security_txt); let mut file = File::create("tests/generated_security_image.jpg").expect("open write file"); file.write_all(&v0.content.security_img); let f = File::open("tests/generated_security_image.jpg.compare") .expect("open of generated_security_image.jpg.compare"); let mut reader = BufReader::new(f); let mut generated_security_image_compare = Vec::new(); // Read file into vector. reader .read_to_end(&mut generated_security_image_compare) .expect("read of generated_security_image.jpg.compare"); assert_eq!(v0.content.security_img, generated_security_image_compare); let opening_mnemonic = Instant::now(); let w = open_wallet_with_mnemonic(Wallet::V0(v0.clone()), res.mnemonic, pin.clone()) .expect("open with mnemonic"); //log_debug!("encrypted part {:?}", w); log_debug!( "opening of wallet with mnemonic took: {} ms", opening_mnemonic.elapsed().as_millis() ); if v0.content.pazzle_length > 0 { let opening_pazzle = Instant::now(); let w = open_wallet_with_pazzle(&Wallet::V0(v0.clone()), res.pazzle.clone(), pin) .expect("open with pazzle"); log_debug!( "opening of wallet with pazzle took: {} ms", opening_pazzle.elapsed().as_millis() ); } log_debug!("encrypted part {:?}", w); } } }