use std::any::Any; use std::collections::HashMap; use std::str::FromStr; use std::sync::{Mutex, OnceLock, PoisonError}; use log::debug; use rand::Rng; use anyhow::Error as AnyhowError; use sdk_common::crypto::AnkSharedSecret; use serde_json::Error as SerdeJsonError; use shamir::SecretData; use sp_backend::bitcoin::consensus::{deserialize, serialize}; use sp_backend::bitcoin::hex::{parse, DisplayHex, FromHex, HexToBytesError}; use sp_backend::bitcoin::secp256k1::ecdh::SharedSecret; use sp_backend::bitcoin::secp256k1::{PublicKey, SecretKey}; use sp_backend::bitcoin::{OutPoint, Transaction, Txid}; use sp_backend::silentpayments::Error as SpError; use serde::{Deserialize, Serialize}; use sp_backend::silentpayments::sending::SilentPaymentAddress; use tsify::Tsify; use wasm_bindgen::convert::FromWasmAbi; use wasm_bindgen::prelude::*; use sdk_common::network::{AnkFlag, AnkNetworkMsg, NewTxMessage}; use sp_backend::spclient::{derive_keys_from_seed, OutputList, OwnedOutput, SpClient}; use sp_backend::spclient::{SpWallet, SpendKey}; use crate::images; use crate::silentpayments::{check_transaction, create_transaction_for_address}; use crate::user::{lock_connected_users, User, UserWallets, CONNECTED_USERS}; use crate::process::Process; type ApiResult = Result; const IS_TESTNET: bool = true; #[derive(Debug)] struct ApiError { message: String, } impl From for ApiError { fn from(value: AnyhowError) -> Self { ApiError { message: value.to_string(), } } } impl From for ApiError { fn from(value: SpError) -> Self { ApiError { message: value.to_string(), } } } impl From for ApiError { fn from(value: SerdeJsonError) -> Self { ApiError { message: value.to_string(), } } } impl From for ApiError { fn from(value: HexToBytesError) -> Self { ApiError { message: value.to_string(), } } } impl From for ApiError { fn from(value: sp_backend::bitcoin::secp256k1::Error) -> Self { ApiError { message: value.to_string(), } } } impl From for ApiError { fn from(value: sp_backend::bitcoin::consensus::encode::Error) -> Self { ApiError { message: value.to_string(), } } } impl Into for ApiError { fn into(self) -> JsValue { JsValue::from_str(&self.message) } } #[derive(Tsify, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] #[allow(non_camel_case_types)] pub struct createUserReturn { pub user: User, pub output_list_vec: Vec, } #[wasm_bindgen] pub fn setup() { wasm_logger::init(wasm_logger::Config::default()); } // Should be transfered to annother module pub fn generate_sp_wallet( label: Option, birthday: u32, is_testnet: bool, ) -> ApiResult { let mut seed = [0u8; 64]; rand::thread_rng().fill(&mut seed); let (scan_sk, spend_sk) = derive_keys_from_seed(&seed, is_testnet)?; let sp_client = SpClient::new( label.unwrap_or("default".into()), scan_sk, SpendKey::Secret(spend_sk), None, IS_TESTNET, )?; let our_address: SilentPaymentAddress = sp_client.get_receiving_address().try_into()?; log::info!( "Created client for sp with address: {}", our_address.to_string() ); let res = SpWallet::new(sp_client, None)?; Ok(res) } #[wasm_bindgen] pub fn get_receiving_address(pre_id: String) -> ApiResult { if let Some(my_wallets) = lock_connected_users()?.get(&pre_id) { Ok(my_wallets.recover.get_client().get_receiving_address()) } else { Err(ApiError { message: "Unknown user pre_id".to_owned(), }) } } #[wasm_bindgen] pub fn create_user( password: String, // Attention à la conversion depuis le js label: Option, birthday_main: u32, birthday_signet: u32, process: String, ) -> ApiResult { //recover let sp_wallet_recover = generate_sp_wallet(label.clone(), birthday_signet, true)?; //revoke let sp_wallet_revoke = generate_sp_wallet(label.clone(), birthday_signet, true)?; //mainet let sp_wallet_main = generate_sp_wallet(label, birthday_main, false)?; let user_wallets = UserWallets::new( Some(sp_wallet_main), sp_wallet_recover, Some(sp_wallet_revoke), ); let user = User::new(user_wallets.clone(), password, process)?; let outputs = user_wallets.get_all_outputs(); lock_connected_users()?.insert(user.pre_id.clone(), user_wallets); let generate_user = createUserReturn { user, output_list_vec: outputs, }; Ok(generate_user) } #[wasm_bindgen] pub fn add_data_to_image(image: Vec, data: Vec, is_revoke: bool) -> ApiResult> { let mut new_image: Vec; if is_revoke { new_image = images::BackUpImage::new_revoke(image, &data)?.to_inner(); } else { new_image = images::BackUpImage::new_recover(image, &data)?.to_inner(); } Ok(new_image) } #[derive(Tsify, Serialize, Deserialize)] #[tsify(into_wasm_abi)] #[allow(non_camel_case_types)] pub struct get_process_return(Vec); #[wasm_bindgen] pub fn get_processes() -> ApiResult { let MEMBERS: [String;5] = [ "tsp1qqdvmxycf3c3tf2qhpev0npx25rj05270d6j2pcsrfk2qn5gdy0rpwq6hd9u9sztl3fwmrzzqafzl3ymkq86aqfz5jl5egdkz72tqmhcnrswdz3pk".to_owned(), "tsp1qqwafwn7dcr9d6ta0w8fjtd9s53u72x9qmmtgd8adqr7454xl90a5jq3vw23l2x8ypt55nrg7trl9lwz5xr5j357ucu4sf9rfmvc0zujcpqcps6rm".to_owned(), "tsp1qqw02t5hmg5rxpjdkmjdnnmhvuc76wt6vlqdmn2zafnh6axxjd6e2gqcz04gzvnkzf572mur8spyx2a2s8sqzll2ymdpyz59cpl96j4zuvcdvrzxz".to_owned(), "tsp1qqgpay2r5jswm7vcv24xd94shdf90w30vxtql9svw7qnlnrzd6xt02q7s7z57uw0sssh6c0xddcrryq4mxup93jsh3gfau3autrawl8umkgsyupkm".to_owned(), "tsp1qqtsqmtgnxp0lsmnxyxcq52zpgxwugwlq8urlprs5pr5lwyqc789gjqhx5qra6g4rszsq43pms6nguee2l9trx905rk5sgntek05hnf7say4ru69y".to_owned(), ]; //instances of process let process1 = Process { id: 6, name: String::from("Messaging"), version: String::from("1.0"), members: MEMBERS.to_vec(), html: crate::process::HTML_MESSAGING.to_owned(), style: crate::process::CSS.to_owned(), script: "".to_owned(), }; let process2 = Process { id: 7, name: String::from("Kotpart"), version: String::from("1.0"), members: MEMBERS.to_vec(), html: crate::process::HTML_MESSAGING.to_owned(), style: crate::process::CSS.to_owned(), script: "".to_owned(), }; let process3 = Process { id: 8, name: String::from("Storage"), version: String::from("1.0"), members: MEMBERS.to_vec(), html: crate::process::HTML_MESSAGING.to_owned(), style: crate::process::CSS.to_owned(), script: "".to_owned(), }; // vec with the instances of processes let mut data_process: Vec = Vec::new(); data_process.push(process1); data_process.push(process2); data_process.push(process3); Ok(get_process_return(data_process)) } #[derive(Debug, Tsify, Serialize, Deserialize)] #[tsify(from_wasm_abi)] #[allow(non_camel_case_types)] pub struct recover_data(Vec); impl recover_data { fn as_inner(&self) -> &[u8] { &self.0 } } #[derive(Debug, Tsify, Serialize, Deserialize)] #[tsify(from_wasm_abi)] #[allow(non_camel_case_types)] pub struct shamir_shares(Vec>); impl shamir_shares { fn as_inner(&self) -> &[Vec] { &self.0 } } #[derive(Debug, Tsify, Serialize, Deserialize)] #[tsify(from_wasm_abi, into_wasm_abi)] #[allow(non_camel_case_types)] pub struct outputs_list(Vec); impl outputs_list { fn as_inner(&self) -> &[OutputList] { &self.0 } } #[wasm_bindgen] pub fn login_user( user_password: String, pre_id: String, recover: recover_data, shares: shamir_shares, outputs: outputs_list, ) -> ApiResult<()> { let res = User::login( pre_id, user_password, recover.as_inner(), shares.as_inner(), outputs.as_inner(), )?; Ok(res) } #[wasm_bindgen] pub fn check_transaction_for_silent_payments( tx_hex: String, blockheight: u32, tweak_data_hex: String, ) -> ApiResult { let tx = deserialize::(&Vec::from_hex(&tx_hex)?)?; let tweak_data = PublicKey::from_str(&tweak_data_hex)?; let updated_user = check_transaction(&tx, blockheight, tweak_data)?; Ok(updated_user) } #[derive(Tsify, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] #[allow(non_camel_case_types)] pub struct parseNetworkMsgReturn { topic: String, message: String, } #[wasm_bindgen] pub fn parse_network_msg(raw: String) -> ApiResult { if let Ok(ank_msg) = serde_json::from_str::(&raw) { match ank_msg.flag { AnkFlag::NewTx => { let tx_message = serde_json::from_str::(&ank_msg.content)?; let tx = deserialize::(&Vec::from_hex(&tx_message.transaction)?)?; if tx_message.tweak_data.is_none() { return Err(ApiError { message: "Missing tweak_data".to_owned(), }); } let partial_tweak = PublicKey::from_str(&tx_message.tweak_data.unwrap())?; let txid = check_transaction(&tx, 0, partial_tweak)?; return Ok(parseNetworkMsgReturn { topic: AnkFlag::NewTx.as_str().to_owned(), message: txid, }); } AnkFlag::Faucet => unimplemented!(), AnkFlag::Error => { return Ok(parseNetworkMsgReturn { topic: AnkFlag::Error.as_str().to_owned(), message: ank_msg.content.to_owned(), }) } _ => unimplemented!(), } } else { Err(ApiError { message: format!("Can't parse message as a valid 4nk message: {}", raw), }) } } #[wasm_bindgen] pub fn parse_4nk_msg(raw: String) -> Option{ if let Ok(msg) = AnkNetworkMsg::new(&raw) { match msg.topic { AnkTopic::Faucet => { match Txid::from_str(msg.content) { Ok(txid) => { // return the txid for verification Some(txid.to_string()) }, Err(e) => { log::error!("Invalid txid with a \"faucet\" message: {}", e.to_string()); None } } } } } else { log::debug!("Can't parse message as a valid 4nk message: {}", raw); None } }