208 lines
6.4 KiB
Rust
208 lines
6.4 KiB
Rust
use anyhow::{Error, Result};
|
|
use js_sys::Date;
|
|
use rand::{thread_rng, RngCore};
|
|
use serde::{Deserialize, Serialize};
|
|
use sp_client::bitcoin::hex::{DisplayHex, FromHex};
|
|
use sp_client::bitcoin::OutPoint;
|
|
use tsify::Tsify;
|
|
|
|
use crate::crypto::{Aes256Decryption, Purpose};
|
|
use crate::error::AnkError;
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Tsify)]
|
|
#[tsify(into_wasm_abi, from_wasm_abi)]
|
|
pub enum AnkFlag {
|
|
NewTx,
|
|
Faucet,
|
|
Cipher,
|
|
Unknown,
|
|
}
|
|
|
|
impl From<&str> for AnkFlag {
|
|
fn from(value: &str) -> Self {
|
|
match value {
|
|
"NewTx" => Self::NewTx,
|
|
"Faucet" => Self::Faucet,
|
|
"Cipher" => Self::Cipher,
|
|
_ => Self::Unknown,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<String> for AnkFlag {
|
|
fn from(value: String) -> Self {
|
|
(&value[..]).into()
|
|
}
|
|
}
|
|
|
|
impl AnkFlag {
|
|
pub fn new_from_byte(byte: u8) -> Self {
|
|
match byte {
|
|
0 => Self::NewTx,
|
|
1 => Self::Faucet,
|
|
2 => Self::Cipher,
|
|
_ => Self::Unknown,
|
|
}
|
|
}
|
|
|
|
pub fn as_str(&self) -> &str {
|
|
match self {
|
|
Self::NewTx => "NewTx",
|
|
Self::Faucet => "Faucet",
|
|
Self::Cipher => "Cipher",
|
|
Self::Unknown => "Unknown",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Tsify)]
|
|
#[tsify(into_wasm_abi, from_wasm_abi)]
|
|
pub struct FaucetMessage {
|
|
pub sp_address: String,
|
|
pub commitment: String,
|
|
pub error: Option<AnkError>,
|
|
}
|
|
|
|
impl FaucetMessage {
|
|
pub fn new(sp_address: String) -> Self {
|
|
let mut buf = [0u8;64];
|
|
thread_rng().fill_bytes(&mut buf);
|
|
Self { sp_address, commitment: buf.to_lower_hex_string(), error: None }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Tsify)]
|
|
#[tsify(into_wasm_abi, from_wasm_abi)]
|
|
pub struct NewTxMessage {
|
|
pub transaction: String,
|
|
pub tweak_data: Option<String>,
|
|
pub error: Option<AnkError>,
|
|
}
|
|
|
|
impl NewTxMessage {
|
|
pub fn new(transaction: String, tweak_data: Option<String>) -> Self {
|
|
Self {
|
|
transaction,
|
|
tweak_data,
|
|
error: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Tsify)]
|
|
#[tsify(into_wasm_abi, from_wasm_abi)]
|
|
pub struct CipherMessage {
|
|
pub sender: String,
|
|
pub message: String,
|
|
pub error: Option<AnkError>,
|
|
}
|
|
|
|
impl CipherMessage {
|
|
pub fn new(sender: String, message: String) -> Self {
|
|
Self {
|
|
sender,
|
|
message,
|
|
error: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Tsify)]
|
|
#[tsify(into_wasm_abi, from_wasm_abi)]
|
|
pub struct AnkNetworkMsg {
|
|
pub flag: AnkFlag,
|
|
pub content: String,
|
|
}
|
|
|
|
impl AnkNetworkMsg {
|
|
pub fn new(flag: AnkFlag, raw: &str) -> Self {
|
|
Self {
|
|
flag,
|
|
content: raw.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Tsify, Clone)]
|
|
pub enum CachedMessageStatus {
|
|
#[default]
|
|
NoStatus,
|
|
FaucetWaiting,
|
|
FaucetComplete,
|
|
CipherWaitingTx,
|
|
TxWaitingCipher,
|
|
SentWaitingConfirmation, // we're sender and wait for commited_in to be spent
|
|
ReceivedMustConfirm, // we're receiver and we spend commited_in to sender
|
|
MustSpendConfirmation, // we're sender and we must spend confirmed_by
|
|
Complete,
|
|
}
|
|
|
|
/// Unique struct for both 3nk messages and notification/key exchange, both rust and ts
|
|
/// 0. Faucet: commited_in with nothing else, status is NoStatus
|
|
/// 1. notification:
|
|
/// 0. sender: ciphertext, plaintext, commited_in, sender, recipient, shared_secret, key
|
|
/// 1. receiver (without tx): ciphertext
|
|
/// 2. receiver (tx without msg): commited_in, commitment, recipient, shared_secret
|
|
/// 3. receiver (receive tx after msg): plaintext, key, sender, commited_in, commitment, recipient, shared_secret
|
|
/// 4. receiver (msg after tx): ciphertext, key, plaintext, sender
|
|
/// 2. confirmation:
|
|
/// 0. receiver (spend the smallest vout that pays him in the first tx): confirmed_by
|
|
/// 1. sender (detect a transaction that pays him and spend commited_by): confirmed_by
|
|
/// 2. 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 CachedMessage {
|
|
pub id: u32,
|
|
pub status: CachedMessageStatus,
|
|
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<AnkError>,
|
|
}
|
|
|
|
impl CachedMessage {
|
|
pub fn new() -> Self {
|
|
let mut new = Self::default();
|
|
let mut buf = [0u8;4];
|
|
thread_rng().fill_bytes(&mut buf);
|
|
new.id = u32::from_be_bytes(buf);
|
|
new.timestamp = Date::now().floor() as u64;
|
|
new
|
|
}
|
|
|
|
pub fn try_decrypt_cipher(&self, cipher: Vec<u8>) -> Result<Vec<u8>> {
|
|
if self.ciphertext.is_some() || self.shared_secret.is_none() {
|
|
return Err(Error::msg(
|
|
"Can't try decrypt this message, there's already a ciphertext or no shared secret"
|
|
));
|
|
}
|
|
let mut shared_secret = [0u8; 32];
|
|
shared_secret
|
|
.copy_from_slice(&Vec::from_hex(self.shared_secret.as_ref().unwrap())?);
|
|
let aes_decrypt = Aes256Decryption::new(Purpose::Arbitrary, cipher, shared_secret)?;
|
|
|
|
aes_decrypt.decrypt_with_key()
|
|
}
|
|
|
|
pub fn try_decrypt_with_shared_secret(&self, shared_secret: [u8; 32]) -> Result<Vec<u8>> {
|
|
if self.ciphertext.is_none() || self.shared_secret.is_some() {
|
|
return Err(Error::msg(
|
|
"Can't try decrypt this message, ciphertext is none or shared_secret already found"
|
|
));
|
|
}
|
|
let cipher_bin = Vec::from_hex(self.ciphertext.as_ref().unwrap())?;
|
|
let aes_decrypt =
|
|
Aes256Decryption::new(Purpose::Arbitrary, cipher_bin, shared_secret)?;
|
|
|
|
aes_decrypt.decrypt_with_key()
|
|
}
|
|
}
|