Message processing heavy refactoring
This commit is contained in:
parent
c3e435a228
commit
bc6f95a98f
@ -25,7 +25,8 @@ 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, Network, OutPoint, Psbt, Transaction, Txid};
|
use sp_client::bitcoin::{Amount, Network, OutPoint, Psbt, Transaction, Txid};
|
||||||
use sp_client::silentpayments::Error as SpError;
|
use sp_client::silentpayments::utils as sp_utils;
|
||||||
|
use sp_client::silentpayments::{Error as SpError, Network as SpNetwork};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sp_client::silentpayments::sending::SilentPaymentAddress;
|
use sp_client::silentpayments::sending::SilentPaymentAddress;
|
||||||
@ -34,11 +35,12 @@ use wasm_bindgen::convert::FromWasmAbi;
|
|||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use sdk_common::network::{
|
use sdk_common::network::{
|
||||||
self, AnkFlag, AnkNetworkMsg, CachedMessage, CachedMessageStatus, FaucetMessage, NewTxMessage, UnknownMessage
|
self, AnkFlag, AnkNetworkMsg, CachedMessage, CachedMessageStatus, FaucetMessage, NewTxMessage,
|
||||||
|
UnknownMessage,
|
||||||
};
|
};
|
||||||
use sdk_common::silentpayments::{
|
use sdk_common::silentpayments::{
|
||||||
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
|
create_transaction_spend_outpoint, map_outputs_to_sp_address,
|
||||||
};
|
};
|
||||||
|
|
||||||
use sp_client::spclient::{
|
use sp_client::spclient::{
|
||||||
@ -402,23 +404,29 @@ fn handle_recover_transaction(
|
|||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
})
|
}) {
|
||||||
{
|
|
||||||
let message = messages.get_mut(pos).unwrap();
|
let message = messages.get_mut(pos).unwrap();
|
||||||
match message.status {
|
match message.status {
|
||||||
CachedMessageStatus::FaucetWaiting => {
|
CachedMessageStatus::FaucetWaiting => {
|
||||||
message.status = CachedMessageStatus::FaucetComplete;
|
message.status = CachedMessageStatus::FaucetComplete;
|
||||||
message.commited_in = utxo_created.into_iter().next().map(|(outpoint, _)| *outpoint);
|
message.commited_in = utxo_created
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.map(|(outpoint, _)| *outpoint);
|
||||||
return Ok(message.clone());
|
return Ok(message.clone());
|
||||||
},
|
}
|
||||||
|
// Actually this is unreachable
|
||||||
CachedMessageStatus::FaucetComplete => return Ok(message.clone()),
|
CachedMessageStatus::FaucetComplete => return Ok(message.clone()),
|
||||||
_ => ()
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we inspect inputs looking for links with previous tx
|
// we inspect inputs looking for links with previous tx
|
||||||
for input in tx.input.iter() {
|
for input in tx.input.iter() {
|
||||||
if let Some(pos) = messages.iter().position(|m| {
|
if let Some(pos) = messages
|
||||||
|
.iter()
|
||||||
|
.position(|m| {
|
||||||
|
debug!("{:?}", Some(input.previous_output));
|
||||||
m.confirmed_by == Some(input.previous_output)
|
m.confirmed_by == Some(input.previous_output)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
@ -426,9 +434,9 @@ fn handle_recover_transaction(
|
|||||||
// If we are receiver, that's pretty much it, just set status to complete
|
// If we are receiver, that's pretty much it, just set status to complete
|
||||||
message.status = CachedMessageStatus::Complete;
|
message.status = CachedMessageStatus::Complete;
|
||||||
return Ok(message.clone());
|
return Ok(message.clone());
|
||||||
} else if let Some(pos) = messages.iter().position(|m| {
|
} else if let Some(pos) = messages
|
||||||
m.commited_in == Some(input.previous_output)
|
.iter()
|
||||||
})
|
.position(|m| m.commited_in == Some(input.previous_output))
|
||||||
{
|
{
|
||||||
// sender needs to spent it back again to receiver
|
// sender needs to spent it back again to receiver
|
||||||
let (outpoint, output) = utxo_created.into_iter().next().unwrap();
|
let (outpoint, output) = utxo_created.into_iter().next().unwrap();
|
||||||
@ -444,9 +452,16 @@ fn handle_recover_transaction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if we've found nothing we are being notified
|
// if we've found nothing we are being notified
|
||||||
let shared_point =
|
let shared_point = sp_utils::receiving::calculate_shared_point(
|
||||||
shared_secret_point(&tweak_data, &sp_wallet.get_client().get_scan_key());
|
&tweak_data,
|
||||||
let shared_secret = AnkSharedSecret::new(shared_point);
|
&sp_wallet.get_client().get_scan_key(),
|
||||||
|
);
|
||||||
|
let shared_secret = AnkSharedSecret::new(PublicKey::from_slice(&shared_point)?);
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Shared secret: {}",
|
||||||
|
shared_secret.to_byte_array().to_lower_hex_string()
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(cipher_pos) = messages.iter().position(|m| {
|
if let Some(cipher_pos) = messages.iter().position(|m| {
|
||||||
if m.status != CachedMessageStatus::CipherWaitingTx {
|
if m.status != CachedMessageStatus::CipherWaitingTx {
|
||||||
@ -454,15 +469,13 @@ fn handle_recover_transaction(
|
|||||||
}
|
}
|
||||||
m.try_decrypt_with_shared_secret(shared_secret.to_byte_array())
|
m.try_decrypt_with_shared_secret(shared_secret.to_byte_array())
|
||||||
.is_ok()
|
.is_ok()
|
||||||
})
|
}) {
|
||||||
{
|
|
||||||
let message = messages.get_mut(cipher_pos).unwrap();
|
let message = messages.get_mut(cipher_pos).unwrap();
|
||||||
|
|
||||||
let (outpoint, output) = utxo_created.into_iter().next().unwrap();
|
let (outpoint, output) = utxo_created.into_iter().next().unwrap();
|
||||||
|
|
||||||
message.commited_in = Some(outpoint.clone());
|
message.commited_in = Some(outpoint.clone());
|
||||||
message.shared_secret =
|
message.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string());
|
||||||
Some(shared_secret.to_byte_array().to_lower_hex_string());
|
|
||||||
message.commitment = Some(commitment_str);
|
message.commitment = Some(commitment_str);
|
||||||
|
|
||||||
let plaintext = message
|
let plaintext = message
|
||||||
@ -474,16 +487,18 @@ fn handle_recover_transaction(
|
|||||||
message.recipient = Some(sp_wallet.get_client().get_receiving_address());
|
message.recipient = Some(sp_wallet.get_client().get_receiving_address());
|
||||||
message.status = CachedMessageStatus::ReceivedMustConfirm;
|
message.status = CachedMessageStatus::ReceivedMustConfirm;
|
||||||
|
|
||||||
return Ok(message.clone())
|
return Ok(message.clone());
|
||||||
} else {
|
} else {
|
||||||
// store it and wait for the message
|
// store it and wait for the message
|
||||||
let mut new_msg = CachedMessage::new();
|
let mut new_msg = CachedMessage::new();
|
||||||
let (outpoint, output) = utxo_created.into_iter().next().expect("utxo_created shouldn't be empty");
|
let (outpoint, output) = utxo_created
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.expect("utxo_created shouldn't be empty");
|
||||||
new_msg.commited_in = Some(outpoint.clone());
|
new_msg.commited_in = Some(outpoint.clone());
|
||||||
new_msg.commitment = Some(commitment.to_lower_hex_string());
|
new_msg.commitment = Some(commitment.to_lower_hex_string());
|
||||||
new_msg.recipient = Some(sp_wallet.get_client().get_receiving_address());
|
new_msg.recipient = Some(sp_wallet.get_client().get_receiving_address());
|
||||||
new_msg.shared_secret =
|
new_msg.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string());
|
||||||
Some(shared_secret.to_byte_array().to_lower_hex_string());
|
|
||||||
new_msg.status = CachedMessageStatus::TxWaitingCipher;
|
new_msg.status = CachedMessageStatus::TxWaitingCipher;
|
||||||
messages.push(new_msg.clone());
|
messages.push(new_msg.clone());
|
||||||
return Ok(new_msg.clone());
|
return Ok(new_msg.clone());
|
||||||
@ -491,14 +506,15 @@ fn handle_recover_transaction(
|
|||||||
} else {
|
} else {
|
||||||
// We are sender of a transaction
|
// We are sender of a transaction
|
||||||
// We only need to return the message
|
// We only need to return the message
|
||||||
if let Some(message) = messages.iter()
|
if let Some(message) = messages
|
||||||
.find(|m| {
|
.iter()
|
||||||
m.commitment.as_ref() == Some(&commitment_str)
|
.find(|m| m.commitment.as_ref() == Some(&commitment_str))
|
||||||
})
|
|
||||||
{
|
{
|
||||||
return Ok(message.clone());
|
return Ok(message.clone());
|
||||||
} else {
|
} else {
|
||||||
return Err(anyhow::Error::msg("We spent a transaction for a commitment we don't know"));
|
return Err(anyhow::Error::msg(
|
||||||
|
"We spent a transaction for a commitment we don't know",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -577,7 +593,7 @@ pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<CachedMessage>
|
|||||||
AnkFlag::Faucet => unimplemented!(),
|
AnkFlag::Faucet => unimplemented!(),
|
||||||
AnkFlag::Error => {
|
AnkFlag::Error => {
|
||||||
let error_msg = CachedMessage::new_error(ank_msg.content);
|
let error_msg = CachedMessage::new_error(ank_msg.content);
|
||||||
return Ok(error_msg)
|
return Ok(error_msg);
|
||||||
}
|
}
|
||||||
AnkFlag::Unknown => {
|
AnkFlag::Unknown => {
|
||||||
// let's try to decrypt with keys we found in transactions but haven't used yet
|
// let's try to decrypt with keys we found in transactions but haven't used yet
|
||||||
@ -597,16 +613,15 @@ pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<CachedMessage>
|
|||||||
message.plaintext = Some(unknown_msg.message);
|
message.plaintext = Some(unknown_msg.message);
|
||||||
message.sender = Some(unknown_msg.sender);
|
message.sender = Some(unknown_msg.sender);
|
||||||
message.ciphertext = Some(ank_msg.content);
|
message.ciphertext = Some(ank_msg.content);
|
||||||
|
message.status = CachedMessageStatus::ReceivedMustConfirm;
|
||||||
return Ok(message.clone());
|
return Ok(message.clone());
|
||||||
} else {
|
} else {
|
||||||
// let's keep it in case we receive the transaction later
|
// let's keep it in case we receive the transaction later
|
||||||
let mut new_msg = CachedMessage::new();
|
let mut new_msg = CachedMessage::new();
|
||||||
new_msg.status = CachedMessageStatus::CipherWaitingTx;
|
new_msg.status = CachedMessageStatus::CipherWaitingTx;
|
||||||
new_msg.ciphertext = Some(ank_msg.content);
|
new_msg.ciphertext = Some(ank_msg.content);
|
||||||
messages.push(new_msg);
|
messages.push(new_msg.clone());
|
||||||
return Err(ApiError {
|
return Ok(new_msg);
|
||||||
message: "Can't decrypt message".to_owned(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
@ -679,19 +694,30 @@ pub struct createTransactionReturn {
|
|||||||
/// This is what we call to answer a confirmation as a sender
|
/// This is what we call to answer a confirmation as a sender
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn answer_confirmation_transaction(
|
pub fn answer_confirmation_transaction(
|
||||||
message: CachedMessage,
|
message_id: u32,
|
||||||
fee_rate: u32,
|
fee_rate: u32,
|
||||||
) -> ApiResult<createTransactionReturn> {
|
) -> ApiResult<createTransactionReturn> {
|
||||||
if message.recipient.is_none() || message.confirmed_by.is_none() {
|
let mut messages = lock_messages()?;
|
||||||
return Err(ApiError { message: "Invalid network message".to_owned() });
|
let message: &mut CachedMessage;
|
||||||
|
if let Some(m) = messages.iter_mut().find(|m| m.id == message_id) {
|
||||||
|
if m.sender.is_none() || m.commited_in.is_none() {
|
||||||
|
return Err(ApiError {
|
||||||
|
message: "Invalid network message".to_owned(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let sp_address: SilentPaymentAddress = message.recipient.as_ref().unwrap().as_str().try_into()?;
|
message = m;
|
||||||
|
} else {
|
||||||
|
return Err(ApiError { message: format!("Can't find message for id {}", message_id) });
|
||||||
|
}
|
||||||
|
|
||||||
|
let sp_address: SilentPaymentAddress =
|
||||||
|
message.recipient.as_ref().unwrap().as_str().try_into()?;
|
||||||
|
|
||||||
let connected_user = lock_connected_user()?;
|
let connected_user = lock_connected_user()?;
|
||||||
|
|
||||||
let sp_wallet: &SpWallet;
|
let sp_wallet: &SpWallet;
|
||||||
if sp_address.is_testnet() {
|
if sp_address.get_network() != SpNetwork::Mainnet {
|
||||||
sp_wallet = connected_user.try_get_recover()?;
|
sp_wallet = connected_user.try_get_recover()?;
|
||||||
} else {
|
} else {
|
||||||
sp_wallet = connected_user.try_get_main()?;
|
sp_wallet = connected_user.try_get_main()?;
|
||||||
@ -703,38 +729,53 @@ pub fn answer_confirmation_transaction(
|
|||||||
nb_outputs: 1,
|
nb_outputs: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let confirmed_by = message.confirmed_by.clone().unwrap();
|
||||||
|
let commited_in = message.commited_in.clone().unwrap();
|
||||||
|
|
||||||
let signed_psbt = create_transaction_spend_outpoint(
|
let signed_psbt = create_transaction_spend_outpoint(
|
||||||
&message.confirmed_by.unwrap(),
|
&confirmed_by,
|
||||||
sp_wallet,
|
sp_wallet,
|
||||||
recipient,
|
recipient,
|
||||||
Amount::from_sat(fee_rate.into())
|
&commited_in.txid,
|
||||||
|
Amount::from_sat(fee_rate.into()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let final_tx = signed_psbt.extract_tx()?;
|
let final_tx = signed_psbt.extract_tx()?;
|
||||||
|
|
||||||
|
message.status = CachedMessageStatus::Complete;
|
||||||
|
|
||||||
Ok(createTransactionReturn {
|
Ok(createTransactionReturn {
|
||||||
txid: final_tx.txid().to_string(),
|
txid: final_tx.txid().to_string(),
|
||||||
transaction: serialize(&final_tx).to_lower_hex_string(),
|
transaction: serialize(&final_tx).to_lower_hex_string(),
|
||||||
new_network_msg: message
|
new_network_msg: message.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is what we call to confirm as a receiver
|
/// This is what we call to confirm as a receiver
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn create_confirmation_transaction(
|
pub fn create_confirmation_transaction(
|
||||||
message: CachedMessage,
|
message_id: u32,
|
||||||
fee_rate: u32,
|
fee_rate: u32,
|
||||||
) -> ApiResult<createTransactionReturn> {
|
) -> ApiResult<createTransactionReturn> {
|
||||||
if message.sender.is_none() || message.confirmed_by.is_none() {
|
let mut messages = lock_messages()?;
|
||||||
return Err(ApiError { message: "Invalid network message".to_owned() });
|
let message: &mut CachedMessage;
|
||||||
|
if let Some(m) = messages.iter_mut().find(|m| m.id == message_id) {
|
||||||
|
if m.sender.is_none() || m.commited_in.is_none() {
|
||||||
|
return Err(ApiError {
|
||||||
|
message: "Invalid network message".to_owned(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
message = m;
|
||||||
|
} else {
|
||||||
|
return Err(ApiError { message: format!("Can't find message for id {}", message_id) });
|
||||||
}
|
}
|
||||||
|
|
||||||
let sp_address: SilentPaymentAddress = message.sender.as_ref().unwrap().as_str().try_into()?;
|
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()?;
|
||||||
|
|
||||||
let sp_wallet: &SpWallet;
|
let sp_wallet: &SpWallet;
|
||||||
if sp_address.is_testnet() {
|
if sp_address.get_network() != SpNetwork::Mainnet {
|
||||||
sp_wallet = connected_user.try_get_recover()?;
|
sp_wallet = connected_user.try_get_recover()?;
|
||||||
} else {
|
} else {
|
||||||
sp_wallet = connected_user.try_get_main()?;
|
sp_wallet = connected_user.try_get_main()?;
|
||||||
@ -746,26 +787,38 @@ pub fn create_confirmation_transaction(
|
|||||||
nb_outputs: 1,
|
nb_outputs: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let commited_in = message.commited_in.clone().unwrap();
|
||||||
|
|
||||||
let signed_psbt = create_transaction_spend_outpoint(
|
let signed_psbt = create_transaction_spend_outpoint(
|
||||||
&message.confirmed_by.unwrap(),
|
&commited_in,
|
||||||
sp_wallet,
|
sp_wallet,
|
||||||
recipient,
|
recipient,
|
||||||
Amount::from_sat(fee_rate.into())
|
&commited_in.txid,
|
||||||
|
Amount::from_sat(fee_rate.into()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// what's the vout of the output sent to sender?
|
||||||
|
let sp_address2vouts = map_outputs_to_sp_address(&signed_psbt.to_string())?;
|
||||||
|
let recipients_vouts = sp_address2vouts
|
||||||
|
.get::<String>(&sp_address.into())
|
||||||
|
.expect("recipients didn't change")
|
||||||
|
.as_slice();
|
||||||
|
|
||||||
let final_tx = signed_psbt.extract_tx()?;
|
let final_tx = signed_psbt.extract_tx()?;
|
||||||
|
|
||||||
|
message.confirmed_by = Some(OutPoint { txid: final_tx.txid(), vout: recipients_vouts[0] as u32 });
|
||||||
|
|
||||||
Ok(createTransactionReturn {
|
Ok(createTransactionReturn {
|
||||||
txid: final_tx.txid().to_string(),
|
txid: final_tx.txid().to_string(),
|
||||||
transaction: serialize(&final_tx).to_lower_hex_string(),
|
transaction: serialize(&final_tx).to_lower_hex_string(),
|
||||||
new_network_msg: message
|
new_network_msg: message.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn create_notification_transaction(
|
pub fn create_notification_transaction(
|
||||||
address: String,
|
address: String,
|
||||||
commitment: Option<String>,
|
message: UnknownMessage,
|
||||||
fee_rate: u32,
|
fee_rate: u32,
|
||||||
) -> ApiResult<createTransactionReturn> {
|
) -> ApiResult<createTransactionReturn> {
|
||||||
let sp_address: SilentPaymentAddress = address.as_str().try_into()?;
|
let sp_address: SilentPaymentAddress = address.as_str().try_into()?;
|
||||||
@ -773,7 +826,7 @@ pub fn create_notification_transaction(
|
|||||||
let connected_user = lock_connected_user()?;
|
let connected_user = lock_connected_user()?;
|
||||||
|
|
||||||
let sp_wallet: &SpWallet;
|
let sp_wallet: &SpWallet;
|
||||||
if sp_address.is_testnet() {
|
if sp_address.get_network() != SpNetwork::Mainnet {
|
||||||
sp_wallet = connected_user.try_get_recover()?;
|
sp_wallet = connected_user.try_get_recover()?;
|
||||||
} else {
|
} else {
|
||||||
sp_wallet = connected_user.try_get_main()?;
|
sp_wallet = connected_user.try_get_main()?;
|
||||||
@ -785,42 +838,47 @@ pub fn create_notification_transaction(
|
|||||||
nb_outputs: 1,
|
nb_outputs: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let commitment = create_commitment(serde_json::to_string(&message)?);
|
||||||
|
|
||||||
let signed_psbt = create_transaction_for_address_with_shared_secret(
|
let signed_psbt = create_transaction_for_address_with_shared_secret(
|
||||||
recipient,
|
recipient,
|
||||||
sp_wallet,
|
sp_wallet,
|
||||||
commitment.as_deref(),
|
Some(&commitment),
|
||||||
Amount::from_sat(fee_rate.into()),
|
Amount::from_sat(fee_rate.into()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let psbt = Psbt::from_str(&signed_psbt)?;
|
let psbt = Psbt::from_str(&signed_psbt)?;
|
||||||
|
|
||||||
let partial_secret = sp_wallet
|
let partial_secret = sp_wallet.get_client().get_partial_secret_from_psbt(&psbt)?;
|
||||||
.get_client()
|
|
||||||
.get_partial_secret_from_psbt(&psbt)?;
|
|
||||||
|
|
||||||
let shared_point = shared_secret_point(
|
let shared_point =
|
||||||
&sp_wallet
|
sp_utils::sending::calculate_shared_point(&sp_address.get_scan_key(), &partial_secret);
|
||||||
.get_client()
|
|
||||||
.get_scan_key()
|
|
||||||
.public_key(&Secp256k1::signing_only()),
|
|
||||||
&partial_secret,
|
|
||||||
);
|
|
||||||
|
|
||||||
let shared_secret = AnkSharedSecret::new(shared_point);
|
let shared_secret = AnkSharedSecret::new(PublicKey::from_slice(&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 cipher = encrypt_with_key(serde_json::to_string(&message)?, shared_secret.to_byte_array().to_lower_hex_string())?;
|
||||||
|
|
||||||
// update our cache
|
// update our cache
|
||||||
let sp_address2vouts = map_outputs_to_sp_address(&signed_psbt)?;
|
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();
|
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
|
// for now let's just take the smallest vout that belongs to the recipient
|
||||||
let final_tx = psbt.extract_tx()?;
|
let final_tx = psbt.extract_tx()?;
|
||||||
let mut new_msg = CachedMessage::default();
|
let mut new_msg = CachedMessage::new();
|
||||||
new_msg.commitment = commitment;
|
new_msg.plaintext = Some(message.message);
|
||||||
new_msg.commited_in = Some(OutPoint { txid: final_tx.txid(), vout: recipients_vouts[0] as u32 });
|
new_msg.ciphertext = Some(cipher);
|
||||||
|
new_msg.commitment = Some(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.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string());
|
||||||
new_msg.recipient = Some(address);
|
new_msg.recipient = Some(address);
|
||||||
new_msg.sender = Some(sp_wallet.get_client().get_receiving_address());
|
new_msg.sender = Some(sp_wallet.get_client().get_receiving_address());
|
||||||
@ -908,7 +966,7 @@ pub fn create_faucet_msg() -> ApiResult<CachedMessage> {
|
|||||||
let user = lock_connected_user()?;
|
let user = lock_connected_user()?;
|
||||||
let sp_address = user.try_get_recover()?.get_client().get_receiving_address();
|
let sp_address = user.try_get_recover()?.get_client().get_receiving_address();
|
||||||
|
|
||||||
let mut commitment = [0u8;64];
|
let mut commitment = [0u8; 64];
|
||||||
thread_rng().fill_bytes(&mut commitment);
|
thread_rng().fill_bytes(&mut commitment);
|
||||||
|
|
||||||
let mut cached_msg = CachedMessage::new();
|
let mut cached_msg = CachedMessage::new();
|
||||||
|
111
src/services.ts
111
src/services.ts
@ -1,4 +1,4 @@
|
|||||||
import { createUserReturn, User, Process, createTransactionReturn, parse_network_msg, outputs_list, FaucetMessage, AnkFlag, NewTxMessage, encryptWithNewKeyResult, AnkSharedSecret, CachedMessage } from '../dist/pkg/sdk_client';
|
import { createUserReturn, User, Process, createTransactionReturn, parse_network_msg, outputs_list, FaucetMessage, AnkFlag, NewTxMessage, encryptWithNewKeyResult, AnkSharedSecret, CachedMessage, UnknownMessage } from '../dist/pkg/sdk_client';
|
||||||
import IndexedDB from './database'
|
import IndexedDB from './database'
|
||||||
import { WebSocketClient } from './websockets';
|
import { WebSocketClient } from './websockets';
|
||||||
|
|
||||||
@ -105,36 +105,24 @@ class Services {
|
|||||||
const recipientSpAddress = spAddressElement.value;
|
const recipientSpAddress = spAddressElement.value;
|
||||||
const message = messageElement.value;
|
const message = messageElement.value;
|
||||||
|
|
||||||
const msg_payload = JSON.stringify({sender: this.sp_address, message: message});
|
const msg_payload: UnknownMessage = {sender: this.sp_address!, message: message};
|
||||||
|
|
||||||
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;
|
let networkMsg = notificationInfo.new_network_msg;
|
||||||
let shared_secret = '';
|
console.debug(networkMsg);
|
||||||
if (networkMsg.shared_secret) {
|
|
||||||
shared_secret = networkMsg.shared_secret;
|
|
||||||
} else {
|
|
||||||
throw 'no shared_secret';
|
|
||||||
}
|
|
||||||
console.info('Successfully sent notification transaction');
|
|
||||||
|
|
||||||
const connection = await services.pickWebsocketConnectionRandom();
|
const connection = await services.pickWebsocketConnectionRandom();
|
||||||
const flag: AnkFlag = "Unknown";
|
const flag: AnkFlag = "Unknown";
|
||||||
// encrypt the message(s)
|
|
||||||
// TODO we'd rather do that in the wasm as part of notify_address_for_message
|
|
||||||
try {
|
try {
|
||||||
const cipher = await services.encryptData(msg_payload, shared_secret);
|
// send message (transaction in envelope)
|
||||||
let updated_msg = notificationInfo.new_network_msg;
|
await services.updateMessages(networkMsg);
|
||||||
updated_msg.plaintext = msg_payload;
|
connection?.sendMessage(flag, networkMsg.ciphertext!);
|
||||||
updated_msg.ciphertext = cipher;
|
|
||||||
await services.updateMessages(updated_msg);
|
|
||||||
connection?.sendMessage(flag, cipher);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
// add peers list
|
// add peers list
|
||||||
// add processes list
|
// add processes list
|
||||||
// send message (transaction in envelope)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -820,6 +808,41 @@ class Services {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async answer_confirmation_message(msg: CachedMessage): Promise<void> {
|
||||||
|
const services = await Services.getInstance();
|
||||||
|
const connection = await services.pickWebsocketConnectionRandom();
|
||||||
|
if (!connection) {
|
||||||
|
throw new Error("No connection to relay");
|
||||||
|
}
|
||||||
|
let user: User;
|
||||||
|
try {
|
||||||
|
let possibleUser = await services.getUserInfo();
|
||||||
|
if (!possibleUser) {
|
||||||
|
throw new Error("No user loaded, please first create a new user or login");
|
||||||
|
} else {
|
||||||
|
user = possibleUser;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
let notificationInfo: createTransactionReturn;
|
||||||
|
try {
|
||||||
|
const feeRate = 1;
|
||||||
|
notificationInfo = services.sdkClient.answer_confirmation_transaction(msg.id, feeRate);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to create confirmation transaction: ${error}`);
|
||||||
|
}
|
||||||
|
const flag: AnkFlag = "NewTx";
|
||||||
|
const newTxMsg: NewTxMessage = {
|
||||||
|
'transaction': notificationInfo.transaction,
|
||||||
|
'tweak_data': null
|
||||||
|
}
|
||||||
|
connection.sendMessage(flag, JSON.stringify(newTxMsg));
|
||||||
|
await services.updateMessages(notificationInfo.new_network_msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
public async confirm_sender_address(msg: CachedMessage): Promise<void> {
|
public async confirm_sender_address(msg: CachedMessage): Promise<void> {
|
||||||
const services = await Services.getInstance();
|
const services = await Services.getInstance();
|
||||||
const connection = await services.pickWebsocketConnectionRandom();
|
const connection = await services.pickWebsocketConnectionRandom();
|
||||||
@ -841,7 +864,7 @@ class Services {
|
|||||||
let notificationInfo: createTransactionReturn;
|
let notificationInfo: createTransactionReturn;
|
||||||
try {
|
try {
|
||||||
const feeRate = 1;
|
const feeRate = 1;
|
||||||
notificationInfo = services.sdkClient.answer_confirmation_transaction(msg, feeRate);
|
notificationInfo = services.sdkClient.create_confirmation_transaction(msg.id, feeRate);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Failed to create confirmation transaction: ${error}`);
|
throw new Error(`Failed to create confirmation transaction: ${error}`);
|
||||||
}
|
}
|
||||||
@ -851,10 +874,11 @@ class Services {
|
|||||||
'tweak_data': null
|
'tweak_data': null
|
||||||
}
|
}
|
||||||
connection.sendMessage(flag, JSON.stringify(newTxMsg));
|
connection.sendMessage(flag, JSON.stringify(newTxMsg));
|
||||||
|
await services.updateMessages(notificationInfo.new_network_msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async notify_address_for_message(sp_address: string, message: string): Promise<createTransactionReturn> {
|
public async notify_address_for_message(sp_address: string, message: UnknownMessage): Promise<createTransactionReturn> {
|
||||||
const services = await Services.getInstance();
|
const services = await Services.getInstance();
|
||||||
const connection = await services.pickWebsocketConnectionRandom();
|
const connection = await services.pickWebsocketConnectionRandom();
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
@ -863,62 +887,19 @@ class Services {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const feeRate = 1;
|
const feeRate = 1;
|
||||||
const commitment = services.sdkClient.create_commitment(message);
|
let notificationInfo: createTransactionReturn = services.sdkClient.create_notification_transaction(sp_address, message, feeRate);
|
||||||
let notificationInfo: createTransactionReturn = services.sdkClient.create_notification_transaction(sp_address, commitment, feeRate);
|
|
||||||
const flag: AnkFlag = "NewTx";
|
const flag: AnkFlag = "NewTx";
|
||||||
const newTxMsg: NewTxMessage = {
|
const newTxMsg: NewTxMessage = {
|
||||||
'transaction': notificationInfo.transaction,
|
'transaction': notificationInfo.transaction,
|
||||||
'tweak_data': null
|
'tweak_data': null
|
||||||
}
|
}
|
||||||
connection.sendMessage(flag, JSON.stringify(newTxMsg));
|
connection.sendMessage(flag, JSON.stringify(newTxMsg));
|
||||||
|
console.info('Successfully sent notification transaction');
|
||||||
return notificationInfo;
|
return notificationInfo;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw 'Failed to create notification transaction:", error';
|
throw 'Failed to create notification transaction:", error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// public async encryptData(data: string, sharedSecret: Record<string, AnkSharedSecret>): Promise<Map<string, string>> {
|
|
||||||
// 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: 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<string> {
|
|
||||||
const services = await Services.getInstance();
|
|
||||||
|
|
||||||
try {
|
|
||||||
let res: string = services.sdkClient.encrypt_with_key(data, key);
|
|
||||||
return res;
|
|
||||||
} catch (error) {
|
|
||||||
throw 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;
|
||||||
|
@ -34,7 +34,7 @@ class WebSocketClient {
|
|||||||
if (res.status === 'FaucetComplete') {
|
if (res.status === 'FaucetComplete') {
|
||||||
// we received a faucet tx, there's nothing else to do
|
// we received a faucet tx, there's nothing else to do
|
||||||
window.alert(`New faucet output\n${res.commited_in}`);
|
window.alert(`New faucet output\n${res.commited_in}`);
|
||||||
await services.removeMessage(res.id);
|
await services.updateMessages(res);
|
||||||
await services.updateOwnedOutputsForUser();
|
await services.updateOwnedOutputsForUser();
|
||||||
} else if (res.status === 'TxWaitingCipher') {
|
} else if (res.status === 'TxWaitingCipher') {
|
||||||
// we received a tx but we don't have the cipher
|
// we received a tx but we don't have the cipher
|
||||||
@ -47,6 +47,7 @@ class WebSocketClient {
|
|||||||
await services.updateMessages(res);
|
await services.updateMessages(res);
|
||||||
} else if (res.status === 'SentWaitingConfirmation') {
|
} else if (res.status === 'SentWaitingConfirmation') {
|
||||||
// We are sender and we're waiting for the challenge that will confirm recipient got the transaction and the message
|
// We are sender and we're waiting for the challenge that will confirm recipient got the transaction and the message
|
||||||
|
await services.updateMessages(res);
|
||||||
await services.updateOwnedOutputsForUser();
|
await services.updateOwnedOutputsForUser();
|
||||||
} else if (res.status === 'MustSpendConfirmation') {
|
} else if (res.status === 'MustSpendConfirmation') {
|
||||||
// we received a challenge for a notification we made
|
// we received a challenge for a notification we made
|
||||||
@ -54,7 +55,17 @@ class WebSocketClient {
|
|||||||
window.alert(`Spending ${res.confirmed_by} to prove our identity`);
|
window.alert(`Spending ${res.confirmed_by} to prove our identity`);
|
||||||
console.debug(`sending confirm message to ${res.recipient}`);
|
console.debug(`sending confirm message to ${res.recipient}`);
|
||||||
await services.updateMessages(res);
|
await services.updateMessages(res);
|
||||||
|
await services.answer_confirmation_message(res);
|
||||||
|
} else if (res.status === 'ReceivedMustConfirm') {
|
||||||
|
// we found a notification and decrypted the cipher
|
||||||
|
window.alert(`Received message from ${res.sender}\n${res.plaintext}`);
|
||||||
|
// we must spend the commited_in output to sender
|
||||||
|
await services.updateMessages(res);
|
||||||
await services.confirm_sender_address(res);
|
await services.confirm_sender_address(res);
|
||||||
|
} else if (res.status === 'Complete') {
|
||||||
|
window.alert(`Received confirmation that ${res.sender} is the author of message ${res.plaintext}`)
|
||||||
|
await services.updateMessages(res);
|
||||||
|
await services.updateOwnedOutputsForUser();
|
||||||
} else {
|
} else {
|
||||||
console.debug('Received an unimplemented valid message');
|
console.debug('Received an unimplemented valid message');
|
||||||
}
|
}
|
||||||
@ -88,7 +99,7 @@ class WebSocketClient {
|
|||||||
// console.debug("Sending message:", JSON.stringify(networkMessage));
|
// console.debug("Sending message:", JSON.stringify(networkMessage));
|
||||||
this.ws.send(JSON.stringify(networkMessage));
|
this.ws.send(JSON.stringify(networkMessage));
|
||||||
} else {
|
} else {
|
||||||
console.error('WebSocket is not open. ReadyState:', this.ws.readyState);
|
console.warn('WebSocket is not open. ReadyState:', this.ws.readyState);
|
||||||
this.messageQueue.push(message);
|
this.messageQueue.push(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user