diff --git a/crates/sp_client/src/api.rs b/crates/sp_client/src/api.rs index e118698..eb6b1aa 100644 --- a/crates/sp_client/src/api.rs +++ b/crates/sp_client/src/api.rs @@ -16,6 +16,7 @@ use serde_json::Error as SerdeJsonError; use shamir::SecretData; use sp_client::bitcoin::consensus::{deserialize, serialize}; use sp_client::bitcoin::hex::{parse, DisplayHex, FromHex, HexToBytesError}; +use sp_client::bitcoin::secp256k1::ecdh::shared_secret_point; use sp_client::bitcoin::secp256k1::{PublicKey, SecretKey}; use sp_client::bitcoin::{Amount, OutPoint, Transaction, Txid}; use sp_client::silentpayments::Error as SpError; @@ -336,27 +337,40 @@ pub fn check_transaction_for_silent_payments( tweak_data_hex: String, ) -> ApiResult { let tx = deserialize::(&Vec::from_hex(&tx_hex)?)?; + // check that we don't already have scanned the tx + if lock_scanned_transactions()?.contains_key(&tx.txid()) { + return Err(ApiError { message: "Transaction already scanned".to_owned()}); + } + let tweak_data = PublicKey::from_str(&tweak_data_hex)?; 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) { + let shared_point = shared_secret_point(&tweak_data, &recover.get_client().get_scan_key()); + lock_scanned_transactions()?.insert(tx.txid(), vec![("".to_owned(), AnkSharedSecret::new(shared_point, false))]); return Ok(txid); } } if let Ok(main) = connected_user.try_get_mut_main() { if let Ok(txid) = check_transaction(&tx, main, blockheight, tweak_data) { + let shared_point = shared_secret_point(&tweak_data, &main.get_client().get_scan_key()); + lock_scanned_transactions()?.insert(tx.txid(), vec![("".to_owned(), AnkSharedSecret::new(shared_point, false))]); return Ok(txid); } } if let Ok(revoke) = connected_user.try_get_mut_revoke() { if let Ok(txid) = check_transaction(&tx, revoke, blockheight, tweak_data) { + let shared_point = shared_secret_point(&tweak_data, &revoke.get_client().get_scan_key()); + lock_scanned_transactions()?.insert(tx.txid(), vec![("".to_owned(), AnkSharedSecret::new(shared_point, false))]); return Ok(txid); } } + // we still want to insert an empty entry in our cache to make sure we don't scan the transaction again + lock_scanned_transactions()?.insert(tx.txid(), vec![("".to_owned(), AnkSharedSecret::default())]); Err(ApiError { message: "No output found".to_owned(), }) @@ -399,23 +413,30 @@ pub fn parse_network_msg(raw: String) -> ApiResult { }) } AnkFlag::Unknown => { - let transaction_cache = lock_scanned_transactions()?; // try to decrypt the cipher with all available keys let mut plaintext: String = "".to_owned(); - for (txid, secret_vec) in transaction_cache.iter() { + for (txid, secret_vec) in lock_scanned_transactions()?.iter() { for (shared_with, ank_secret) in secret_vec.iter() { + if *ank_secret == AnkSharedSecret::default() { + continue; + } let shared_secret = ank_secret.to_byte_array(); if let Ok(msg_decrypt) = Aes256Decryption::new( Purpose::Arbitrary, - ank_msg.content.as_bytes().to_vec(), + Vec::from_hex(&ank_msg.content.trim_matches('\"'))?, shared_secret, ) { - if let Ok(plain) = msg_decrypt.decrypt_with_key() { - plaintext = String::from_utf8(plain)?; - break; + match msg_decrypt.decrypt_with_key() { + Ok(plain) => { + plaintext = String::from_utf8(plain)?; + break; + }, + Err(e) => { + debug!("{}", e); + debug!("Failed to decrypt message {} with key {}", ank_msg.content, shared_secret.to_lower_hex_string()); + } } } - continue; } } if plaintext.is_empty() { @@ -523,6 +544,8 @@ pub fn create_notification_transaction( Amount::from_sat(fee_rate.into()), )?; + debug!("Created transaction with secret {}", shared_secret.to_byte_array().to_lower_hex_string()); + let mut address2secret: Vec<(String, AnkSharedSecret)> = vec![]; address2secret.push((sp_address.into(), shared_secret)); @@ -560,7 +583,8 @@ pub fn encrypt_with_key(plaintext: String, key: String) -> ApiResult { )?; let cipher = aes_enc.encrypt_with_aes_key()?; - Ok(String::from_utf8(cipher)?) + + Ok(cipher.to_lower_hex_string()) } #[wasm_bindgen] diff --git a/src/services.ts b/src/services.ts index 802c089..f975b67 100644 --- a/src/services.ts +++ b/src/services.ts @@ -107,17 +107,31 @@ class Services { let notificationInfo = await services.notify_address_for_message(recipientSpAddress, message); if (notificationInfo) { + let ciphers: string[] = []; console.info('Successfully sent notification transaction'); // Save the secret to db // encrypt the message(s) - services.encryptData(message, notificationInfo.address2secret); - // encrypt the key - + for (const [address, ankSharedSecret] of Object.entries(notificationInfo.address2secret)) { + try { + let cipher = await services.encryptData(message, ankSharedSecret.secret); + ciphers.push(cipher); + } catch (error) { + throw error; + } + } + const connection = await services.pickWebsocketConnectionRandom(); + const flag: AnkFlag = "Unknown"; + // for testing we only take the first cipher + const payload = ciphers.at(0); + if (!payload) { + console.error("No payload"); + return; + } // add peers list // add processes list // send message (transaction in envelope) + connection?.sendMessage(flag, payload); } - console.log(notificationInfo); } public async createId(event: Event): Promise { @@ -224,6 +238,10 @@ class Services { if (user) { services.sdkClient.login_user(password, user.pre_id, user.recover_data, user.shares, user.outputs); this.sp_address = services.sdkClient.get_recover_address(); + if (this.sp_address) { + console.info('Using sp_address:', this.sp_address); + await services.obtainTokenWithFaucet(this.sp_address); + } } } catch (error) { console.error(error); @@ -802,7 +820,7 @@ class Services { } try { - const feeRate = 1000; + const feeRate = 1; let notificationInfo: createNotificationTransactionReturn = services.sdkClient.create_notification_transaction(sp_address, message, feeRate); const flag: AnkFlag = "NewTx"; const newTxMsg: NewTxMessage = { @@ -817,26 +835,38 @@ class Services { } } - public async encryptData(data: string, sharedSecret: Record): Promise { + // public async encryptData(data: string, sharedSecret: Record): Promise> { + // 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(); + // for (const [recipient, secret] of Object.entries(sharedSecret)) { + // try { + // const key = secret.secret; + // const encryptedKey: string = 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}`); + // } + // } + + // return res; + // } + public async encryptData(data: string, key: string): Promise { const services = await Services.getInstance(); - let msg_cipher: encryptWithNewKeyResult; + try { - msg_cipher = services.sdkClient.encrypt_with_new_key(data); + let res: string = services.sdkClient.encrypt_with_key(data, key); + return res; } catch (error) { throw error; } - - let res = new Map(); - 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 { const services = await Services.getInstance(); diff --git a/src/websockets.ts b/src/websockets.ts index c4fc134..79b9872 100644 --- a/src/websockets.ts +++ b/src/websockets.ts @@ -32,6 +32,8 @@ class WebSocketClient { // we received a tx window.alert(`New tx\n${res.message}`); await services.updateOwnedOutputsForUser(); + } else if (res.topic === 'unknown') { + window.alert(`new message: ${res.message}`); } } } else {