379 lines
11 KiB
Rust
379 lines
11 KiB
Rust
use std::any::Any;
|
|
use std::collections::HashMap;
|
|
use std::str::FromStr;
|
|
use std::sync::{Mutex, OnceLock, PoisonError};
|
|
|
|
use log::debug;
|
|
use rand::Rng;
|
|
|
|
use anyhow::Error as AnyhowError;
|
|
use sdk_common::crypto::AnkSharedSecret;
|
|
use serde_json::Error as SerdeJsonError;
|
|
use shamir::SecretData;
|
|
use sp_backend::bitcoin::consensus::{deserialize, serialize};
|
|
use sp_backend::bitcoin::hex::{parse, DisplayHex, FromHex, HexToBytesError};
|
|
use sp_backend::bitcoin::secp256k1::ecdh::SharedSecret;
|
|
use sp_backend::bitcoin::secp256k1::{PublicKey, SecretKey};
|
|
use sp_backend::bitcoin::{OutPoint, Transaction, Txid};
|
|
use sp_backend::silentpayments::Error as SpError;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use sp_backend::silentpayments::sending::SilentPaymentAddress;
|
|
use tsify::Tsify;
|
|
use wasm_bindgen::convert::FromWasmAbi;
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
use sdk_common::network::{AnkFlag, AnkNetworkMsg, NewTxMessage};
|
|
|
|
use sp_backend::spclient::{derive_keys_from_seed, OutputList, OwnedOutput, SpClient};
|
|
use sp_backend::spclient::{SpWallet, SpendKey};
|
|
|
|
use crate::images;
|
|
use crate::silentpayments::{check_transaction, create_transaction_for_address};
|
|
use crate::user::{lock_connected_users, User, UserWallets, CONNECTED_USERS};
|
|
|
|
use crate::process::Process;
|
|
|
|
type ApiResult<T: FromWasmAbi> = Result<T, ApiError>;
|
|
|
|
const IS_TESTNET: bool = true;
|
|
|
|
#[derive(Debug)]
|
|
struct ApiError {
|
|
message: String,
|
|
}
|
|
|
|
impl From<AnyhowError> for ApiError {
|
|
fn from(value: AnyhowError) -> Self {
|
|
ApiError {
|
|
message: value.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<SpError> for ApiError {
|
|
fn from(value: SpError) -> Self {
|
|
ApiError {
|
|
message: value.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<SerdeJsonError> for ApiError {
|
|
fn from(value: SerdeJsonError) -> Self {
|
|
ApiError {
|
|
message: value.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<HexToBytesError> for ApiError {
|
|
fn from(value: HexToBytesError) -> Self {
|
|
ApiError {
|
|
message: value.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<sp_backend::bitcoin::secp256k1::Error> for ApiError {
|
|
fn from(value: sp_backend::bitcoin::secp256k1::Error) -> Self {
|
|
ApiError {
|
|
message: value.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<sp_backend::bitcoin::consensus::encode::Error> for ApiError {
|
|
fn from(value: sp_backend::bitcoin::consensus::encode::Error) -> Self {
|
|
ApiError {
|
|
message: value.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Into<JsValue> for ApiError {
|
|
fn into(self) -> JsValue {
|
|
JsValue::from_str(&self.message)
|
|
}
|
|
}
|
|
|
|
#[derive(Tsify, Serialize, Deserialize)]
|
|
#[tsify(into_wasm_abi, from_wasm_abi)]
|
|
#[allow(non_camel_case_types)]
|
|
pub struct createUserReturn {
|
|
pub user: User,
|
|
pub output_list_vec: Vec<OutputList>,
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn setup() {
|
|
wasm_logger::init(wasm_logger::Config::default());
|
|
}
|
|
|
|
// Should be transfered to annother module
|
|
pub fn generate_sp_wallet(
|
|
label: Option<String>,
|
|
birthday: u32,
|
|
is_testnet: bool,
|
|
) -> ApiResult<SpWallet> {
|
|
let mut seed = [0u8; 64];
|
|
rand::thread_rng().fill(&mut seed);
|
|
let (scan_sk, spend_sk) = derive_keys_from_seed(&seed, is_testnet)?;
|
|
let sp_client = SpClient::new(
|
|
label.unwrap_or("default".into()),
|
|
scan_sk,
|
|
SpendKey::Secret(spend_sk),
|
|
None,
|
|
IS_TESTNET,
|
|
)?;
|
|
let our_address: SilentPaymentAddress = sp_client.get_receiving_address().try_into()?;
|
|
log::info!(
|
|
"Created client for sp with address: {}",
|
|
our_address.to_string()
|
|
);
|
|
|
|
let res = SpWallet::new(sp_client, None)?;
|
|
|
|
Ok(res)
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn get_receiving_address(pre_id: String) -> ApiResult<String> {
|
|
if let Some(my_wallets) = lock_connected_users()?.get(&pre_id) {
|
|
Ok(my_wallets.recover.get_client().get_receiving_address())
|
|
} else {
|
|
Err(ApiError {
|
|
message: "Unknown user pre_id".to_owned(),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn create_user(
|
|
password: String, // Attention à la conversion depuis le js
|
|
label: Option<String>,
|
|
birthday_main: u32,
|
|
birthday_signet: u32,
|
|
process: String,
|
|
) -> ApiResult<createUserReturn> {
|
|
//recover
|
|
let sp_wallet_recover = generate_sp_wallet(label.clone(), birthday_signet, true)?;
|
|
//revoke
|
|
let sp_wallet_revoke = generate_sp_wallet(label.clone(), birthday_signet, true)?;
|
|
//mainet
|
|
let sp_wallet_main = generate_sp_wallet(label, birthday_main, false)?;
|
|
|
|
let user_wallets = UserWallets::new(
|
|
Some(sp_wallet_main),
|
|
sp_wallet_recover,
|
|
Some(sp_wallet_revoke),
|
|
);
|
|
|
|
let user = User::new(user_wallets.clone(), password, process)?;
|
|
|
|
let outputs = user_wallets.get_all_outputs();
|
|
|
|
lock_connected_users()?.insert(user.pre_id.clone(), user_wallets);
|
|
|
|
let generate_user = createUserReturn {
|
|
user,
|
|
output_list_vec: outputs,
|
|
};
|
|
|
|
Ok(generate_user)
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn add_data_to_image(image: Vec<u8>, data: Vec<u8>, is_revoke: bool) -> ApiResult<Vec<u8>> {
|
|
let mut new_image: Vec<u8>;
|
|
if is_revoke {
|
|
new_image = images::BackUpImage::new_revoke(image, &data)?.to_inner();
|
|
} else {
|
|
new_image = images::BackUpImage::new_recover(image, &data)?.to_inner();
|
|
}
|
|
Ok(new_image)
|
|
}
|
|
|
|
#[derive(Tsify, Serialize, Deserialize)]
|
|
#[tsify(into_wasm_abi)]
|
|
#[allow(non_camel_case_types)]
|
|
pub struct get_process_return(Vec<Process>);
|
|
|
|
#[wasm_bindgen]
|
|
pub fn get_processes() -> ApiResult<get_process_return> {
|
|
let MEMBERS: [String;5] = [
|
|
"tsp1qqdvmxycf3c3tf2qhpev0npx25rj05270d6j2pcsrfk2qn5gdy0rpwq6hd9u9sztl3fwmrzzqafzl3ymkq86aqfz5jl5egdkz72tqmhcnrswdz3pk".to_owned(),
|
|
"tsp1qqwafwn7dcr9d6ta0w8fjtd9s53u72x9qmmtgd8adqr7454xl90a5jq3vw23l2x8ypt55nrg7trl9lwz5xr5j357ucu4sf9rfmvc0zujcpqcps6rm".to_owned(),
|
|
"tsp1qqw02t5hmg5rxpjdkmjdnnmhvuc76wt6vlqdmn2zafnh6axxjd6e2gqcz04gzvnkzf572mur8spyx2a2s8sqzll2ymdpyz59cpl96j4zuvcdvrzxz".to_owned(),
|
|
"tsp1qqgpay2r5jswm7vcv24xd94shdf90w30vxtql9svw7qnlnrzd6xt02q7s7z57uw0sssh6c0xddcrryq4mxup93jsh3gfau3autrawl8umkgsyupkm".to_owned(),
|
|
"tsp1qqtsqmtgnxp0lsmnxyxcq52zpgxwugwlq8urlprs5pr5lwyqc789gjqhx5qra6g4rszsq43pms6nguee2l9trx905rk5sgntek05hnf7say4ru69y".to_owned(),
|
|
];
|
|
//instances of process
|
|
let process1 = Process {
|
|
id: 6,
|
|
name: String::from("Messaging"),
|
|
version: String::from("1.0"),
|
|
members: MEMBERS.to_vec(),
|
|
html: crate::process::HTML_MESSAGING.to_owned(),
|
|
style: crate::process::CSS.to_owned(),
|
|
script: "".to_owned(),
|
|
};
|
|
let process2 = Process {
|
|
id: 7,
|
|
name: String::from("Kotpart"),
|
|
version: String::from("1.0"),
|
|
members: MEMBERS.to_vec(),
|
|
html: crate::process::HTML_MESSAGING.to_owned(),
|
|
style: crate::process::CSS.to_owned(),
|
|
script: "".to_owned(),
|
|
};
|
|
let process3 = Process {
|
|
id: 8,
|
|
name: String::from("Storage"),
|
|
version: String::from("1.0"),
|
|
members: MEMBERS.to_vec(),
|
|
html: crate::process::HTML_MESSAGING.to_owned(),
|
|
style: crate::process::CSS.to_owned(),
|
|
script: "".to_owned(),
|
|
};
|
|
|
|
// vec with the instances of processes
|
|
let mut data_process: Vec<Process> = Vec::new();
|
|
data_process.push(process1);
|
|
data_process.push(process2);
|
|
data_process.push(process3);
|
|
Ok(get_process_return(data_process))
|
|
}
|
|
|
|
#[derive(Debug, Tsify, Serialize, Deserialize)]
|
|
#[tsify(from_wasm_abi)]
|
|
#[allow(non_camel_case_types)]
|
|
pub struct recover_data(Vec<u8>);
|
|
|
|
impl recover_data {
|
|
fn as_inner(&self) -> &[u8] {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Tsify, Serialize, Deserialize)]
|
|
#[tsify(from_wasm_abi)]
|
|
#[allow(non_camel_case_types)]
|
|
pub struct shamir_shares(Vec<Vec<u8>>);
|
|
|
|
impl shamir_shares {
|
|
fn as_inner(&self) -> &[Vec<u8>] {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Tsify, Serialize, Deserialize)]
|
|
#[tsify(from_wasm_abi, into_wasm_abi)]
|
|
#[allow(non_camel_case_types)]
|
|
pub struct outputs_list(Vec<OutputList>);
|
|
|
|
impl outputs_list {
|
|
fn as_inner(&self) -> &[OutputList] {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn login_user(
|
|
user_password: String,
|
|
pre_id: String,
|
|
recover: recover_data,
|
|
shares: shamir_shares,
|
|
outputs: outputs_list,
|
|
) -> ApiResult<()> {
|
|
let res = User::login(
|
|
pre_id,
|
|
user_password,
|
|
recover.as_inner(),
|
|
shares.as_inner(),
|
|
outputs.as_inner(),
|
|
)?;
|
|
|
|
Ok(res)
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn check_transaction_for_silent_payments(
|
|
tx_hex: String,
|
|
blockheight: u32,
|
|
tweak_data_hex: String,
|
|
) -> ApiResult<String> {
|
|
let tx = deserialize::<Transaction>(&Vec::from_hex(&tx_hex)?)?;
|
|
let tweak_data = PublicKey::from_str(&tweak_data_hex)?;
|
|
|
|
let updated_user = check_transaction(&tx, blockheight, tweak_data)?;
|
|
|
|
Ok(updated_user)
|
|
}
|
|
|
|
#[derive(Tsify, Serialize, Deserialize)]
|
|
#[tsify(into_wasm_abi, from_wasm_abi)]
|
|
#[allow(non_camel_case_types)]
|
|
pub struct parseNetworkMsgReturn {
|
|
topic: String,
|
|
message: String,
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn parse_network_msg(raw: String) -> ApiResult<parseNetworkMsgReturn> {
|
|
if let Ok(ank_msg) = serde_json::from_str::<AnkNetworkMsg>(&raw) {
|
|
match ank_msg.flag {
|
|
AnkFlag::NewTx => {
|
|
let tx_message = serde_json::from_str::<NewTxMessage>(&ank_msg.content)?;
|
|
let tx = deserialize::<Transaction>(&Vec::from_hex(&tx_message.transaction)?)?;
|
|
if tx_message.tweak_data.is_none() {
|
|
return Err(ApiError {
|
|
message: "Missing tweak_data".to_owned(),
|
|
});
|
|
}
|
|
let partial_tweak = PublicKey::from_str(&tx_message.tweak_data.unwrap())?;
|
|
let txid = check_transaction(&tx, 0, partial_tweak)?;
|
|
return Ok(parseNetworkMsgReturn {
|
|
topic: AnkFlag::NewTx.as_str().to_owned(),
|
|
message: txid,
|
|
});
|
|
}
|
|
AnkFlag::Faucet => unimplemented!(),
|
|
AnkFlag::Error => {
|
|
return Ok(parseNetworkMsgReturn {
|
|
topic: AnkFlag::Error.as_str().to_owned(),
|
|
message: ank_msg.content.to_owned(),
|
|
})
|
|
}
|
|
_ => unimplemented!(),
|
|
}
|
|
} else {
|
|
Err(ApiError {
|
|
message: format!("Can't parse message as a valid 4nk message: {}", raw),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn parse_4nk_msg(raw: String) -> Option<String>{
|
|
if let Ok(msg) = AnkNetworkMsg::new(&raw) {
|
|
match msg.topic {
|
|
AnkTopic::Faucet => {
|
|
match Txid::from_str(msg.content) {
|
|
Ok(txid) => {
|
|
// return the txid for verification
|
|
Some(txid.to_string())
|
|
},
|
|
Err(e) => {
|
|
log::error!("Invalid txid with a \"faucet\" message: {}", e.to_string());
|
|
None
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
log::debug!("Can't parse message as a valid 4nk message: {}", raw);
|
|
None
|
|
}
|
|
}
|