This commit is contained in:
Sosthene 2024-04-22 17:17:12 +02:00 committed by Sosthene00
parent 26bf0c0865
commit fcdfeeaecc
8 changed files with 263 additions and 252 deletions

View File

@ -8,6 +8,7 @@ name = "sdk_client"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
# sp_client= { path = "../../../sp-client" }
sp_client= { git = "https://github.com/Sosthene00/sp-client", branch = "sp_client" } sp_client= { git = "https://github.com/Sosthene00/sp-client", branch = "sp_client" }
anyhow = "1.0" anyhow = "1.0"
serde = { version = "1.0.188", features = ["derive"] } serde = { version = "1.0.188", features = ["derive"] }
@ -18,7 +19,8 @@ wasm-logger = "0.2.0"
rand = "0.8.5" rand = "0.8.5"
log = "0.4.6" log = "0.4.6"
tsify = { git = "https://github.com/Sosthene00/tsify", branch = "next" } tsify = { git = "https://github.com/Sosthene00/tsify", branch = "next" }
sdk_common = { git = "https://git.4nkweb.com/4nk/sdk_common.git", branch = "demo" } sdk_common = { path = "../../../sdk_common" }
#sdk_common = { git = "https://git.4nkweb.com/4nk/sdk_common.git", branch = "demo" }
shamir = { git = "https://github.com/Sosthene00/shamir", branch = "master" } shamir = { git = "https://github.com/Sosthene00/shamir", branch = "master" }
img-parts = "0.3.0" img-parts = "0.3.0"

View File

@ -2,20 +2,22 @@ use std::any::Any;
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Write; use std::io::Write;
use std::str::FromStr; use std::str::FromStr;
use std::string::FromUtf8Error;
use std::sync::{Mutex, OnceLock, PoisonError}; use std::sync::{Mutex, OnceLock, PoisonError};
use log::debug; use log::debug;
use rand::Rng; use rand::{Fill, Rng};
use anyhow::Error as AnyhowError; use anyhow::Error as AnyhowError;
use sdk_common::crypto::AnkSharedSecret; use sdk_common::crypto::{
AeadCore, Aes256Decryption, Aes256Encryption, Aes256Gcm, AnkSharedSecret, KeyInit, Purpose,
};
use serde_json::Error as SerdeJsonError; use serde_json::Error as SerdeJsonError;
use shamir::SecretData; use shamir::SecretData;
use sp_client::bitcoin::consensus::{deserialize, serialize}; use sp_client::bitcoin::consensus::{deserialize, serialize};
use sp_client::bitcoin::hex::{parse, DisplayHex, FromHex, HexToBytesError}; use sp_client::bitcoin::hex::{parse, DisplayHex, FromHex, HexToBytesError};
use sp_client::bitcoin::secp256k1::ecdh::SharedSecret;
use sp_client::bitcoin::secp256k1::{PublicKey, SecretKey}; use sp_client::bitcoin::secp256k1::{PublicKey, SecretKey};
use sp_client::bitcoin::{OutPoint, Transaction, Txid}; use sp_client::bitcoin::{Amount, OutPoint, Transaction, Txid};
use sp_client::silentpayments::Error as SpError; use sp_client::silentpayments::Error as SpError;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -25,15 +27,15 @@ use wasm_bindgen::convert::FromWasmAbi;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use sdk_common::network::{AnkFlag, AnkNetworkMsg, NewTxMessage}; use sdk_common::network::{AnkFlag, AnkNetworkMsg, NewTxMessage};
use sdk_common::silentpayments::{
check_transaction, create_transaction_for_address_with_shared_secret,
};
use sp_client::spclient::{derive_keys_from_seed, OutputList, OwnedOutput, SpClient}; use sp_client::spclient::{derive_keys_from_seed, OutputList, OwnedOutput, SpClient};
use sp_client::spclient::{SpWallet, SpendKey}; use sp_client::spclient::{SpWallet, SpendKey};
use crate::images;
use crate::silentpayments::{
check_transaction, create_transaction_for_address_with_shared_secret, ScannedTransaction,
};
use crate::user::{lock_connected_user, User, UserWallets, CONNECTED_USER}; use crate::user::{lock_connected_user, User, UserWallets, CONNECTED_USER};
use crate::{images, lock_scanned_transactions, Txid2Secrets};
use crate::process::Process; use crate::process::Process;
@ -94,6 +96,14 @@ impl From<sp_client::bitcoin::consensus::encode::Error> for ApiError {
} }
} }
impl From<FromUtf8Error> for ApiError {
fn from(value: FromUtf8Error) -> Self {
ApiError {
message: value.to_string(),
}
}
}
impl Into<JsValue> for ApiError { impl Into<JsValue> for ApiError {
fn into(self) -> JsValue { fn into(self) -> JsValue {
JsValue::from_str(&self.message) JsValue::from_str(&self.message)
@ -143,7 +153,10 @@ pub fn generate_sp_wallet(
#[wasm_bindgen] #[wasm_bindgen]
pub fn get_recover_address() -> ApiResult<String> { pub fn get_recover_address() -> ApiResult<String> {
if let Ok(my_wallets) = lock_connected_user() { if let Ok(my_wallets) = lock_connected_user() {
Ok(my_wallets.try_get_recover()?.get_client().get_receiving_address()) Ok(my_wallets
.try_get_recover()?
.get_client()
.get_receiving_address())
} else { } else {
Err(ApiError { Err(ApiError {
message: "Unknown user pre_id".to_owned(), message: "Unknown user pre_id".to_owned(),
@ -154,7 +167,10 @@ pub fn get_recover_address() -> ApiResult<String> {
#[wasm_bindgen] #[wasm_bindgen]
pub fn get_main_address() -> ApiResult<String> { pub fn get_main_address() -> ApiResult<String> {
if let Ok(my_wallets) = lock_connected_user() { if let Ok(my_wallets) = lock_connected_user() {
Ok(my_wallets.try_get_main()?.get_client().get_receiving_address()) Ok(my_wallets
.try_get_main()?
.get_client()
.get_receiving_address())
} else { } else {
Err(ApiError { Err(ApiError {
message: "Unknown user pre_id".to_owned(), message: "Unknown user pre_id".to_owned(),
@ -322,9 +338,28 @@ pub fn check_transaction_for_silent_payments(
let tx = deserialize::<Transaction>(&Vec::from_hex(&tx_hex)?)?; let tx = deserialize::<Transaction>(&Vec::from_hex(&tx_hex)?)?;
let tweak_data = PublicKey::from_str(&tweak_data_hex)?; let tweak_data = PublicKey::from_str(&tweak_data_hex)?;
let txid = check_transaction(&tx, blockheight, tweak_data)?; let mut connected_user = lock_connected_user()?;
if let Ok(recover) = connected_user.try_get_mut_recover() {
if let Ok(txid) = check_transaction(&tx, recover, blockheight, tweak_data) {
return Ok(txid);
}
}
Ok(txid) if let Ok(main) = connected_user.try_get_mut_main() {
if let Ok(txid) = check_transaction(&tx, main, blockheight, tweak_data) {
return Ok(txid);
}
}
if let Ok(revoke) = connected_user.try_get_mut_revoke() {
if let Ok(txid) = check_transaction(&tx, revoke, blockheight, tweak_data) {
return Ok(txid);
}
}
Err(ApiError {
message: "No output found".to_owned(),
})
} }
#[derive(Tsify, Serialize, Deserialize)] #[derive(Tsify, Serialize, Deserialize)]
@ -341,14 +376,16 @@ pub fn parse_network_msg(raw: String) -> ApiResult<parseNetworkMsgReturn> {
match ank_msg.flag { match ank_msg.flag {
AnkFlag::NewTx => { AnkFlag::NewTx => {
let tx_message = serde_json::from_str::<NewTxMessage>(&ank_msg.content)?; let tx_message = serde_json::from_str::<NewTxMessage>(&ank_msg.content)?;
let tx = deserialize::<Transaction>(&Vec::from_hex(&tx_message.transaction)?)?;
if tx_message.tweak_data.is_none() { if tx_message.tweak_data.is_none() {
return Err(ApiError { return Err(ApiError {
message: "Missing tweak_data".to_owned(), message: "Missing tweak_data".to_owned(),
}); });
} }
let partial_tweak = PublicKey::from_str(&tx_message.tweak_data.unwrap())?; let txid = check_transaction_for_silent_payments(
let txid = check_transaction(&tx, 0, partial_tweak)?; tx_message.transaction,
0,
tx_message.tweak_data.unwrap(),
)?;
return Ok(parseNetworkMsgReturn { return Ok(parseNetworkMsgReturn {
topic: AnkFlag::NewTx.as_str().to_owned(), topic: AnkFlag::NewTx.as_str().to_owned(),
message: txid, message: txid,
@ -376,7 +413,27 @@ pub fn get_outpoints_for_user() -> ApiResult<outputs_list> {
if connected_user.is_not_empty() { if connected_user.is_not_empty() {
Ok(outputs_list(connected_user.get_all_outputs())) Ok(outputs_list(connected_user.get_all_outputs()))
} else { } else {
Err(ApiError { message: "No user logged in".to_owned() }) Err(ApiError {
message: "No user logged in".to_owned(),
})
}
}
#[wasm_bindgen]
pub fn get_available_amount_for_user(recover: bool) -> ApiResult<u64> {
let connected_user = lock_connected_user()?;
if recover {
if let Ok(recover_wallet) = connected_user.try_get_recover() {
Ok(recover_wallet.get_outputs().get_balance().to_sat())
} else {
Err(ApiError {
message: "User doesn't have recover wallet available".to_owned(),
})
}
} else {
Err(ApiError {
message: "No user logged in".to_owned(),
})
} }
} }
@ -403,28 +460,117 @@ pub fn is_tx_owned_by_user(pre_id: String, tx: String) -> ApiResult<bool> {
#[tsify(into_wasm_abi, from_wasm_abi)] #[tsify(into_wasm_abi, from_wasm_abi)]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub struct createNotificationTransactionReturn { pub struct createNotificationTransactionReturn {
pub txid: String,
pub transaction: String, pub transaction: String,
pub transaction2secret: ScannedTransaction, pub address2secret: HashMap<String, AnkSharedSecret>,
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn create_notification_transaction( pub fn create_notification_transaction(
recipient: String, recipient: String,
message: String, message: String,
fee_rate: u32,
) -> ApiResult<createNotificationTransactionReturn> { ) -> ApiResult<createNotificationTransactionReturn> {
let sp_address: SilentPaymentAddress = recipient.try_into()?; let sp_address: SilentPaymentAddress = recipient.try_into()?;
let (transaction, shared_secret) = let connected_user = lock_connected_user()?;
create_transaction_for_address_with_shared_secret(sp_address, message)?;
let sp_wallet: &SpWallet;
if sp_address.is_testnet() {
sp_wallet = connected_user.try_get_recover()?;
} else {
sp_wallet = connected_user.try_get_main()?;
}
let (transaction, shared_secret) = create_transaction_for_address_with_shared_secret(
sp_address,
sp_wallet,
message,
Amount::from_sat(fee_rate.into()),
)?;
let mut address2secret: Vec<(String, AnkSharedSecret)> = vec![];
address2secret.push((sp_address.into(), shared_secret));
// update our cache // update our cache
lock_scanned_transactions()?
let mut transaction2secret = ScannedTransaction::new(); .insert(transaction.txid(), address2secret.clone());
transaction2secret.get_mut().insert(transaction.txid(), vec![shared_secret]);
Ok(createNotificationTransactionReturn { Ok(createNotificationTransactionReturn {
txid: transaction.txid().to_string(),
transaction: serialize(&transaction).to_lower_hex_string(), transaction: serialize(&transaction).to_lower_hex_string(),
transaction2secret, address2secret: address2secret.into_iter().collect()
}) })
} }
#[derive(Tsify, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[allow(non_camel_case_types)]
pub struct encryptWithNewKeyResult {
pub cipher: String,
pub key: String,
}
#[wasm_bindgen]
pub fn encrypt_with_key(plaintext: String, key: String) -> ApiResult<String> {
let nonce = Aes256Gcm::generate_nonce(&mut rand::thread_rng());
let mut aes_key = [0u8;32];
aes_key.copy_from_slice(&Vec::from_hex(&key)?);
// encrypt
let aes_enc = Aes256Encryption::import_key(
Purpose::Arbitrary,
plaintext.into_bytes(),
aes_key,
nonce.into(),
)?;
let cipher = aes_enc.encrypt_with_aes_key()?;
Ok(String::from_utf8(cipher)?)
}
#[wasm_bindgen]
pub fn encrypt_with_new_key(plaintext: String) -> ApiResult<encryptWithNewKeyResult> {
let mut rng = rand::thread_rng();
// generate new key
let aes_key = Aes256Gcm::generate_key(&mut rng);
let nonce = Aes256Gcm::generate_nonce(&mut rng);
// encrypt
let aes_enc = Aes256Encryption::import_key(
Purpose::Arbitrary,
plaintext.into_bytes(),
aes_key.into(),
nonce.into(),
)?;
let cipher = aes_enc.encrypt_with_aes_key()?;
Ok(encryptWithNewKeyResult {
cipher: cipher.to_lower_hex_string(),
key: aes_key.to_lower_hex_string(),
})
}
#[wasm_bindgen]
pub fn try_decrypt_with_key(
cipher: String,
key: String,
) -> ApiResult<String> {
let key_bin = Vec::from_hex(&key)?;
if key_bin.len() != 32 {
return Err(ApiError { message: "key of invalid lenght".to_owned() });
}
let mut aes_key = [0u8;32];
aes_key.copy_from_slice(&Vec::from_hex(&key)?);
let aes_dec = Aes256Decryption::new(
Purpose::Arbitrary,
Vec::from_hex(&cipher)?,
aes_key
)?;
let plain = String::from_utf8(aes_dec.decrypt_with_key()?)?;
Ok(plain)
}

View File

@ -1,16 +1,31 @@
#![allow(warnings)] #![allow(warnings)]
use anyhow::Error; use anyhow::Error;
use sdk_common::crypto::AnkSharedSecret;
use serde::{Deserialize, Serialize};
use sp_client::bitcoin::Txid;
use sp_client::silentpayments::sending::SilentPaymentAddress;
use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::sync::{Mutex, MutexGuard}; use std::sync::{Mutex, MutexGuard, OnceLock};
use tsify::Tsify;
mod Prd_list; mod Prd_list;
pub mod api; pub mod api;
mod images; mod images;
mod peers; mod peers;
mod process; mod process;
mod silentpayments;
mod user; mod user;
pub type Txid2Secrets = HashMap<Txid, Vec<(String, AnkSharedSecret)>>;
pub static TRANSACTIONCACHE: OnceLock<Mutex<Txid2Secrets>> = OnceLock::new();
pub fn lock_scanned_transactions() -> Result<MutexGuard<'static, Txid2Secrets>, Error> {
TRANSACTIONCACHE
.get_or_init(|| Mutex::new(Txid2Secrets::new()))
.lock_anyhow()
}
pub(crate) trait MutexExt<T> { pub(crate) trait MutexExt<T> {
fn lock_anyhow(&self) -> Result<MutexGuard<T>, Error>; fn lock_anyhow(&self) -> Result<MutexGuard<T>, Error>;
} }

View File

@ -1,217 +0,0 @@
// This file should move to common
use std::collections::{HashMap, HashSet};
use std::io::Write;
use std::iter::Once;
use std::sync::{Mutex, MutexGuard, OnceLock};
use anyhow::{Error, Result};
use log::debug;
use rand::Rng;
use sdk_common::crypto::{Aes256Encryption, Aes256Gcm, AeadCore, Purpose};
use serde::{Deserialize, Serialize};
use sp_client::bitcoin::policy::DUST_RELAY_TX_FEE;
use sp_client::bitcoin::secp256k1::ecdh::shared_secret_point;
use sp_client::bitcoin::{block, Amount, OutPoint, Txid};
use sp_client::silentpayments::sending::SilentPaymentAddress;
use sp_client::silentpayments::utils::receiving::calculate_shared_secret;
use sp_client::spclient::{OutputList, OwnedOutput, Recipient, SpClient, SpWallet};
use sp_client::silentpayments::bitcoin_hashes::{sha256, Hash, HashEngine};
use sp_client::{
bitcoin::{
secp256k1::{PublicKey, Scalar, XOnlyPublicKey},
Transaction,
},
silentpayments::receiving::Label,
};
use tsify::Tsify;
use crate::user::{lock_connected_user, CONNECTED_USER};
use crate::MutexExt;
#[derive(Debug, Serialize, Deserialize, Tsify)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct ScannedTransaction(HashMap<Txid, Vec<SharedSecret>>);
impl ScannedTransaction {
pub fn new() -> Self {
Self(HashMap::new())
}
pub fn get_mut(&mut self) -> &mut HashMap<Txid, Vec<SharedSecret>> {
&mut self.0
}
}
pub static TRANSACTIONCACHE: OnceLock<Mutex<ScannedTransaction>> = OnceLock::new();
pub fn lock_scanned_transactions() -> Result<MutexGuard<'static, ScannedTransaction>> {
TRANSACTIONCACHE
.get_or_init(|| Mutex::new(ScannedTransaction::new()))
.lock_anyhow()
}
type FoundOutputs = HashMap<Option<Label>, HashMap<XOnlyPublicKey, Scalar>>;
#[derive(Debug)]
pub struct SharedPoint([u8; 64]);
impl SharedPoint {
pub fn as_inner(&self) -> &[u8; 64] {
&self.0
}
}
#[derive(Debug, Serialize, Deserialize, Tsify)]
#[tsify(from_wasm_abi, into_wasm_abi)]
pub struct SharedSecret {
secret: [u8; 32],
shared_with: Option<String>, // SilentPaymentAddress
}
impl SharedSecret {
pub fn new(secret: [u8;32], shared_with: Option<String>) -> Result<Self> {
if let Some(ref shared) = shared_with {
if let Ok(_) = SilentPaymentAddress::try_from(shared.as_str()) {
Ok(Self {
secret,
shared_with
})
} else {
Err(Error::msg("Invalid silent payment address"))
}
} else {
Ok(Self {
secret,
shared_with: None
})
}
}
}
pub fn create_transaction_for_address_with_shared_secret(
sp_address: SilentPaymentAddress,
message: String,
) -> Result<(Transaction, SharedSecret)> {
let connected_user = lock_connected_user()?;
let sp_wallet = if sp_address.is_testnet() {
connected_user.try_get_recover()?
} else {
if let Ok(main) = connected_user.try_get_main() {
main
} else {
return Err(Error::msg("Can't spend on mainnet"));
}
};
let available_outpoints = sp_wallet.get_outputs().to_spendable_list();
// Here we need to add more heuristics about which outpoint we spend
// For now let's keep it simple
let mut inputs: HashMap<OutPoint, OwnedOutput> = HashMap::new();
let total_available =
available_outpoints
.into_iter()
.try_fold(Amount::from_sat(0), |acc, (outpoint, output)| {
let new_total = acc + output.amount;
inputs.insert(outpoint, output);
if new_total > Amount::from_sat(1000) {
Err(new_total)
} else {
Ok(new_total)
}
});
match total_available {
Err(total) => log::debug!("Spending {} outputs totaling {} sats", inputs.len(), total),
Ok(_) => return Err(Error::msg("Not enought fund available")),
}
let recipient = Recipient {
address: sp_address.into(),
amount: Amount::from_sat(1000),
nb_outputs: 1,
};
let mut new_psbt = sp_wallet
.get_client()
.create_new_psbt(inputs, vec![recipient], Some(message.as_bytes()))?;
log::debug!("Created psbt: {}", new_psbt);
SpClient::set_fees(&mut new_psbt, Amount::from_sat(1000), sp_address.into())?;
let partial_secret = sp_wallet
.get_client()
.get_partial_secret_from_psbt(&new_psbt)?;
// This wouldn't work with many recipients in the same transaction
// each address (or more precisely each scan public key) would have its own point
let shared_point = shared_secret_point(&sp_address.get_scan_key(), &partial_secret);
// The shared_point *must* be hashed to produce a proper ecdh secret
let mut eng = sha256::HashEngine::default();
eng.write_all(&shared_point);
let shared_secret = sha256::Hash::from_engine(eng);
// encrypt the message with the new shared_secret
let message_encryption = Aes256Encryption::import_key(
Purpose::Arbitrary,
message.into_bytes(),
shared_secret.to_byte_array(),
Aes256Gcm::generate_nonce(&mut rand::thread_rng()).into(),
)?;
let cipher = message_encryption.encrypt_with_aes_key()?;
sp_client::spclient::SpClient::replace_op_return_with(&mut new_psbt, &cipher)?;
sp_wallet
.get_client()
.fill_sp_outputs(&mut new_psbt, partial_secret)?;
log::debug!("Definitive psbt: {}", new_psbt);
let mut aux_rand = [0u8; 32];
rand::thread_rng().fill(&mut aux_rand);
let mut signed = sp_wallet.get_client().sign_psbt(new_psbt, &aux_rand)?;
log::debug!("signed psbt: {}", signed);
SpClient::finalize_psbt(&mut signed)?;
let final_tx = signed.extract_tx()?;
Ok((final_tx, SharedSecret {
secret: shared_secret.to_byte_array(),
shared_with: Some(sp_address.into())
}))
}
// This need to go
pub fn check_transaction(
tx: &Transaction,
blockheight: u32,
tweak_data: PublicKey,
) -> Result<String> {
// first check that we haven't scanned this transaction yet
if let Some((txid, _)) = lock_scanned_transactions()?.0.get_key_value(&tx.txid()) {
let err_msg = format!("Already scanned tx {}", txid);
return Err(Error::msg(err_msg));
}
let mut connected_user = lock_connected_user()?;
let txid = tx.txid().to_string();
if connected_user.try_get_mut_recover()?.update_wallet_with_transaction(tx, blockheight, tweak_data)? > 0 {
return Ok(txid);
}
if connected_user.try_get_mut_main()?.update_wallet_with_transaction(tx, blockheight, tweak_data)? > 0 {
return Ok(txid);
}
if connected_user.try_get_mut_revoke()?.update_wallet_with_transaction(tx, blockheight, tweak_data)? > 0 {
return Ok(txid);
}
return Err(Error::msg("No new outputs found"));
}

View File

@ -52,7 +52,11 @@ pub struct UserWallets {
} }
impl UserWallets { impl UserWallets {
pub fn new(main: Option<SpWallet>, recover: Option<SpWallet>, revoke: Option<SpWallet>) -> Self { pub fn new(
main: Option<SpWallet>,
recover: Option<SpWallet>,
revoke: Option<SpWallet>,
) -> Self {
Self { Self {
main, main,
recover, recover,

View File

@ -1,7 +1,7 @@
import Services from './services'; import Services from './services';
import { WebSocketClient } from './websockets'; import { WebSocketClient } from './websockets';
const wsurl = "ws://192.168.1.44:8090"; const wsurl = "ws://localhost:8090";
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
try { try {
const services = await Services.getInstance(); const services = await Services.getInstance();

View File

@ -1,4 +1,4 @@
import { createUserReturn, User, Process, createNotificationTransactionReturn, parse_network_msg, outputs_list, parseNetworkMsgReturn, FaucetMessage, AnkFlag, NewTxMessage } from '../dist/pkg/sdk_client'; import { createUserReturn, User, Process, createNotificationTransactionReturn, parse_network_msg, outputs_list, parseNetworkMsgReturn, FaucetMessage, AnkFlag, NewTxMessage, encryptWithNewKeyResult, AnkSharedSecret } from '../dist/pkg/sdk_client';
import IndexedDB from './database' import IndexedDB from './database'
import { WebSocketClient } from './websockets'; import { WebSocketClient } from './websockets';
@ -74,6 +74,26 @@ class Services {
public async sendMessage(event: Event): Promise<void> { public async sendMessage(event: Event): Promise<void> {
event.preventDefault(); event.preventDefault();
const services = await Services.getInstance();
let availableAmt: number = 0;
// check available amount
try {
availableAmt = await services.sdkClient.get_available_amount_for_user(true);
} catch (error) {
console.error('Failed to get available amount');
return;
}
if (availableAmt < 2000) {
try {
await services.obtainTokenWithFaucet(this.sp_address!);
} catch (error) {
console.error('Failed to obtain faucet token:', error);
return;
}
}
const spAddressElement = document.getElementById("sp_address") as HTMLInputElement; const spAddressElement = document.getElementById("sp_address") as HTMLInputElement;
const messageElement = document.getElementById("message") as HTMLInputElement; const messageElement = document.getElementById("message") as HTMLInputElement;
@ -84,11 +104,18 @@ class Services {
const recipientSpAddress = spAddressElement.value; const recipientSpAddress = spAddressElement.value;
const message = messageElement.value; const message = messageElement.value;
const services = await Services.getInstance();
let notificationInfo = await services.notify_address_for_message(recipientSpAddress, message); let notificationInfo = await services.notify_address_for_message(recipientSpAddress, message);
if (notificationInfo) { if (notificationInfo) {
notificationInfo.transaction console.info('Successfully sent notification transaction');
// Save the secret to db
// encrypt the message(s)
services.encryptData(message, notificationInfo.address2secret);
// encrypt the key
// add peers list
// add processes list
// send message (transaction in envelope)
} }
console.log(notificationInfo); console.log(notificationInfo);
} }
@ -151,7 +178,7 @@ class Services {
} }
try { try {
this.sp_address = services.sdkClient.get_receiving_address(user.pre_id); this.sp_address = services.sdkClient.get_recover_address();
if (this.sp_address) { if (this.sp_address) {
console.info('Using sp_address:', this.sp_address); console.info('Using sp_address:', this.sp_address);
await services.obtainTokenWithFaucet(this.sp_address); await services.obtainTokenWithFaucet(this.sp_address);
@ -196,12 +223,14 @@ class Services {
const user = await services.getUserInfo(); const user = await services.getUserInfo();
if (user) { if (user) {
services.sdkClient.login_user(password, user.pre_id, user.recover_data, user.shares, user.outputs); services.sdkClient.login_user(password, user.pre_id, user.recover_data, user.shares, user.outputs);
this.sp_address = services.sdkClient.get_receiving_address(user?.pre_id); this.sp_address = services.sdkClient.get_recover_address();
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
console.info(this.sp_address);
// TODO: check blocks since last_scan and update outputs // TODO: check blocks since last_scan and update outputs
await services.displaySendMessage(); await services.displaySendMessage();
@ -341,6 +370,7 @@ class Services {
let user = await services.getUserInfo(); let user = await services.getUserInfo();
if (user) { if (user) {
user.outputs = latest_outputs; user.outputs = latest_outputs;
// console.warn(user);
await services.updateUser(user); await services.updateUser(user);
} }
} catch (error) { } catch (error) {
@ -772,7 +802,8 @@ class Services {
} }
try { try {
let notificationInfo: createNotificationTransactionReturn = services.sdkClient.create_notification_transaction(user, sp_address, message); const feeRate = 1000;
let notificationInfo: createNotificationTransactionReturn = services.sdkClient.create_notification_transaction(sp_address, message, feeRate);
const flag: AnkFlag = "NewTx"; const flag: AnkFlag = "NewTx";
const newTxMsg: NewTxMessage = { const newTxMsg: NewTxMessage = {
'transaction': notificationInfo.transaction, 'transaction': notificationInfo.transaction,
@ -785,6 +816,37 @@ class Services {
return null return null
} }
} }
public async encryptData(data: string, sharedSecret: Record<string, AnkSharedSecret>): Promise<encryptWithNewKeyResult> {
const services = await Services.getInstance();
let msg_cipher: encryptWithNewKeyResult;
try {
msg_cipher = services.sdkClient.encrypt_with_new_key(data);
} catch (error) {
throw error;
}
let res = new Map<string, string>();
for (const [recipient, secret] of Object.entries(sharedSecret)) {
try {
const key = secret.secret;
const encryptedKey = await services.sdkClient.encrypt_with_key(msg_cipher.key, key);
res.set(recipient, encryptedKey);
} catch (error) {
throw new Error(`Failed to encrypt key for recipient ${recipient}: ${error}`);
}
}
}
public async decryptData(cipher: string, key: string): Promise<string> {
const services = await Services.getInstance();
try {
let res = services.sdkClient.try_decrypt_with_key(cipher, key);
return res;
} catch (error) {
throw error;
}
}
} }
export default Services; export default Services;

View File

@ -22,7 +22,6 @@ class WebSocketClient {
// Listen for messages // Listen for messages
this.ws.addEventListener('message', (event) => { this.ws.addEventListener('message', (event) => {
const msgData = event.data; const msgData = event.data;
console.log(msgData);
(async () => { (async () => {
if (typeof(msgData) === 'string') { if (typeof(msgData) === 'string') {