Heavy refactor of the caching and message structure
This commit is contained in:
parent
fcafcff69e
commit
0f7bc644c8
@ -8,8 +8,8 @@ name = "sdk_client"
|
|||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# sp_client= { path = "../../../sp-client" }
|
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"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
use std::borrow::Borrow;
|
||||||
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::string::FromUtf8Error;
|
||||||
use std::sync::{Mutex, OnceLock, PoisonError};
|
use std::sync::{Mutex, OnceLock, PoisonError};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use rand::{Fill, Rng};
|
use rand::{thread_rng, Fill, Rng, RngCore};
|
||||||
|
|
||||||
use anyhow::Error as AnyhowError;
|
use anyhow::Error as AnyhowError;
|
||||||
use sdk_common::crypto::{
|
use sdk_common::crypto::{
|
||||||
@ -16,10 +18,13 @@ use serde_json::{Error as SerdeJsonError, Value};
|
|||||||
use shamir::SecretData;
|
use shamir::SecretData;
|
||||||
use sp_client::bitcoin::blockdata::fee_rate;
|
use sp_client::bitcoin::blockdata::fee_rate;
|
||||||
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::hashes::HashEngine;
|
||||||
|
use sp_client::bitcoin::hashes::{sha256, Hash};
|
||||||
|
use sp_client::bitcoin::hex::{parse, DisplayHex, FromHex, HexToArrayError, HexToBytesError};
|
||||||
|
use sp_client::bitcoin::key::Secp256k1;
|
||||||
use sp_client::bitcoin::secp256k1::ecdh::shared_secret_point;
|
use sp_client::bitcoin::secp256k1::ecdh::shared_secret_point;
|
||||||
use sp_client::bitcoin::secp256k1::{PublicKey, SecretKey};
|
use sp_client::bitcoin::secp256k1::{PublicKey, SecretKey};
|
||||||
use sp_client::bitcoin::{Amount, OutPoint, Transaction, Txid};
|
use sp_client::bitcoin::{Amount, Network, OutPoint, Psbt, Transaction, Txid};
|
||||||
use sp_client::silentpayments::Error as SpError;
|
use sp_client::silentpayments::Error as SpError;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -28,16 +33,21 @@ use tsify::Tsify;
|
|||||||
use wasm_bindgen::convert::FromWasmAbi;
|
use wasm_bindgen::convert::FromWasmAbi;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use sdk_common::network::{AnkFlag, AnkNetworkMsg, NewTxMessage, UnknownMessage};
|
use sdk_common::network::{
|
||||||
|
self, AnkFlag, AnkNetworkMsg, FaucetMessage, NewTxMessage, UnknownMessage,
|
||||||
|
};
|
||||||
use sdk_common::silentpayments::{
|
use sdk_common::silentpayments::{
|
||||||
check_transaction, create_transaction, create_transaction_for_address_with_shared_secret,
|
create_transaction, create_transaction_for_address_with_shared_secret,
|
||||||
|
create_transaction_spend_outpoint, map_outputs_to_sp_address
|
||||||
};
|
};
|
||||||
|
|
||||||
use sp_client::spclient::{derive_keys_from_seed, OutputList, OwnedOutput, SpClient};
|
use sp_client::spclient::{
|
||||||
|
derive_keys_from_seed, OutputList, OutputSpendStatus, OwnedOutput, Recipient, SpClient,
|
||||||
|
};
|
||||||
use sp_client::spclient::{SpWallet, SpendKey};
|
use sp_client::spclient::{SpWallet, SpendKey};
|
||||||
|
|
||||||
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, lock_secrets, lock_watched, Txid2Secrets};
|
use crate::{images, lock_messages};
|
||||||
|
|
||||||
use crate::process::Process;
|
use crate::process::Process;
|
||||||
|
|
||||||
@ -82,6 +92,30 @@ impl From<HexToBytesError> for ApiError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<HexToArrayError> for ApiError {
|
||||||
|
fn from(value: HexToArrayError) -> Self {
|
||||||
|
ApiError {
|
||||||
|
message: value.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<sp_client::bitcoin::psbt::PsbtParseError> for ApiError {
|
||||||
|
fn from(value: sp_client::bitcoin::psbt::PsbtParseError) -> Self {
|
||||||
|
ApiError {
|
||||||
|
message: value.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<sp_client::bitcoin::psbt::ExtractTxError> for ApiError {
|
||||||
|
fn from(value: sp_client::bitcoin::psbt::ExtractTxError) -> Self {
|
||||||
|
ApiError {
|
||||||
|
message: value.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<sp_client::bitcoin::secp256k1::Error> for ApiError {
|
impl From<sp_client::bitcoin::secp256k1::Error> for ApiError {
|
||||||
fn from(value: sp_client::bitcoin::secp256k1::Error) -> Self {
|
fn from(value: sp_client::bitcoin::secp256k1::Error) -> Self {
|
||||||
ApiError {
|
ApiError {
|
||||||
@ -331,112 +365,184 @@ pub fn login_user(
|
|||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scan_for_confirmation_transaction(tx_hex: String) -> anyhow::Result<String> {
|
fn handle_recover_transaction(
|
||||||
let tx = deserialize::<Transaction>(&Vec::from_hex(&tx_hex)?)?;
|
updated: HashMap<OutPoint, OwnedOutput>,
|
||||||
|
tx: &Transaction,
|
||||||
for i in tx.input.iter() {
|
sp_wallet: &mut SpWallet,
|
||||||
if let Some(waiting) = lock_watched()?.remove(&i.previous_output) {
|
tweak_data: PublicKey,
|
||||||
match lock_secrets()?.get_mut(&waiting) {
|
fee_rate: u32,
|
||||||
None => return Err(anyhow::Error::msg("No secret match for an error we're waiting for")),
|
) -> anyhow::Result<NetworkMessage> {
|
||||||
Some(secret) => {
|
// We need to look for different case:
|
||||||
// for now we only handle the case of one secret for one transaction
|
// 1) faucet
|
||||||
let res = secret.get_mut(0).unwrap();
|
// This one is the simplest, we only care about finding the commitment
|
||||||
res.1.trusted = true;
|
let op_return = tx.output.iter().find(|o| o.script_pubkey.is_op_return());
|
||||||
return Ok(res.0.clone());
|
let commitment = if op_return.is_none() {
|
||||||
}
|
vec![]
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(anyhow::Error::msg("Not spending a watched output"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_recover_transaction(tx: Transaction, sp_wallet: &mut SpWallet, tweak_data: PublicKey, fee_rate: u32) -> anyhow::Result<Option<Transaction>> {
|
|
||||||
// does this transaction spent a txid and output we're waiting confirmation for?
|
|
||||||
let scan_sk = sp_wallet.get_client().get_scan_key();
|
|
||||||
let txid = tx.txid();
|
|
||||||
for input in tx.input {
|
|
||||||
let prevout = input.previous_output;
|
|
||||||
match lock_secrets()?.get_mut(&prevout.txid) {
|
|
||||||
None => {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Some(secret) => {
|
|
||||||
// We found an input spending a notification transaction we sent
|
|
||||||
if let Some(res) = secret.get_mut(prevout.vout as usize) {
|
|
||||||
// This is a challenge from a previous message we sent
|
|
||||||
// we toggle the trusted value
|
|
||||||
if !res.1.trusted {
|
|
||||||
res.1.trusted = true;
|
|
||||||
} else {
|
} else {
|
||||||
return Err(anyhow::Error::msg("Received a confirmation for a transaction we already confirmed"));
|
op_return.unwrap().script_pubkey.as_bytes()[2..].to_vec()
|
||||||
|
};
|
||||||
|
let commitment_str = commitment.to_lower_hex_string();
|
||||||
|
let pos = lock_messages()?
|
||||||
|
.iter()
|
||||||
|
.position(|m| m.commitment.as_ref() == Some(&commitment_str));
|
||||||
|
|
||||||
|
if pos.is_some() {
|
||||||
|
let messages = lock_messages()?;
|
||||||
|
let message = messages.get(pos.unwrap());
|
||||||
|
return Ok(message.cloned().unwrap());
|
||||||
}
|
}
|
||||||
// We spend the output back to the receiver
|
|
||||||
let sp_address = SilentPaymentAddress::try_from(res.0.as_str()).expect("Invalid silent payment address");
|
// If we got updates from a transaction, it means that it creates an output to us, spend an output we owned, or both
|
||||||
let response_tx = create_transaction(sp_address, sp_wallet, Amount::from_sat(fee_rate.into()))?;
|
// If we destroyed outputs it means we either notified others, or ask confirmation, or confirm
|
||||||
return Ok(Some(response_tx));
|
// We probably creates outputs too in this case because of change
|
||||||
|
// If we only created outputs it means we are being notified
|
||||||
|
let utxo_destroyed: HashMap<&OutPoint, &OwnedOutput> = updated
|
||||||
|
.iter()
|
||||||
|
.filter(|(outpoint, output)| output.spend_status != OutputSpendStatus::Unspent)
|
||||||
|
.collect();
|
||||||
|
let utxo_created: HashMap<&OutPoint, &OwnedOutput> = updated
|
||||||
|
.iter()
|
||||||
|
.filter(|(outpoint, output)| output.spend_status == OutputSpendStatus::Unspent)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// 2) confirmation
|
||||||
|
// If the transaction spends one outpoint in `commited_in`, it means we are receiving a confirmation for a notification
|
||||||
|
// if we are receiver, then we must look for `confirmed_by`
|
||||||
|
// if we owned at least one input or no outputs, we can skip the check
|
||||||
|
if utxo_destroyed.is_empty() && !utxo_created.is_empty() {
|
||||||
|
for input in tx.input.iter() {
|
||||||
|
// Check for each input if it match a known commitment we made as a sender
|
||||||
|
// OR a confirmation for the receiver
|
||||||
|
let pos = lock_messages()?.iter().position(|m| {
|
||||||
|
m.commited_in == Some(input.previous_output)
|
||||||
|
|| m.confirmed_by == Some(input.previous_output)
|
||||||
|
});
|
||||||
|
if pos.is_some() {
|
||||||
|
let mut messages = lock_messages()?;
|
||||||
|
let message = messages.get_mut(pos.unwrap()).unwrap();
|
||||||
|
// If we are receiver, that's pretty much it, just set status to complete
|
||||||
|
if message.recipient == Some(sp_wallet.get_client().get_receiving_address()) {
|
||||||
|
debug_assert!(message.confirmed_by == Some(input.previous_output));
|
||||||
|
message.status = NetworkMessageStatus::Complete;
|
||||||
|
return Ok(message.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// sender needs to spent it back again to receiver
|
||||||
|
let (outpoint, output) = utxo_created.iter().next().unwrap();
|
||||||
|
|
||||||
|
// If we are sender, then we must update the confirmed_by field
|
||||||
|
message.confirmed_by = Some(**outpoint);
|
||||||
|
|
||||||
|
// Caller must interpret this message as "spend confirmed_by outpoint to receiver"
|
||||||
|
return Ok(message.clone());
|
||||||
} else {
|
} else {
|
||||||
return Err(anyhow::Error::msg("Received a confirmation from an umapped output"));
|
// we are being notified
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If we exhausted all inputs without finding one of our transaction, it means it's a notification
|
|
||||||
let shared_point =
|
let shared_point =
|
||||||
shared_secret_point(&tweak_data, &scan_sk);
|
shared_secret_point(&tweak_data, &sp_wallet.get_client().get_scan_key());
|
||||||
lock_secrets()?.insert(
|
let shared_secret = AnkSharedSecret::new(shared_point);
|
||||||
txid,
|
|
||||||
vec![("".to_owned(), AnkSharedSecret::new(shared_point, false))],
|
let mut messages = lock_messages()?;
|
||||||
);
|
let cipher_pos = messages.iter().position(|m| {
|
||||||
Ok(None)
|
if m.status != NetworkMessageStatus::CipherWaitingTx {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m.try_decrypt_with_shared_secret(shared_secret.to_byte_array())
|
||||||
|
.is_some()
|
||||||
|
});
|
||||||
|
|
||||||
|
if cipher_pos.is_some() {
|
||||||
|
let message = messages.get_mut(cipher_pos.unwrap()).unwrap();
|
||||||
|
let (outpoint, output) = utxo_created.iter().next().unwrap();
|
||||||
|
message.commited_in = Some(**outpoint);
|
||||||
|
message.shared_secret =
|
||||||
|
Some(shared_secret.to_byte_array().to_lower_hex_string());
|
||||||
|
message.commitment = Some(commitment.to_lower_hex_string());
|
||||||
|
|
||||||
|
let plaintext = message
|
||||||
|
.try_decrypt_with_shared_secret(shared_secret.to_byte_array())
|
||||||
|
.unwrap();
|
||||||
|
let unknown_msg: UnknownMessage = serde_json::from_slice(&plaintext)?;
|
||||||
|
message.plaintext = Some(unknown_msg.message);
|
||||||
|
message.sender = Some(unknown_msg.sender);
|
||||||
|
message.recipient = Some(sp_wallet.get_client().get_receiving_address());
|
||||||
|
return Ok(message.clone())
|
||||||
|
} else {
|
||||||
|
// store it and wait for the message
|
||||||
|
let mut new_msg = NetworkMessage::default();
|
||||||
|
let (outpoint, output) = utxo_created.iter().next().unwrap();
|
||||||
|
new_msg.commited_in = Some(**outpoint);
|
||||||
|
new_msg.commitment = Some(commitment.to_lower_hex_string());
|
||||||
|
new_msg.recipient = Some(sp_wallet.get_client().get_receiving_address());
|
||||||
|
new_msg.shared_secret =
|
||||||
|
Some(shared_secret.to_byte_array().to_lower_hex_string());
|
||||||
|
new_msg.status = NetworkMessageStatus::TxWaitingCipher;
|
||||||
|
lock_messages()?.push(new_msg.clone());
|
||||||
|
return Ok(new_msg.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unreachable!("Transaction with no inputs");
|
||||||
|
} else {
|
||||||
|
// We are sender of a notification transaction
|
||||||
|
// We only need to return the message
|
||||||
|
if let Some(message) = lock_messages()?.iter()
|
||||||
|
.find(|m| {
|
||||||
|
m.commitment.as_ref() == Some(&commitment_str)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return Ok(message.clone());
|
||||||
|
} else {
|
||||||
|
return Err(anyhow::Error::msg("We spent a transaction for a commitment we don't know"));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
/// If the transaction has anything to do with us, we create/update the relevant `NetworkMessage`
|
||||||
pub fn check_transaction_for_silent_payments(
|
/// and return it to caller for persistent storage
|
||||||
|
fn process_transaction(
|
||||||
tx_hex: String,
|
tx_hex: String,
|
||||||
blockheight: u32,
|
blockheight: u32,
|
||||||
tweak_data_hex: String,
|
tweak_data_hex: String,
|
||||||
fee_rate: u32,
|
fee_rate: u32,
|
||||||
) -> ApiResult<String> {
|
) -> anyhow::Result<NetworkMessage> {
|
||||||
let tx = deserialize::<Transaction>(&Vec::from_hex(&tx_hex)?)?;
|
let tx = deserialize::<Transaction>(&Vec::from_hex(&tx_hex)?)?;
|
||||||
|
|
||||||
// check that we don't already have scanned the tx, and insert it if we don't
|
// check that we don't already have scanned the tx
|
||||||
if !lock_scanned_transactions()?.insert(tx.txid()) {
|
if let Some(_) = lock_messages()?.iter().find(|message| {
|
||||||
return Err(ApiError {
|
if let Some(outpoint) = message.commited_in {
|
||||||
message: "Transaction already scanned".to_owned(),
|
if outpoint.txid == tx.txid() {
|
||||||
});
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}) {
|
||||||
|
return Err(anyhow::Error::msg("Transaction already scanned"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let tweak_data = PublicKey::from_str(&tweak_data_hex)?;
|
let tweak_data = PublicKey::from_str(&tweak_data_hex)?;
|
||||||
|
|
||||||
let mut connected_user = lock_connected_user()?;
|
let mut connected_user = lock_connected_user()?;
|
||||||
if let Ok(recover) = connected_user.try_get_mut_recover() {
|
if let Ok(recover) = connected_user.try_get_mut_recover() {
|
||||||
if let Ok(txid) = check_transaction(&tx, recover, blockheight, tweak_data) {
|
let updated = recover.update_wallet_with_transaction(&tx, blockheight, tweak_data)?;
|
||||||
if let Err(e) = scan_for_confirmation_transaction(tx_hex) {
|
|
||||||
log::error!("{}", e);
|
if updated.len() > 0 {
|
||||||
handle_recover_transaction(tx, recover, tweak_data, fee_rate)?;
|
let updated_msg =
|
||||||
}
|
handle_recover_transaction(updated, &tx, recover, tweak_data, fee_rate)?;
|
||||||
return Ok(txid);
|
return Ok(updated_msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(main) = connected_user.try_get_mut_main() {
|
if let Ok(main) = connected_user.try_get_mut_main() {
|
||||||
if let Ok(txid) = check_transaction(&tx, main, blockheight, tweak_data) {
|
let updated = main.update_wallet_with_transaction(&tx, blockheight, tweak_data)?;
|
||||||
// TODO
|
unimplemented!();
|
||||||
return Ok(txid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(revoke) = connected_user.try_get_mut_revoke() {
|
if let Ok(revoke) = connected_user.try_get_mut_revoke() {
|
||||||
if let Ok(txid) = check_transaction(&tx, revoke, blockheight, tweak_data) {
|
let updated = revoke.update_wallet_with_transaction(&tx, blockheight, tweak_data)?;
|
||||||
// TODO
|
unimplemented!();
|
||||||
return Ok(txid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(ApiError {
|
Err(anyhow::Error::msg("No output found"))
|
||||||
message: "No output found".to_owned(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Tsify, Serialize, Deserialize)]
|
#[derive(Tsify, Serialize, Deserialize)]
|
||||||
@ -444,7 +550,7 @@ pub fn check_transaction_for_silent_payments(
|
|||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub struct parseNetworkMsgReturn {
|
pub struct parseNetworkMsgReturn {
|
||||||
topic: String,
|
topic: String,
|
||||||
message: String,
|
message: NetworkMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@ -458,81 +564,58 @@ pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<parseNetworkMs
|
|||||||
message: "Missing tweak_data".to_owned(),
|
message: "Missing tweak_data".to_owned(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let txid = check_transaction_for_silent_payments(
|
let network_msg = process_transaction(
|
||||||
tx_message.transaction,
|
tx_message.transaction,
|
||||||
0,
|
0,
|
||||||
tx_message.tweak_data.unwrap(),
|
tx_message.tweak_data.unwrap(),
|
||||||
fee_rate,
|
fee_rate,
|
||||||
)?;
|
)?;
|
||||||
|
debug!("{:?}", network_msg);
|
||||||
return Ok(parseNetworkMsgReturn {
|
return Ok(parseNetworkMsgReturn {
|
||||||
topic: AnkFlag::NewTx.as_str().to_owned(),
|
topic: ank_msg.flag.as_str().to_owned(),
|
||||||
message: txid,
|
message: network_msg,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AnkFlag::Faucet => unimplemented!(),
|
AnkFlag::Faucet => unimplemented!(),
|
||||||
AnkFlag::Error => {
|
AnkFlag::Error => {
|
||||||
|
let error_msg = NetworkMessage::new_error(ank_msg.content);
|
||||||
return Ok(parseNetworkMsgReturn {
|
return Ok(parseNetworkMsgReturn {
|
||||||
topic: AnkFlag::Error.as_str().to_owned(),
|
topic: AnkFlag::Error.as_str().to_owned(),
|
||||||
message: ank_msg.content.to_owned(),
|
message: error_msg,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
AnkFlag::Unknown => {
|
AnkFlag::Unknown => {
|
||||||
// try to decrypt the cipher with all available keys
|
// let's try to decrypt with keys we found in transactions but haven't used yet
|
||||||
for (txid, secret_vec) in lock_secrets()?.iter_mut() {
|
let mut messages = lock_messages()?;
|
||||||
// Actually we probably will ever have only one secret in the case we're receiver
|
let cipher = Vec::from_hex(&ank_msg.content)?;
|
||||||
for (shared_with, ank_secret) in secret_vec.iter_mut() {
|
let cipher_pos = messages.iter().position(|m| {
|
||||||
// if we already have shared_with, that means we already used that key for another message
|
if m.status != NetworkMessageStatus::TxWaitingCipher {
|
||||||
if !shared_with.is_empty() { continue }
|
return false;
|
||||||
let shared_secret = ank_secret.to_byte_array();
|
|
||||||
debug!("{} {}", shared_with, shared_secret.to_lower_hex_string());
|
|
||||||
let msg_decrypt = Aes256Decryption::new(
|
|
||||||
Purpose::Arbitrary,
|
|
||||||
Vec::from_hex(&ank_msg.content.trim_matches('\"'))?,
|
|
||||||
shared_secret,
|
|
||||||
)?;
|
|
||||||
match msg_decrypt.decrypt_with_key() {
|
|
||||||
Ok(plaintext) => {
|
|
||||||
let unknown_msg = serde_json::from_slice::<UnknownMessage>(&plaintext);
|
|
||||||
if unknown_msg.is_err() {
|
|
||||||
// The message we were sent is invalid, drop everything
|
|
||||||
// for now let's just fill the shared_with with garbage
|
|
||||||
*shared_with = "a".to_owned();
|
|
||||||
return Err(ApiError { message: "Invalid msg".to_owned() })
|
|
||||||
}
|
}
|
||||||
let sender: Result<SilentPaymentAddress, SpError> = unknown_msg.unwrap().sender.try_into();
|
m.try_decrypt_cipher(cipher.clone()).is_some()
|
||||||
if sender.is_err() {
|
});
|
||||||
// The sender is invalid address
|
if cipher_pos.is_some() {
|
||||||
*shared_with = "a".to_owned();
|
let mut message = messages.get_mut(cipher_pos.unwrap()).unwrap();
|
||||||
return Err(ApiError { message: "Invalid sp address".to_owned() })
|
let plain = message.try_decrypt_cipher(cipher).unwrap();
|
||||||
}
|
let unknown_msg: UnknownMessage = serde_json::from_slice(&plain)?;
|
||||||
|
message.plaintext = Some(unknown_msg.message);
|
||||||
// we update our list with the sender address
|
message.sender = Some(unknown_msg.sender);
|
||||||
*shared_with = sender.unwrap().into();
|
message.ciphertext = Some(ank_msg.content);
|
||||||
|
|
||||||
// We return the whole message
|
|
||||||
// ts is responsible for sending the confirmation message
|
|
||||||
return Ok(parseNetworkMsgReturn {
|
return Ok(parseNetworkMsgReturn {
|
||||||
topic: AnkFlag::Unknown.as_str().to_owned(),
|
topic: AnkFlag::Unknown.as_str().to_owned(),
|
||||||
message: String::from_utf8(plaintext)?,
|
message: message.clone(),
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
Err(e) => {
|
// let's keep it in case we receive the transaction later
|
||||||
debug!("{}", e);
|
let mut new_msg = NetworkMessage::default();
|
||||||
debug!(
|
new_msg.status = NetworkMessageStatus::CipherWaitingTx;
|
||||||
"Failed to decrypt message {} with key {}",
|
new_msg.ciphertext = Some(ank_msg.content);
|
||||||
ank_msg.content,
|
messages.push(new_msg);
|
||||||
shared_secret.to_lower_hex_string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// keep the message in cache, just in case?
|
|
||||||
// return an error
|
|
||||||
return Err(ApiError {
|
return Err(ApiError {
|
||||||
message: "No key found".to_owned(),
|
message: "Can't decrypt message".to_owned(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -597,16 +680,20 @@ pub fn is_tx_owned_by_user(pre_id: String, tx: String) -> ApiResult<bool> {
|
|||||||
pub struct createNotificationTransactionReturn {
|
pub struct createNotificationTransactionReturn {
|
||||||
pub txid: String,
|
pub txid: String,
|
||||||
pub transaction: String,
|
pub transaction: String,
|
||||||
pub address2secret: HashMap<String, AnkSharedSecret>,
|
pub new_network_msg: NetworkMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is what we call to confirm as a receiver
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn create_notification_transaction(
|
pub fn create_confirmation_transaction(
|
||||||
recipient: String,
|
message: NetworkMessage,
|
||||||
message: Option<String>,
|
|
||||||
fee_rate: u32,
|
fee_rate: u32,
|
||||||
) -> ApiResult<createNotificationTransactionReturn> {
|
) -> ApiResult<createNotificationTransactionReturn> {
|
||||||
let sp_address: SilentPaymentAddress = recipient.try_into()?;
|
if message.sender.is_none() || message.confirmed_by.is_none() {
|
||||||
|
return Err(ApiError { message: "Invalid network message".to_owned() });
|
||||||
|
}
|
||||||
|
|
||||||
|
let sp_address: SilentPaymentAddress = message.sender.as_ref().unwrap().as_str().try_into()?;
|
||||||
|
|
||||||
let connected_user = lock_connected_user()?;
|
let connected_user = lock_connected_user()?;
|
||||||
|
|
||||||
@ -617,28 +704,97 @@ pub fn create_notification_transaction(
|
|||||||
sp_wallet = connected_user.try_get_main()?;
|
sp_wallet = connected_user.try_get_main()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (transaction, shared_secret) = create_transaction_for_address_with_shared_secret(
|
let recipient = Recipient {
|
||||||
sp_address,
|
address: sp_address.into(),
|
||||||
|
amount: Amount::from_sat(1200),
|
||||||
|
nb_outputs: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let signed_psbt = create_transaction_spend_outpoint(
|
||||||
|
&message.confirmed_by.unwrap(),
|
||||||
sp_wallet,
|
sp_wallet,
|
||||||
message,
|
recipient,
|
||||||
|
Amount::from_sat(fee_rate.into())
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let final_tx = signed_psbt.extract_tx()?;
|
||||||
|
|
||||||
|
Ok(createNotificationTransactionReturn {
|
||||||
|
txid: final_tx.txid().to_string(),
|
||||||
|
transaction: serialize(&final_tx).to_lower_hex_string(),
|
||||||
|
new_network_msg: message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn create_notification_transaction(
|
||||||
|
address: String,
|
||||||
|
commitment: Option<String>,
|
||||||
|
fee_rate: u32,
|
||||||
|
) -> ApiResult<createNotificationTransactionReturn> {
|
||||||
|
let sp_address: SilentPaymentAddress = address.as_str().try_into()?;
|
||||||
|
|
||||||
|
let connected_user = lock_connected_user()?;
|
||||||
|
|
||||||
|
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 recipient = Recipient {
|
||||||
|
address: sp_address.into(),
|
||||||
|
amount: Amount::from_sat(1200),
|
||||||
|
nb_outputs: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let signed_psbt = create_transaction_for_address_with_shared_secret(
|
||||||
|
recipient,
|
||||||
|
sp_wallet,
|
||||||
|
commitment.as_deref(),
|
||||||
Amount::from_sat(fee_rate.into()),
|
Amount::from_sat(fee_rate.into()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let psbt = Psbt::from_str(&signed_psbt)?;
|
||||||
|
|
||||||
|
let partial_secret = sp_wallet
|
||||||
|
.get_client()
|
||||||
|
.get_partial_secret_from_psbt(&psbt)?;
|
||||||
|
|
||||||
|
let shared_point = shared_secret_point(
|
||||||
|
&sp_wallet
|
||||||
|
.get_client()
|
||||||
|
.get_scan_key()
|
||||||
|
.public_key(&Secp256k1::signing_only()),
|
||||||
|
&partial_secret,
|
||||||
|
);
|
||||||
|
|
||||||
|
let shared_secret = AnkSharedSecret::new(shared_point);
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"Created transaction with secret {}",
|
"Created transaction with secret {}",
|
||||||
shared_secret.to_byte_array().to_lower_hex_string()
|
shared_secret.to_byte_array().to_lower_hex_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut address2secret: Vec<(String, AnkSharedSecret)> = vec![];
|
|
||||||
address2secret.push((sp_address.into(), shared_secret));
|
|
||||||
|
|
||||||
// update our cache
|
// update our cache
|
||||||
lock_secrets()?.insert(transaction.txid(), address2secret.clone());
|
let sp_address2vouts = map_outputs_to_sp_address(&signed_psbt)?;
|
||||||
|
let recipients_vouts = sp_address2vouts.get::<String>(&address).expect("recipients didn't change").as_slice();
|
||||||
|
// for now let's just take the smallest vout that belongs to the recipient
|
||||||
|
let final_tx = psbt.extract_tx()?;
|
||||||
|
let mut new_msg = NetworkMessage::default();
|
||||||
|
new_msg.commitment = commitment;
|
||||||
|
new_msg.commited_in = Some(OutPoint { txid: final_tx.txid(), vout: recipients_vouts[0] as u32 });
|
||||||
|
new_msg.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string());
|
||||||
|
new_msg.recipient = Some(address);
|
||||||
|
new_msg.sender = Some(sp_wallet.get_client().get_receiving_address());
|
||||||
|
// plaintext and ciphertext to be added later when sending the encrypted message
|
||||||
|
lock_messages()?.push(new_msg.clone());
|
||||||
|
|
||||||
Ok(createNotificationTransactionReturn {
|
Ok(createNotificationTransactionReturn {
|
||||||
txid: transaction.txid().to_string(),
|
txid: final_tx.txid().to_string(),
|
||||||
transaction: serialize(&transaction).to_lower_hex_string(),
|
transaction: serialize(&final_tx).to_lower_hex_string(),
|
||||||
address2secret: address2secret.into_iter().collect(),
|
new_network_msg: new_msg,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -710,6 +866,18 @@ pub fn try_decrypt_with_key(cipher: String, key: String) -> ApiResult<String> {
|
|||||||
Ok(plain)
|
Ok(plain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn create_faucet_msg() -> ApiResult<FaucetMessage> {
|
||||||
|
let user = lock_connected_user()?;
|
||||||
|
let sp_address = user.try_get_recover()?.get_client().get_receiving_address();
|
||||||
|
let faucet_msg = FaucetMessage::new(sp_address);
|
||||||
|
// we write the commitment in a networkmessage so that we can keep track
|
||||||
|
let mut network_msg = NetworkMessage::default();
|
||||||
|
network_msg.commitment = Some(faucet_msg.commitment.clone());
|
||||||
|
lock_messages()?.push(network_msg);
|
||||||
|
Ok(faucet_msg)
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn create_commitment(payload_to_hash: String) -> String {
|
pub fn create_commitment(payload_to_hash: String) -> String {
|
||||||
let mut engine = sha256::HashEngine::default();
|
let mut engine = sha256::HashEngine::default();
|
||||||
@ -717,3 +885,110 @@ pub fn create_commitment(payload_to_hash: String) -> String {
|
|||||||
let hash = sha256::Hash::from_engine(engine);
|
let hash = sha256::Hash::from_engine(engine);
|
||||||
hash.to_byte_array().to_lower_hex_string()
|
hash.to_byte_array().to_lower_hex_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Tsify, Clone)]
|
||||||
|
pub enum NetworkMessageStatus {
|
||||||
|
NoStatus, // Default
|
||||||
|
CipherWaitingTx,
|
||||||
|
TxWaitingCipher,
|
||||||
|
SentWaitingConfirmation,
|
||||||
|
MustSpentConfirmation,
|
||||||
|
Complete,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NetworkMessageStatus {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::NoStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique struct for both 4nk messages and notification/key exchange, both rust and ts
|
||||||
|
/// 1. Faucet: commited_in with nothing else, status is NoStatus
|
||||||
|
/// 2. notification:
|
||||||
|
/// 1. sender: ciphertext, plaintext, commited_in, sender, recipient, shared_secret, key
|
||||||
|
/// 2. receiver (without tx): ciphertext
|
||||||
|
/// 3. receiver (tx without msg): commited_in, commitment, recipient, shared_secret
|
||||||
|
/// 4. receiver (receive tx after msg): plaintext, key, sender, commited_in, commitment, recipient, shared_secret
|
||||||
|
/// 5. receiver (msg after tx): ciphertext, key, plaintext, sender
|
||||||
|
/// 3. confirmation:
|
||||||
|
/// 1. receiver (spend the smallest vout that pays him in the first tx): confirmed_by
|
||||||
|
/// 2. sender (detect a transaction that pays him and spend commited_by): confirmed_by
|
||||||
|
/// 3. sender toggle status to complete when it spent confirmed_by, receiver when it detects the confirmed_by is spent
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize, Tsify, Clone)]
|
||||||
|
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub struct NetworkMessage {
|
||||||
|
pub id: u32,
|
||||||
|
pub status: NetworkMessageStatus,
|
||||||
|
pub ciphertext: Option<String>, // When we receive message we can't decrypt we only have this and commited_in_tx
|
||||||
|
pub plaintext: Option<String>, // Never None when message sent
|
||||||
|
pub commited_in: Option<OutPoint>,
|
||||||
|
pub commitment: Option<String>, // content of the op_return
|
||||||
|
pub sender: Option<String>, // Never None when message sent
|
||||||
|
pub recipient: Option<String>, // Never None when message sent
|
||||||
|
pub shared_secret: Option<String>, // Never None when message sent
|
||||||
|
pub key: Option<String>, // Never None when message sent
|
||||||
|
pub confirmed_by: Option<OutPoint>, // If this None, Sender keeps sending
|
||||||
|
pub timestamp: u64,
|
||||||
|
pub error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetworkMessage {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut new = NetworkMessage::default();
|
||||||
|
let mut buf = [0u8;4];
|
||||||
|
thread_rng().fill_bytes(&mut buf);
|
||||||
|
new.id = u32::from_be_bytes(buf);
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_error(error: String) -> Self {
|
||||||
|
let mut new = NetworkMessage::default();
|
||||||
|
new.error = Some(error);
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_decrypt_cipher(&self, cipher: Vec<u8>) -> Option<Vec<u8>> {
|
||||||
|
if self.ciphertext.is_some() || self.shared_secret.is_none() {
|
||||||
|
log::error!(
|
||||||
|
"Can't try decrypt this message, there's already a ciphertext or no shared secret"
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut shared_secret = [0u8; 32];
|
||||||
|
shared_secret
|
||||||
|
.copy_from_slice(&Vec::from_hex(self.shared_secret.as_ref().unwrap()).unwrap());
|
||||||
|
let aes_decrypt = Aes256Decryption::new(Purpose::Arbitrary, cipher, shared_secret);
|
||||||
|
|
||||||
|
if aes_decrypt.is_err() {
|
||||||
|
log::error!("Failed to create decrypt object");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
aes_decrypt.unwrap().decrypt_with_key().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_decrypt_with_shared_secret(&self, shared_secret: [u8; 32]) -> Option<Vec<u8>> {
|
||||||
|
if self.ciphertext.is_none() || self.shared_secret.is_some() {
|
||||||
|
log::error!(
|
||||||
|
"Can't try decrypt this message, ciphertext is none or shared_secret already found"
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let cipher_bin = Vec::from_hex(self.ciphertext.as_ref().unwrap());
|
||||||
|
if cipher_bin.is_err() {
|
||||||
|
let error = cipher_bin.unwrap_err();
|
||||||
|
log::error!("Invalid hex in ciphertext: {}", error.to_string());
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let aes_decrypt =
|
||||||
|
Aes256Decryption::new(Purpose::Arbitrary, cipher_bin.unwrap(), shared_secret);
|
||||||
|
|
||||||
|
if aes_decrypt.is_err() {
|
||||||
|
log::error!("Failed to create decrypt object");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
aes_decrypt.unwrap().decrypt_with_key().ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#![allow(warnings)]
|
#![allow(warnings)]
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
|
use api::NetworkMessage;
|
||||||
use sdk_common::crypto::AnkSharedSecret;
|
use sdk_common::crypto::AnkSharedSecret;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sp_client::bitcoin::{OutPoint, Txid};
|
use sp_client::bitcoin::{OutPoint, Txid};
|
||||||
@ -16,37 +17,11 @@ mod peers;
|
|||||||
mod process;
|
mod process;
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
/// We map txid with one or n secrets
|
pub static NETWORKMESSAGES: OnceLock<Mutex<Vec<NetworkMessage>>> = OnceLock::new();
|
||||||
/// Each secret match one sp address
|
|
||||||
/// When we first detect a transaction, we can't tell who's the sender, so we like sp address empty
|
|
||||||
/// When we receive the corresponding message, we get a sp address declaration, we complete here
|
|
||||||
/// Then when we send the confirmation transaction and got the response we can flip the secret to trusted
|
|
||||||
pub type Txid2Secrets = HashMap<Txid, Vec<(String, AnkSharedSecret)>>;
|
|
||||||
|
|
||||||
pub static SECRETCACHE: OnceLock<Mutex<Txid2Secrets>> = OnceLock::new();
|
pub fn lock_messages() -> Result<MutexGuard<'static, Vec<NetworkMessage>>, Error> {
|
||||||
|
NETWORKMESSAGES
|
||||||
pub fn lock_secrets() -> Result<MutexGuard<'static, Txid2Secrets>, Error> {
|
.get_or_init(|| Mutex::new(vec![]))
|
||||||
SECRETCACHE
|
|
||||||
.get_or_init(|| Mutex::new(Txid2Secrets::new()))
|
|
||||||
.lock_anyhow()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// this is to keep track of transaction we already analysed without finding notification
|
|
||||||
/// This is not critical and there's no need to keep that in persistent storage, as most transactions would only show up twice
|
|
||||||
/// Worst case is we will scan again transactions when they got into a block
|
|
||||||
pub static TRANSACTIONCACHE: OnceLock<Mutex<HashSet<Txid>>> = OnceLock::new();
|
|
||||||
|
|
||||||
pub fn lock_scanned_transactions() -> Result<MutexGuard<'static, HashSet<Txid>>, Error> {
|
|
||||||
TRANSACTIONCACHE
|
|
||||||
.get_or_init(|| Mutex::new(HashSet::new()))
|
|
||||||
.lock_anyhow()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub static WATCHEDUTXO: OnceLock<Mutex<HashMap<OutPoint, Txid>>> = OnceLock::new();
|
|
||||||
|
|
||||||
pub fn lock_watched() -> Result<MutexGuard<'static, HashMap<OutPoint, Txid>>, Error> {
|
|
||||||
WATCHEDUTXO
|
|
||||||
.get_or_init(|| Mutex::new(HashMap::new()))
|
|
||||||
.lock_anyhow()
|
.lock_anyhow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"build_wasm": "wasm-pack build --out-dir ../../dist/pkg ./crates/sp_client --target bundler",
|
"build_wasm": "wasm-pack build --out-dir ../../dist/pkg ./crates/sp_client --target bundler --dev",
|
||||||
"start": "webpack serve",
|
"start": "webpack serve",
|
||||||
"build": "webpack"
|
"build": "webpack"
|
||||||
},
|
},
|
||||||
|
@ -24,6 +24,11 @@ class Database {
|
|||||||
'unique': true
|
'unique': true
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
},
|
||||||
|
AnkMessages: {
|
||||||
|
name: "messages",
|
||||||
|
options: {'keyPath': 'id'},
|
||||||
|
indices: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { createUserReturn, User, Process, createNotificationTransactionReturn, parse_network_msg, outputs_list, parseNetworkMsgReturn, FaucetMessage, AnkFlag, NewTxMessage, encryptWithNewKeyResult, AnkSharedSecret } from '../dist/pkg/sdk_client';
|
import { createUserReturn, User, Process, createNotificationTransactionReturn, parse_network_msg, outputs_list, parseNetworkMsgReturn, FaucetMessage, AnkFlag, NewTxMessage, encryptWithNewKeyResult, AnkSharedSecret, NetworkMessage } from '../dist/pkg/sdk_client';
|
||||||
import IndexedDB from './database'
|
import IndexedDB from './database'
|
||||||
import { WebSocketClient } from './websockets';
|
import { WebSocketClient } from './websockets';
|
||||||
|
|
||||||
@ -109,32 +109,41 @@ class Services {
|
|||||||
|
|
||||||
let notificationInfo = await services.notify_address_for_message(recipientSpAddress, msg_payload);
|
let notificationInfo = await services.notify_address_for_message(recipientSpAddress, msg_payload);
|
||||||
if (notificationInfo) {
|
if (notificationInfo) {
|
||||||
|
let networkMsg = notificationInfo.new_network_msg;
|
||||||
|
const msgId = notificationInfo.new_network_msg.id;
|
||||||
|
let shared_secret = '';
|
||||||
|
if (networkMsg.shared_secret) {
|
||||||
|
shared_secret = networkMsg.shared_secret;
|
||||||
|
} else {
|
||||||
|
throw 'no shared_secret';
|
||||||
|
}
|
||||||
let ciphers: string[] = [];
|
let ciphers: string[] = [];
|
||||||
console.info('Successfully sent notification transaction');
|
console.info('Successfully sent notification transaction');
|
||||||
// Save the secret to db
|
// Save the secret to db
|
||||||
|
const indexedDb = await IndexedDB.getInstance();
|
||||||
|
const db = await indexedDb.getDb();
|
||||||
|
|
||||||
// encrypt the message(s)
|
// encrypt the message(s)
|
||||||
for (const [address, ankSharedSecret] of Object.entries(notificationInfo.address2secret)) {
|
|
||||||
try {
|
try {
|
||||||
let cipher = await services.encryptData(msg_payload, ankSharedSecret.secret);
|
const cipher = await services.encryptData(msg_payload, shared_secret);
|
||||||
|
let updated_msg = notificationInfo.new_network_msg;
|
||||||
|
updated_msg.plaintext = msg_payload;
|
||||||
|
updated_msg.ciphertext = cipher;
|
||||||
|
await indexedDb.writeObject(db, indexedDb.getStoreList().AnkMessages, updated_msg, null);
|
||||||
ciphers.push(cipher);
|
ciphers.push(cipher);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
const connection = await services.pickWebsocketConnectionRandom();
|
const connection = await services.pickWebsocketConnectionRandom();
|
||||||
const flag: AnkFlag = "Unknown";
|
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 peers list
|
||||||
// add processes list
|
// add processes list
|
||||||
// send message (transaction in envelope)
|
// send message (transaction in envelope)
|
||||||
|
for (const payload of ciphers) {
|
||||||
connection?.sendMessage(flag, payload);
|
connection?.sendMessage(flag, payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async createId(event: Event): Promise<void> {
|
public async createId(event: Event): Promise<void> {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -397,19 +406,6 @@ class Services {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async checkTransaction(tx: string, tweak_data: string, blkheight: number): Promise<string | null> {
|
|
||||||
const services = await Services.getInstance();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const txid = services.sdkClient.check_transaction_for_silent_payments(tx, blkheight, tweak_data);
|
|
||||||
await services.updateOwnedOutputsForUser();
|
|
||||||
return txid;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getAllProcessForUser(pre_id: string): Promise<Process[]> {
|
public async getAllProcessForUser(pre_id: string): Promise<Process[]> {
|
||||||
const services = await Services.getInstance();
|
const services = await Services.getInstance();
|
||||||
let user: User;
|
let user: User;
|
||||||
@ -447,6 +443,17 @@ class Services {
|
|||||||
return process;
|
return process;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async updateMessages(message: NetworkMessage): Promise<void> {
|
||||||
|
const indexedDb = await IndexedDB.getInstance();
|
||||||
|
const db = await indexedDb.getDb();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await indexedDb.setObject(db, indexedDb.getStoreList().AnkMessages, message, null);
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async updateProcesses(): Promise<void> {
|
public async updateProcesses(): Promise<void> {
|
||||||
const services = await Services.getInstance();
|
const services = await Services.getInstance();
|
||||||
const processList: Process[] = services.sdkClient.get_processes();
|
const processList: Process[] = services.sdkClient.get_processes();
|
||||||
@ -758,10 +765,8 @@ class Services {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const flag: AnkFlag = "Faucet";
|
const flag: AnkFlag = 'Faucet';
|
||||||
const faucetMsg: FaucetMessage = {
|
const faucetMsg = services.sdkClient.create_faucet_msg();
|
||||||
'sp_address': spaddress
|
|
||||||
}
|
|
||||||
connection.sendMessage(flag, JSON.stringify(faucetMsg));
|
connection.sendMessage(flag, JSON.stringify(faucetMsg));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to obtain tokens with relay ", connection.getUrl());
|
console.error("Failed to obtain tokens with relay ", connection.getUrl());
|
||||||
@ -822,7 +827,7 @@ class Services {
|
|||||||
let notificationInfo: createNotificationTransactionReturn;
|
let notificationInfo: createNotificationTransactionReturn;
|
||||||
try {
|
try {
|
||||||
const feeRate = 1;
|
const feeRate = 1;
|
||||||
notificationInfo = services.sdkClient.create_notification_transaction(sp_address, undefined, feeRate);
|
notificationInfo = services.sdkClient.create_notification_transaction(sp_address, null, feeRate);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Failed to create confirmation transaction: ${error}`);
|
throw new Error(`Failed to create confirmation transaction: ${error}`);
|
||||||
}
|
}
|
||||||
@ -835,23 +840,11 @@ class Services {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async notify_address_for_message(sp_address: string, message: string): Promise<createNotificationTransactionReturn | null> {
|
public async notify_address_for_message(sp_address: string, message: string): Promise<createNotificationTransactionReturn> {
|
||||||
const services = await Services.getInstance();
|
const services = await Services.getInstance();
|
||||||
const connection = await services.pickWebsocketConnectionRandom();
|
const connection = await services.pickWebsocketConnectionRandom();
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
return null;
|
throw 'No available connection';
|
||||||
}
|
|
||||||
let user: User;
|
|
||||||
try {
|
|
||||||
let possibleUser = await services.getUserInfo();
|
|
||||||
if (!possibleUser) {
|
|
||||||
console.error("No user loaded, please first create a new user or login");
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
user = possibleUser;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -866,8 +859,7 @@ class Services {
|
|||||||
connection.sendMessage(flag, JSON.stringify(newTxMsg));
|
connection.sendMessage(flag, JSON.stringify(newTxMsg));
|
||||||
return notificationInfo;
|
return notificationInfo;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to create notification transaction:", error);
|
throw 'Failed to create notification transaction:", error';
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,11 +34,14 @@ class WebSocketClient {
|
|||||||
window.alert(`New tx\n${res.message}`);
|
window.alert(`New tx\n${res.message}`);
|
||||||
await services.updateOwnedOutputsForUser();
|
await services.updateOwnedOutputsForUser();
|
||||||
} else if (res.topic === 'unknown') {
|
} else if (res.topic === 'unknown') {
|
||||||
let parsed = JSON.parse(res.message);
|
let message = res.message['plaintext'];
|
||||||
let message = parsed['message'];
|
let sender = res.message['sender'];
|
||||||
let sender = parsed['sender'];
|
if (!message || !sender) {
|
||||||
|
throw 'Message missing plaintext and/or sender';
|
||||||
|
}
|
||||||
window.alert(`new message: ${message}\nAsking sender ${sender} to confirm identity...`);
|
window.alert(`new message: ${message}\nAsking sender ${sender} to confirm identity...`);
|
||||||
console.debug(`sending confirm message to ${sender}`);
|
console.debug(`sending confirm message to ${sender}`);
|
||||||
|
await services.updateMessages(res.message);
|
||||||
await services.confirm_sender_address(sender);
|
await services.confirm_sender_address(sender);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user