diff --git a/crates/sp_client/src/api.rs b/crates/sp_client/src/api.rs index 4ba3c13..1d36711 100644 --- a/crates/sp_client/src/api.rs +++ b/crates/sp_client/src/api.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::sync::{Mutex, OnceLock}; use rand::Rng; @@ -18,7 +19,7 @@ use sp_backend::spclient::SpendKey; use sp_backend::spclient::{derive_keys_from_seed, OutputList, SpClient}; use web_sys::js_sys::JsString; -use crate::user::{User, UserKeys}; +use crate::user::{User, UserKeys, CONNECTED_USERS}; type ApiResult = Result; @@ -128,50 +129,24 @@ pub fn create_user( password: String, label: Option, birthday: u32, - process: String + process: String, ) -> ApiResult { let mut output_list: Vec = Vec::new(); //recover let sp_wallet_recover = generate_sp_wallet(label.clone(), birthday, true)?; output_list.push(sp_wallet_recover.sp_outputs); - let recover_scan_key = sp_wallet_recover.sp_client.get_scan_key(); - let recover_spend_key = match sp_wallet_recover.sp_client.get_spend_key() { - SpendKey::Secret(key) => key, - SpendKey::Public(_) => { - return Err(ApiError::from(AnyhowError::msg( - "No recover spend key created on Signet", - ))) - } - }; - let recover_keys = UserKeys::add_keys_recover(recover_scan_key, recover_spend_key); //revoke let sp_wallet_revoke = generate_sp_wallet(label.clone(), birthday, true)?; output_list.push(sp_wallet_revoke.sp_outputs); - let revoke_scan_key = sp_wallet_revoke.sp_client.get_scan_key(); - let revoke_spend_key = match sp_wallet_revoke.sp_client.get_spend_key() { - SpendKey::Secret(key) => key, - SpendKey::Public(_) => { - return Err(ApiError::from(AnyhowError::msg( - "No revoke spend key created on Signet", - ))) - } - }; - let revoke_keys = UserKeys::add_keys_revoke(revoke_scan_key, revoke_spend_key); //mainet let sp_wallet_main = generate_sp_wallet(label, birthday, false)?; output_list.push(sp_wallet_main.sp_outputs); - let main_scan_key = sp_wallet_main.sp_client.get_scan_key(); - let main_spend_key = match sp_wallet_main.sp_client.get_spend_key() { - SpendKey::Secret(key) => key, - SpendKey::Public(_) => { - return Err(ApiError::from(AnyhowError::msg( - "No spend key created on Mainet", - ))) - } - }; - let main_keys = UserKeys::add_keys_main(main_scan_key, main_spend_key); - let user_keys = UserKeys::new(recover_keys, revoke_keys, main_keys); + let user_keys = UserKeys::new( + Some(sp_wallet_main.sp_client), + sp_wallet_recover.sp_client, + Some(sp_wallet_revoke.sp_client), + ); let user = User::new(user_keys, password, process)?; diff --git a/crates/sp_client/src/user.rs b/crates/sp_client/src/user.rs index d2f15db..852327c 100644 --- a/crates/sp_client/src/user.rs +++ b/crates/sp_client/src/user.rs @@ -12,15 +12,18 @@ use sp_backend::bitcoin::hashes::Hash; use sp_backend::bitcoin::hashes::HashEngine; use sp_backend::bitcoin::hex::{DisplayHex, FromHex}; use sp_backend::bitcoin::secp256k1::SecretKey; +use sp_backend::bitcoin::secp256k1::ThirtyTwoByteHash; use tsify::Tsify; use wasm_bindgen::prelude::*; use bytes::Bytes; use shamir::SecretData; +use std::collections::HashMap; use std::fs::File; use std::io::Read; use std::io::Write; use std::str::FromStr; +use std::sync::{Mutex, OnceLock}; use sp_backend::bitcoin::secp256k1::constants::SECRET_KEY_SIZE; use sp_backend::silentpayments::bitcoin_hashes::sha256; @@ -34,99 +37,60 @@ use img_parts::{ImageEXIF, ImageICC}; use crate::aesgcm::Aes256Decryption; use crate::aesgcm::HalfKey; use crate::aesgcm::{Aes256Encryption, Purpose}; +use crate::user; + +type PreId = String; + +pub static CONNECTED_USERS: OnceLock>> = OnceLock::new(); -//extern crate shamir; -//use shamir::SecretData; -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum KeyType { - Recover(SpKeys), - Revoke(SpKeys), - Main(SpKeys), -} -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SpKeys { - pub scan_key: SecretKey, - pub spend_key: SecretKey, -} #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserKeys { - pub recover_keys: KeyType, - pub revoke_keys: KeyType, - pub main_keys: KeyType, + pub main: Option, + pub recover: SpClient, + pub revoke: Option, } impl UserKeys { - pub fn new(recover_keys: KeyType, revoke_keys: KeyType, main_keys: KeyType) -> Self { - UserKeys { - recover_keys, - revoke_keys, - main_keys, + pub fn new(main: Option, recover: SpClient, revoke: Option) -> Self { + Self { + main, + recover, + revoke, } } - pub fn add_keys_recover(scan_key: SecretKey, spend_key: SecretKey) -> KeyType { - let sp_keys = SpKeys { - scan_key, - spend_key, - }; - KeyType::Recover((sp_keys)) - } - pub fn add_keys_revoke(scan_key: SecretKey, spend_key: SecretKey) -> KeyType { - let sp_keys = SpKeys { - scan_key, - spend_key, - }; - KeyType::Revoke((sp_keys)) - } - pub fn add_keys_main(scan_key: SecretKey, spend_key: SecretKey) -> KeyType { - let sp_keys = SpKeys { - scan_key, - spend_key, - }; - KeyType::Main((sp_keys)) - } - pub fn get_keys(&self, key_type: KeyType) -> SpKeys { - match key_type { - KeyType::Recover(keys) => keys, - KeyType::Revoke(keys) => keys, - KeyType::Main(keys) => keys, - } + pub fn try_get_revoke(&self) -> Option<&SpClient> { + self.revoke.as_ref() } } #[derive(Debug, Serialize, Deserialize, Clone, Tsify)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct User { - pre_id: String, - process: String, + pub pre_id: String, + pub process: String, recover_data: Vec, revoke_data: Option>, sharding: Sharding, - //recovered_spend_key: Option, - //recovered_scan_key: Option, } impl User { - pub fn new( - user_keys: UserKeys, - user_password: String, - process: String - ) -> Result { + pub fn new(user_keys: UserKeys, user_password: String, process: String) -> Result { let mut rng = thread_rng(); - + // image revoke // We just take the 2 revoke keys - let revoke_scan_key = user_keys.get_keys(user_keys.revoke_keys.clone()).scan_key; - let revoke_spend_key = user_keys.get_keys(user_keys.revoke_keys.clone()).spend_key; - let mut revoke_data = Vec::with_capacity(64); - revoke_data.extend_from_slice(revoke_scan_key.as_ref()); - revoke_data.extend_from_slice(revoke_spend_key.as_ref()); + if let Some(revoke) = user_keys.try_get_revoke() { + revoke_data.extend_from_slice(revoke.get_scan_key().as_ref()); + revoke_data.extend_from_slice(revoke.try_get_secret_spend_key()?.as_ref()); + } else { + return Err(Error::msg("No revoke wallet available")); + } // Take the 2 recover keys - let recover_scan_key = user_keys.get_keys(user_keys.recover_keys.clone()).scan_key; - let recover_spend_key = user_keys.get_keys(user_keys.recover_keys.clone()).spend_key; // split recover spend key + let recover_spend_key = user_keys.recover.try_get_secret_spend_key()?.clone(); let (part1_key, part2_key) = recover_spend_key.as_ref().split_at(SECRET_KEY_SIZE / 2); let mut recover_data = Vec::::with_capacity(180); // 32 * 3 + (12+16)*3 @@ -158,6 +122,9 @@ impl User { recover_data.extend_from_slice(&cipher_recover_part1); + //Pre ID + let pre_id: PreId = Self::compute_pre_id(&user_password, &cipher_recover_part1); + // encrypt the part 2 of the key let mut engine = sha256::HashEngine::default(); engine.write_all(&user_password.as_bytes()); @@ -178,12 +145,6 @@ impl User { //create shardings let sharding = Sharding::new(&cipher_recover_part2.to_lower_hex_string(), 10u8); //nMembers = 10 for testing, need to recover nmember elsewhere - //Pre ID - let mut engine = sha256::HashEngine::default(); - engine.write_all(&user_password.as_bytes()); - engine.write_all(&cipher_recover_part1); - let pre_id = sha256::Hash::from_engine(engine); - //scan key: let mut engine = sha256::HashEngine::default(); engine.write_all(&user_password.as_bytes()); @@ -192,7 +153,7 @@ impl User { let scan_key_encryption = Aes256Encryption::import_key( Purpose::ThirtyTwoBytes, - recover_scan_key.secret_bytes().to_vec(), + user_keys.recover.get_scan_key().secret_bytes().to_vec(), hash3.to_byte_array(), Aes256Gcm::generate_nonce(&mut rng).into(), )?; @@ -207,35 +168,65 @@ impl User { //Send messages PRDList //@todo //Receive List Items (PCD) - + Ok(User { pre_id: pre_id.to_string(), process, recover_data, revoke_data: Some(revoke_data), sharding, - //recovered_spend_key: None, - //recovered_scan_key: None, }) } - pub fn login(&self,user_password: String, recover_data: &[u8], sharding: Sharding) -> Result<()> { + pub fn login( + pre_id: PreId, + user_password: String, + recover_data: &[u8], + sharding: Sharding, + ) -> Result<()> { let mut retrieved_spend_key = [0u8; 32]; let mut retrieved_scan_key = [0u8; 32]; let mut entropy1 = [0u8; 32]; let mut entropy2 = [0u8; 32]; let mut entropy3 = [0u8; 32]; - let mut cipher_scan_key = Vec::with_capacity(32); - let mut part1_ciphertext = Vec::with_capacity(32); // just a guess + let mut cipher_scan_key = [0u8; 60]; + let mut part1_ciphertext = [0u8; 44]; let mut reader = recover_data.reader(); reader.read_exact(&mut entropy1)?; reader.read_exact(&mut entropy2)?; reader.read_exact(&mut entropy3)?; reader.read_exact(&mut part1_ciphertext)?; - reader.read_to_end(&mut cipher_scan_key)?; + reader.read_exact(&mut cipher_scan_key)?; - retrieved_spend_key[..16].copy_from_slice(&Self::recover_key_slice(&user_password, &entropy1, part1_ciphertext)?); + // We can retrieve the pre_id and check that it matches + let retrieved_pre_id = Self::compute_pre_id(&user_password, &part1_ciphertext); + + // If pre_id is not the same, password is probably false, or the client is feeding us garbage + if retrieved_pre_id != pre_id { + return Err(Error::msg("pre_id and recover_data don't match")); + } + + // If we already have loaded a user with this pre_id, abort + if let Some(current_users) = CONNECTED_USERS.get() { + if current_users + .to_owned() + .lock() + .unwrap() + .contains_key(&pre_id) + { + return Err(Error::msg(format!( + "User with pre_id {} already logged in", + pre_id + ))); + } + } + + retrieved_spend_key[..16].copy_from_slice(&Self::recover_part1( + &user_password, + &entropy1, + part1_ciphertext.to_vec(), + )?); //@todo: get shardings from member managers! let shardings = sharding.shares_vec.clone(); // temporary @@ -245,34 +236,80 @@ impl User { &entropy2, shardings, )?); - - retrieved_scan_key[..32].copy_from_slice(&Self::recover_key_slice(&user_password, &entropy3, cipher_scan_key)?);; - //@todo: retrieved_scan_key and retrieved_spend_key should be stored somewhere! - + retrieved_scan_key.copy_from_slice(&Self::recover_key_slice( + &user_password, + &entropy3, + cipher_scan_key.to_vec(), + )?); + + // we can create the recover sp_client + let recover_client = SpClient::new( + "".to_owned(), + SecretKey::from_slice(&retrieved_scan_key)?, + SpendKey::Secret(SecretKey::from_slice(&retrieved_spend_key)?), + None, + true, + )?; + + // Adding user to CONNECTED_USERS + if let Some(current_users) = CONNECTED_USERS.get() { + let mut lock = current_users.to_owned().lock().unwrap(); + if lock.contains_key(&pre_id) { + return Err(Error::msg(format!( + "User with pre_id {} already exists", + pre_id + ))); + } else { + lock.insert(pre_id.clone(), UserKeys::new(None, recover_client, None)); + } + } else { + let mut user_map = HashMap::new(); + user_map.insert(pre_id, UserKeys::new(None, recover_client, None)); + let new_value = Mutex::new(user_map); + if let Err(error) = CONNECTED_USERS.set(new_value) { + return Err(Error::msg( + "Failed to set the CONNECTED_USERS static variable", + )); + } + } + Ok(()) } - fn recover_key_slice( - password: &str, - entropy: &[u8], - ciphertext: Vec, - ) -> Result> { + fn recover_key_slice(password: &str, entropy: &[u8], ciphertext: Vec) -> Result> { let mut engine = sha256::HashEngine::default(); engine.write_all(&password.as_bytes()); engine.write_all(&entropy); let hash = sha256::Hash::from_engine(engine); - let aes_dec = Aes256Decryption::new(Purpose::Login, ciphertext, hash.to_byte_array().to_vec(), None)?; + let aes_dec = Aes256Decryption::new( + Purpose::ThirtyTwoBytes, + ciphertext, + hash.to_byte_array().to_vec(), + None, + )?; aes_dec.decrypt_with_key() } - fn recover_part2( - password: &str, - entropy: &[u8], - shares_vec: Vec>, - ) -> Result> { + fn recover_part1(password: &str, entropy: &[u8], ciphertext: Vec) -> Result> { + let mut engine = sha256::HashEngine::default(); + engine.write_all(&password.as_bytes()); + engine.write_all(&entropy); + let hash = sha256::Hash::from_engine(engine); + + let aes_dec = Aes256Decryption::new( + Purpose::Login, + ciphertext, + hash.to_byte_array().to_vec(), + None, + )?; + + aes_dec.decrypt_with_key() + } + + fn recover_part2(password: &str, entropy: &[u8], shares_vec: Vec>) -> Result> { let mut engine = sha256::HashEngine::default(); engine.write_all(&password.as_bytes()); engine.write_all(&entropy); @@ -286,11 +323,25 @@ impl User { .ok_or_else(|| anyhow::Error::msg("Failed to retrieve the sharded secret"))?, )?; - let aes_dec = Aes256Decryption::new(Purpose::Login, part2_key_enc, hash.to_byte_array().to_vec(), None)?; + let aes_dec = Aes256Decryption::new( + Purpose::Login, + part2_key_enc, + hash.to_byte_array().to_vec(), + None, + )?; aes_dec.decrypt_with_key() } + fn compute_pre_id(user_password: &str, cipher_recover_part1: &[u8]) -> PreId { + let mut engine = sha256::HashEngine::default(); + engine.write_all(&user_password.as_bytes()); + engine.write_all(&cipher_recover_part1); + let pre_id = sha256::Hash::from_engine(engine); + + pre_id.to_string() + } + //not used // pub fn pbkdf2(password: &str, data: &str) -> String { // let data_salt = data.trim_end_matches('='); @@ -439,25 +490,56 @@ pub fn read_exif(image: &[u8]) -> Result, String> { // let base64_string = base64::encode(decoded_data); // base64_string // } -/* + #[cfg(test)] mod tests { use super::*; // Import everything from the outer module const RECOVER_SPEND: &str = "394ef7757f5bc8cd692337c62abf6fa0ce9932fd4ec6676daddfbe3c1b3b9d11"; + const RECOVER_SCAN: &str = "3aa8cc570d17ec3a4dc4136e50151cc6de26052d968abfe02a5fea724ce38205"; const REVOKE_SPEND: &str = "821c1a84fa9ee718c02005505fb8315bd479c7b9a878b1eff45929c48dfcaf28"; const REVOKE_SCAN: &str = "a0f36cbc380624fa7eef022f39cab2716333451649dd8eb78e86d2e76bdb3f47"; + const MAIN_SPEND: &str = "b9098a6598ac55d8dd0e6b7aab0d1f63eb8792d06143f3c0fb6f5b80476a1c0d"; + const MAIN_SCAN: &str = "79dda4031663ac2cb250c46d896dc92b3c027a48a761b2342fabf1e441ea2857"; const USER_PASSWORD: &str = "correct horse battery staple"; + const PROCESS: &str = "example"; + + fn helper_create_user_keys() -> UserKeys { + let label = "default".to_owned(); + let sp_main = SpClient::new( + label.clone(), + SecretKey::from_str(MAIN_SCAN).unwrap(), + SpendKey::Secret(SecretKey::from_str(MAIN_SPEND).unwrap()), + None, + true, + ) + .unwrap(); + let sp_recover = SpClient::new( + label.clone(), + SecretKey::from_str(RECOVER_SCAN).unwrap(), + SpendKey::Secret(SecretKey::from_str(RECOVER_SPEND).unwrap()), + None, + true, + ) + .unwrap(); + let sp_revoke = SpClient::new( + label.clone(), + SecretKey::from_str(REVOKE_SCAN).unwrap(), + SpendKey::Secret(SecretKey::from_str(REVOKE_SPEND).unwrap()), + None, + true, + ) + .unwrap(); + let user_keys = UserKeys::new(Some(sp_main), sp_recover, Some(sp_revoke)); + + user_keys + } // Test 1: Create User #[test] fn test_successful_creation() { - let result = User::new( - SecretKey::from_str(RECOVER_SPEND).unwrap(), - SecretKey::from_str(REVOKE_SPEND).unwrap(), - SecretKey::from_str(REVOKE_SCAN).unwrap(), - USER_PASSWORD.to_owned(), - ); + let user_keys = helper_create_user_keys(); + let result = User::new(user_keys, USER_PASSWORD.to_owned(), PROCESS.to_owned()); assert!(result.is_ok()); let user = result.unwrap(); @@ -465,20 +547,31 @@ mod tests { #[test] fn test_login() { - let user = User::new( - SecretKey::from_str(RECOVER_SPEND).unwrap(), - SecretKey::from_str(REVOKE_SPEND).unwrap(), - SecretKey::from_str(REVOKE_SCAN).unwrap(), - USER_PASSWORD.to_owned(), - ).unwrap(); + let user_keys = helper_create_user_keys(); + let user = User::new(user_keys, USER_PASSWORD.to_owned(), PROCESS.to_owned()).unwrap(); + let pre_id = user.pre_id; let recover_data = user.recover_data; let sharding = user.sharding; - let retrieved_recover_spend = User::login(USER_PASSWORD.to_owned(), &recover_data, sharding); + let retrieved_recover_spend = User::login( + pre_id.clone(), + USER_PASSWORD.to_owned(), + &recover_data, + sharding, + ); assert!(retrieved_recover_spend.is_ok()); - assert!(format!("{}", retrieved_recover_spend.unwrap().display_secret()) == RECOVER_SPEND) + let connected = CONNECTED_USERS.get().unwrap().lock().unwrap(); + + let recover = &connected.get(&pre_id).unwrap().recover; + + assert!( + format!( + "{}", + recover.try_get_secret_spend_key().unwrap().display_secret() + ) == RECOVER_SPEND + ) } -}*/ +}