Refactoring
This commit is contained in:
parent
741b303348
commit
750bf69aa5
@ -1,12 +1,17 @@
|
|||||||
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Mutex, OnceLock};
|
use std::str::FromStr;
|
||||||
|
use std::sync::{Mutex, OnceLock, PoisonError};
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
use anyhow::Error as AnyhowError;
|
use anyhow::Error as AnyhowError;
|
||||||
use serde_json::Error as SerdeJsonError;
|
use serde_json::Error as SerdeJsonError;
|
||||||
use shamir::SecretData;
|
use shamir::SecretData;
|
||||||
use sp_backend::bitcoin::secp256k1::SecretKey;
|
use sp_backend::bitcoin::consensus::deserialize;
|
||||||
|
use sp_backend::bitcoin::hex::{FromHex, HexToBytesError};
|
||||||
|
use sp_backend::bitcoin::secp256k1::{PublicKey, SecretKey};
|
||||||
|
use sp_backend::bitcoin::{Transaction, Txid};
|
||||||
use sp_backend::silentpayments::Error as SpError;
|
use sp_backend::silentpayments::Error as SpError;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -15,10 +20,12 @@ use tsify::Tsify;
|
|||||||
use wasm_bindgen::convert::FromWasmAbi;
|
use wasm_bindgen::convert::FromWasmAbi;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use sp_backend::spclient::SpendKey;
|
|
||||||
use sp_backend::spclient::{derive_keys_from_seed, OutputList, SpClient};
|
use sp_backend::spclient::{derive_keys_from_seed, OutputList, SpClient};
|
||||||
|
use sp_backend::spclient::{SpWallet, SpendKey};
|
||||||
|
|
||||||
use crate::user::{User, UserKeys, CONNECTED_USERS};
|
use crate::network::{BitcoinNetworkMsg, BitcoinTopic};
|
||||||
|
use crate::silentpayments::check_transaction;
|
||||||
|
use crate::user::{lock_connected_users, User, UserWallets, CONNECTED_USERS};
|
||||||
|
|
||||||
use crate::process::Process;
|
use crate::process::Process;
|
||||||
|
|
||||||
@ -55,6 +62,30 @@ impl From<SerdeJsonError> for ApiError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
impl Into<JsValue> for ApiError {
|
||||||
fn into(self) -> JsValue {
|
fn into(self) -> JsValue {
|
||||||
JsValue::from_str(&self.message)
|
JsValue::from_str(&self.message)
|
||||||
@ -62,7 +93,7 @@ impl Into<JsValue> for ApiError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Tsify, Serialize, Deserialize)]
|
#[derive(Tsify, Serialize, Deserialize)]
|
||||||
#[tsify(into_wasm_abi)]
|
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub struct createUserReturn {
|
pub struct createUserReturn {
|
||||||
pub user: User,
|
pub user: User,
|
||||||
@ -75,16 +106,11 @@ pub fn setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Should be transfered to annother module
|
// Should be transfered to annother module
|
||||||
pub struct GenerateSPWallet {
|
|
||||||
pub sp_client: SpClient,
|
|
||||||
pub sp_outputs: OutputList,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_sp_wallet(
|
pub fn generate_sp_wallet(
|
||||||
label: Option<String>,
|
label: Option<String>,
|
||||||
birthday: u32,
|
birthday: u32,
|
||||||
is_testnet: bool,
|
is_testnet: bool,
|
||||||
) -> ApiResult<GenerateSPWallet> {
|
) -> ApiResult<SpWallet> {
|
||||||
let mut seed = [0u8; 64];
|
let mut seed = [0u8; 64];
|
||||||
rand::thread_rng().fill(&mut seed);
|
rand::thread_rng().fill(&mut seed);
|
||||||
let (scan_sk, spend_sk) = derive_keys_from_seed(&seed, is_testnet)?;
|
let (scan_sk, spend_sk) = derive_keys_from_seed(&seed, is_testnet)?;
|
||||||
@ -101,20 +127,7 @@ pub fn generate_sp_wallet(
|
|||||||
our_address.to_string()
|
our_address.to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
//let sp_client_json = serde_json::to_string(&sp_client)?;
|
let res = SpWallet::new(sp_client, None)?;
|
||||||
|
|
||||||
// Generate an empty outputs
|
|
||||||
let sp_outputs = OutputList::new(
|
|
||||||
our_address.get_scan_key(),
|
|
||||||
our_address.get_spend_key(),
|
|
||||||
birthday,
|
|
||||||
);
|
|
||||||
//let sp_outputs_json = serde_json::to_string(&sp_outputs)?;
|
|
||||||
|
|
||||||
let res = GenerateSPWallet {
|
|
||||||
sp_client,
|
|
||||||
sp_outputs,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
@ -133,28 +146,28 @@ pub fn create_user(
|
|||||||
birthday_signet: u32,
|
birthday_signet: u32,
|
||||||
process: String,
|
process: String,
|
||||||
) -> ApiResult<createUserReturn> {
|
) -> ApiResult<createUserReturn> {
|
||||||
let mut output_list: Vec<OutputList> = Vec::new();
|
|
||||||
//recover
|
//recover
|
||||||
let sp_wallet_recover = generate_sp_wallet(label.clone(), birthday_signet, true)?;
|
let sp_wallet_recover = generate_sp_wallet(label.clone(), birthday_signet, true)?;
|
||||||
output_list.push(sp_wallet_recover.sp_outputs);
|
|
||||||
//revoke
|
//revoke
|
||||||
let sp_wallet_revoke = generate_sp_wallet(label.clone(), birthday_signet, true)?;
|
let sp_wallet_revoke = generate_sp_wallet(label.clone(), birthday_signet, true)?;
|
||||||
output_list.push(sp_wallet_revoke.sp_outputs);
|
|
||||||
//mainet
|
//mainet
|
||||||
let sp_wallet_main = generate_sp_wallet(label, birthday_main, false)?;
|
let sp_wallet_main = generate_sp_wallet(label, birthday_main, false)?;
|
||||||
output_list.push(sp_wallet_main.sp_outputs);
|
|
||||||
|
|
||||||
let user_keys = UserKeys::new(
|
let user_wallets = UserWallets::new(
|
||||||
Some(sp_wallet_main.sp_client),
|
Some(sp_wallet_main),
|
||||||
sp_wallet_recover.sp_client,
|
sp_wallet_recover,
|
||||||
Some(sp_wallet_revoke.sp_client),
|
Some(sp_wallet_revoke),
|
||||||
);
|
);
|
||||||
|
|
||||||
let user = User::new(user_keys, password, process)?;
|
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 {
|
let generate_user = createUserReturn {
|
||||||
user,
|
user,
|
||||||
output_list_vec: output_list,
|
output_list_vec: outputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(generate_user)
|
Ok(generate_user)
|
||||||
@ -175,7 +188,7 @@ pub fn get_processes() -> ApiResult<get_process_return> {
|
|||||||
for _ in 0..number_managers {
|
for _ in 0..number_managers {
|
||||||
//add sp_client
|
//add sp_client
|
||||||
let sp_wallet = generate_sp_wallet(None, birthday_signet, true)?;
|
let sp_wallet = generate_sp_wallet(None, birthday_signet, true)?;
|
||||||
let sp_address = sp_wallet.sp_client.get_receiving_address();
|
let sp_address = sp_wallet.get_client().get_receiving_address();
|
||||||
members.push(sp_address);
|
members.push(sp_address);
|
||||||
}
|
}
|
||||||
//instances of process
|
//instances of process
|
||||||
@ -235,34 +248,6 @@ pub fn get_processes() -> ApiResult<get_process_return> {
|
|||||||
Ok(get_process_return(data_process))
|
Ok(get_process_return(data_process))
|
||||||
}
|
}
|
||||||
|
|
||||||
//for testing
|
|
||||||
/*#[wasm_bindgen]
|
|
||||||
pub fn get_process() -> ApiResult<get_process_return>{
|
|
||||||
|
|
||||||
let number_managers: u8 = 5;
|
|
||||||
let number_users:u8 = 10;
|
|
||||||
let birthday_signet = 50000;
|
|
||||||
let mut members: Vec<ItemMember> = Vec::with_capacity((number_managers + number_users) as usize);
|
|
||||||
|
|
||||||
for _ in 0..number_managers{
|
|
||||||
//add sp_client
|
|
||||||
let sp_wallet = generate_sp_wallet(None, birthday_signet, true)?;
|
|
||||||
let sp_address: SilentPaymentAddress = sp_wallet.sp_client.get_receiving_address().try_into()?;
|
|
||||||
members.push(ItemMember::new(Role::Manager,sp_address, String::from("")));
|
|
||||||
}
|
|
||||||
for _ in 0..number_users{
|
|
||||||
let sp_wallet = generate_sp_wallet(None, birthday_signet, true)?;
|
|
||||||
let sp_address: SilentPaymentAddress = sp_wallet.sp_client.get_receiving_address().try_into()?;
|
|
||||||
members.push(ItemMember::new(Role::User, sp_address, String::from("")));
|
|
||||||
}
|
|
||||||
|
|
||||||
let process = Process::new(members);
|
|
||||||
let mut data_process: Vec<Process> = Vec::new();
|
|
||||||
data_process.push(process);
|
|
||||||
Ok(get_process_return(data_process))
|
|
||||||
|
|
||||||
}*/
|
|
||||||
|
|
||||||
#[derive(Debug, Tsify, Serialize, Deserialize)]
|
#[derive(Debug, Tsify, Serialize, Deserialize)]
|
||||||
#[tsify(from_wasm_abi)]
|
#[tsify(from_wasm_abi)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
@ -285,14 +270,84 @@ impl shamir_shares {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Tsify, Serialize, Deserialize)]
|
||||||
|
#[tsify(from_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]
|
#[wasm_bindgen]
|
||||||
pub fn login_user(
|
pub fn login_user(
|
||||||
user_password: String,
|
user_password: String,
|
||||||
pre_id: String,
|
pre_id: String,
|
||||||
recover: recover_data,
|
recover: recover_data,
|
||||||
shares: shamir_shares,
|
shares: shamir_shares,
|
||||||
|
outputs: outputs_list,
|
||||||
) -> ApiResult<()> {
|
) -> ApiResult<()> {
|
||||||
let res = User::login(pre_id, user_password, recover.as_inner(), shares.as_inner())?;
|
let res = User::login(
|
||||||
|
pre_id,
|
||||||
|
user_password,
|
||||||
|
recover.as_inner(),
|
||||||
|
shares.as_inner(),
|
||||||
|
outputs.as_inner(),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn check_transaction_for_silent_payments(
|
||||||
|
tx_hex: String,
|
||||||
|
tweak_data_hex: String,
|
||||||
|
) -> ApiResult<()> {
|
||||||
|
let tx = deserialize::<Transaction>(&Vec::from_hex(&tx_hex)?)?;
|
||||||
|
let tweak_data = PublicKey::from_str(&tweak_data_hex)?;
|
||||||
|
|
||||||
|
check_transaction(tx, tweak_data);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn parse_bitcoin_network_msg(msg: Vec<u8>) -> ApiResult<()> {
|
||||||
|
let parsed_msg = BitcoinNetworkMsg::new(&msg)?;
|
||||||
|
|
||||||
|
match parsed_msg.topic {
|
||||||
|
BitcoinTopic::RawTx => {
|
||||||
|
let tx = deserialize::<Transaction>(parsed_msg.data)?;
|
||||||
|
let tweak_data = PublicKey::from_slice(parsed_msg.addon)?;
|
||||||
|
check_transaction(tx, tweak_data);
|
||||||
|
}
|
||||||
|
BitcoinTopic::RawBlock => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,24 @@
|
|||||||
#![allow(warnings)]
|
#![allow(warnings)]
|
||||||
|
use anyhow::Error;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::sync::{Mutex, MutexGuard};
|
||||||
|
|
||||||
mod Prd_list;
|
mod Prd_list;
|
||||||
pub mod api;
|
pub mod api;
|
||||||
mod crypto;
|
mod crypto;
|
||||||
|
mod network;
|
||||||
mod peers;
|
mod peers;
|
||||||
mod process;
|
mod process;
|
||||||
|
mod silentpayments;
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
|
pub(crate) trait MutexExt<T> {
|
||||||
|
fn lock_anyhow(&self) -> Result<MutexGuard<T>, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Debug> MutexExt<T> for Mutex<T> {
|
||||||
|
fn lock_anyhow(&self) -> Result<MutexGuard<T>, Error> {
|
||||||
|
self.lock()
|
||||||
|
.map_err(|e| Error::msg(format!("Failed to lock: {}", e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
94
crates/sp_client/src/network.rs
Normal file
94
crates/sp_client/src/network.rs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
use anyhow::{Error, Result};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tsify::Tsify;
|
||||||
|
|
||||||
|
const RAWTXTOPIC: &'static str = "rawtx";
|
||||||
|
const RAWBLOCKTOPIC: &'static str = "rawblock";
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum BitcoinTopic {
|
||||||
|
RawTx,
|
||||||
|
RawBlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BitcoinTopic {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::RawTx => RAWTXTOPIC,
|
||||||
|
Self::RawBlock => RAWBLOCKTOPIC,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Tsify)]
|
||||||
|
#[tsify(from_wasm_abi, into_wasm_abi)]
|
||||||
|
pub struct BitcoinNetworkMsg<'a> {
|
||||||
|
pub topic: BitcoinTopic,
|
||||||
|
pub data: &'a [u8],
|
||||||
|
pub sequence: &'a [u8],
|
||||||
|
pub addon: &'a [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BitcoinNetworkMsg<'a> {
|
||||||
|
pub fn new(raw_msg: &'a [u8]) -> Result<Self> {
|
||||||
|
let topic: BitcoinTopic;
|
||||||
|
let data: &[u8];
|
||||||
|
let sequence: &[u8];
|
||||||
|
let addon: &[u8];
|
||||||
|
let addon_len: usize;
|
||||||
|
let raw_msg_len = raw_msg.len();
|
||||||
|
|
||||||
|
if raw_msg.starts_with(RAWTXTOPIC.as_bytes()) {
|
||||||
|
topic = BitcoinTopic::RawTx;
|
||||||
|
addon_len = 33;
|
||||||
|
} else if raw_msg.starts_with(RAWBLOCKTOPIC.as_bytes()) {
|
||||||
|
topic = BitcoinTopic::RawBlock;
|
||||||
|
addon_len = 0;
|
||||||
|
} else {
|
||||||
|
return Err(Error::msg("Unknown prefix"));
|
||||||
|
}
|
||||||
|
|
||||||
|
data = &raw_msg[topic.as_str().as_bytes().len()..raw_msg_len - 4 - addon_len];
|
||||||
|
sequence = &raw_msg[raw_msg_len - 4 - addon_len..];
|
||||||
|
addon = &raw_msg[raw_msg_len - addon_len..];
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
topic,
|
||||||
|
data,
|
||||||
|
sequence,
|
||||||
|
addon,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AnkTopic {
|
||||||
|
Faucet,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnkTopic {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Faucet => "faucet",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AnkNetworkMsg<'a> {
|
||||||
|
pub topic: AnkTopic,
|
||||||
|
pub content: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AnkNetworkMsg<'a> {
|
||||||
|
pub fn new(raw: &'a str) -> Result<Self> {
|
||||||
|
if raw.starts_with(AnkTopic::Faucet.as_str()) {
|
||||||
|
Ok(Self {
|
||||||
|
topic: AnkTopic::Faucet,
|
||||||
|
content: &raw[AnkTopic::Faucet.as_str().len()..],
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Error::msg("Unknown 4nk message"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
76
crates/sp_client/src/silentpayments.rs
Normal file
76
crates/sp_client/src/silentpayments.rs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use sp_backend::silentpayments::utils::receiving::calculate_shared_secret;
|
||||||
|
use sp_backend::{
|
||||||
|
bitcoin::{
|
||||||
|
secp256k1::{PublicKey, Scalar, XOnlyPublicKey},
|
||||||
|
Transaction,
|
||||||
|
},
|
||||||
|
silentpayments::receiving::Label,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::user::{lock_connected_users, CONNECTED_USERS};
|
||||||
|
|
||||||
|
type FoundOutputs = HashMap<Option<Label>, HashMap<XOnlyPublicKey, Scalar>>;
|
||||||
|
|
||||||
|
pub fn check_transaction(tx: Transaction, tweak_data: PublicKey) -> Result<FoundOutputs> {
|
||||||
|
let connected_users = lock_connected_users()?;
|
||||||
|
|
||||||
|
let pubkeys_to_check: HashMap<XOnlyPublicKey, u32> = (0u32..)
|
||||||
|
.zip(tx.output)
|
||||||
|
.filter_map(|(i, o)| {
|
||||||
|
if o.script_pubkey.is_p2tr() {
|
||||||
|
let xonly = XOnlyPublicKey::from_slice(&o.script_pubkey.as_bytes()[2..])
|
||||||
|
.expect("Transaction is invalid");
|
||||||
|
Some((xonly, i))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Check the transaction for all connected users
|
||||||
|
for (pre_id, keys) in connected_users.clone() {
|
||||||
|
let recover = keys.recover;
|
||||||
|
let shared_secret =
|
||||||
|
calculate_shared_secret(tweak_data, recover.get_client().get_scan_key())?;
|
||||||
|
let res = recover
|
||||||
|
.get_client()
|
||||||
|
.sp_receiver
|
||||||
|
.scan_transaction(&shared_secret, pubkeys_to_check.keys().cloned().collect())?;
|
||||||
|
|
||||||
|
if res.len() > 0 {
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(main) = keys.main {
|
||||||
|
let shared_secret =
|
||||||
|
calculate_shared_secret(tweak_data, main.get_client().get_scan_key())?;
|
||||||
|
let res = main
|
||||||
|
.get_client()
|
||||||
|
.sp_receiver
|
||||||
|
.scan_transaction(&shared_secret, pubkeys_to_check.keys().cloned().collect())?;
|
||||||
|
|
||||||
|
if res.len() > 0 {
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(revoke) = keys.revoke {
|
||||||
|
let shared_secret =
|
||||||
|
calculate_shared_secret(tweak_data, revoke.get_client().get_scan_key())?;
|
||||||
|
let res = revoke
|
||||||
|
.get_client()
|
||||||
|
.sp_receiver
|
||||||
|
.scan_transaction(&shared_secret, pubkeys_to_check.keys().cloned().collect())?;
|
||||||
|
|
||||||
|
if res.len() > 0 {
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HashMap::new())
|
||||||
|
}
|
@ -12,6 +12,7 @@ use sp_backend::bitcoin::hashes::HashEngine;
|
|||||||
use sp_backend::bitcoin::hex::{DisplayHex, FromHex};
|
use sp_backend::bitcoin::hex::{DisplayHex, FromHex};
|
||||||
use sp_backend::bitcoin::secp256k1::SecretKey;
|
use sp_backend::bitcoin::secp256k1::SecretKey;
|
||||||
use sp_backend::bitcoin::secp256k1::ThirtyTwoByteHash;
|
use sp_backend::bitcoin::secp256k1::ThirtyTwoByteHash;
|
||||||
|
use sp_backend::spclient::SpClient;
|
||||||
use tsify::Tsify;
|
use tsify::Tsify;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
@ -20,34 +21,42 @@ use std::collections::HashMap;
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Cursor, Read, Write};
|
use std::io::{Cursor, Read, Write};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Mutex, OnceLock};
|
use std::sync::{Mutex, MutexGuard, OnceLock};
|
||||||
|
|
||||||
use sp_backend::bitcoin::secp256k1::constants::SECRET_KEY_SIZE;
|
use sp_backend::bitcoin::secp256k1::constants::SECRET_KEY_SIZE;
|
||||||
use sp_backend::silentpayments::bitcoin_hashes::sha256;
|
use sp_backend::silentpayments::bitcoin_hashes::sha256;
|
||||||
use sp_backend::silentpayments::sending::SilentPaymentAddress;
|
use sp_backend::silentpayments::sending::SilentPaymentAddress;
|
||||||
use sp_backend::spclient::SpendKey;
|
use sp_backend::spclient::SpendKey;
|
||||||
use sp_backend::spclient::{OutputList, SpClient};
|
use sp_backend::spclient::{OutputList, SpWallet};
|
||||||
|
|
||||||
use crate::crypto::{Aes256Decryption, Aes256Encryption, HalfKey, Purpose};
|
use crate::crypto::{Aes256Decryption, Aes256Encryption, HalfKey, Purpose};
|
||||||
use crate::peers::Peer;
|
use crate::peers::Peer;
|
||||||
use crate::user;
|
use crate::user;
|
||||||
|
use crate::MutexExt;
|
||||||
|
|
||||||
type PreId = String;
|
type PreId = String;
|
||||||
|
|
||||||
const MANAGERS_NUMBER: u8 = 10;
|
const MANAGERS_NUMBER: u8 = 10;
|
||||||
const QUORUM_SHARD: f32 = 0.8;
|
const QUORUM_SHARD: f32 = 0.8;
|
||||||
|
|
||||||
pub static CONNECTED_USERS: OnceLock<Mutex<HashMap<PreId, UserKeys>>> = OnceLock::new();
|
type UsersMap = HashMap<PreId, UserWallets>;
|
||||||
|
pub static CONNECTED_USERS: OnceLock<Mutex<UsersMap>> = OnceLock::new();
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
pub fn lock_connected_users() -> Result<MutexGuard<'static, UsersMap>> {
|
||||||
pub struct UserKeys {
|
CONNECTED_USERS
|
||||||
pub main: Option<SpClient>,
|
.get_or_init(|| Mutex::new(HashMap::new()))
|
||||||
pub recover: SpClient,
|
.lock_anyhow()
|
||||||
pub revoke: Option<SpClient>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserKeys {
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub fn new(main: Option<SpClient>, recover: SpClient, revoke: Option<SpClient>) -> Self {
|
pub struct UserWallets {
|
||||||
|
pub main: Option<SpWallet>,
|
||||||
|
pub recover: SpWallet,
|
||||||
|
pub revoke: Option<SpWallet>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserWallets {
|
||||||
|
pub fn new(main: Option<SpWallet>, recover: SpWallet, revoke: Option<SpWallet>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
main,
|
main,
|
||||||
recover,
|
recover,
|
||||||
@ -55,9 +64,22 @@ impl UserKeys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_get_revoke(&self) -> Option<&SpClient> {
|
pub fn try_get_revoke(&self) -> Option<&SpWallet> {
|
||||||
self.revoke.as_ref()
|
self.revoke.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_all_outputs(&self) -> Vec<OutputList> {
|
||||||
|
let mut res = Vec::<OutputList>::new();
|
||||||
|
if let Some(main) = &self.main {
|
||||||
|
res.push(main.get_outputs().clone());
|
||||||
|
}
|
||||||
|
if let Some(revoke) = &self.revoke {
|
||||||
|
res.push(revoke.get_outputs().clone());
|
||||||
|
}
|
||||||
|
res.push(self.recover.get_outputs().clone());
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Tsify)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Tsify)]
|
||||||
@ -69,25 +91,30 @@ pub struct User {
|
|||||||
recover_data: Vec<u8>,
|
recover_data: Vec<u8>,
|
||||||
revoke_data: Option<Vec<u8>>,
|
revoke_data: Option<Vec<u8>>,
|
||||||
shares: Vec<Vec<u8>>,
|
shares: Vec<Vec<u8>>,
|
||||||
|
outputs: Vec<OutputList>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
pub fn new(user_keys: UserKeys, user_password: String, process: String) -> Result<Self> {
|
pub fn new(user_wallets: UserWallets, user_password: String, process: String) -> Result<Self> {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
// image revoke
|
// image revoke
|
||||||
// We just take the 2 revoke keys
|
// We just take the 2 revoke keys
|
||||||
let mut revoke_data = Vec::with_capacity(64);
|
let mut revoke_data = Vec::with_capacity(64);
|
||||||
if let Some(revoke) = user_keys.try_get_revoke() {
|
if let Some(revoke) = user_wallets.try_get_revoke() {
|
||||||
revoke_data.extend_from_slice(revoke.get_scan_key().as_ref());
|
revoke_data.extend_from_slice(revoke.get_client().get_scan_key().as_ref());
|
||||||
revoke_data.extend_from_slice(revoke.try_get_secret_spend_key()?.as_ref());
|
revoke_data.extend_from_slice(revoke.get_client().try_get_secret_spend_key()?.as_ref());
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::msg("No revoke wallet available"));
|
return Err(Error::msg("No revoke wallet available"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take the 2 recover keys
|
// Take the 2 recover keys
|
||||||
// split recover spend key
|
// split recover spend key
|
||||||
let recover_spend_key = user_keys.recover.try_get_secret_spend_key()?.clone();
|
let recover_spend_key = user_wallets
|
||||||
|
.recover
|
||||||
|
.get_client()
|
||||||
|
.try_get_secret_spend_key()?
|
||||||
|
.clone();
|
||||||
let (part1_key, part2_key) = recover_spend_key.as_ref().split_at(SECRET_KEY_SIZE / 2);
|
let (part1_key, part2_key) = recover_spend_key.as_ref().split_at(SECRET_KEY_SIZE / 2);
|
||||||
let mut recover_data = Vec::<u8>::with_capacity(180); // 32 * 3 + (12+16)*3
|
let mut recover_data = Vec::<u8>::with_capacity(180); // 32 * 3 + (12+16)*3
|
||||||
|
|
||||||
@ -161,7 +188,12 @@ impl User {
|
|||||||
|
|
||||||
let scan_key_encryption = Aes256Encryption::import_key(
|
let scan_key_encryption = Aes256Encryption::import_key(
|
||||||
Purpose::ThirtyTwoBytes,
|
Purpose::ThirtyTwoBytes,
|
||||||
user_keys.recover.get_scan_key().secret_bytes().to_vec(),
|
user_wallets
|
||||||
|
.recover
|
||||||
|
.get_client()
|
||||||
|
.get_scan_key()
|
||||||
|
.secret_bytes()
|
||||||
|
.to_vec(),
|
||||||
hash3.to_byte_array(),
|
hash3.to_byte_array(),
|
||||||
Aes256Gcm::generate_nonce(&mut rng).into(),
|
Aes256Gcm::generate_nonce(&mut rng).into(),
|
||||||
)?;
|
)?;
|
||||||
@ -171,12 +203,6 @@ impl User {
|
|||||||
|
|
||||||
recover_data.extend_from_slice(&cipher_scan_key);
|
recover_data.extend_from_slice(&cipher_scan_key);
|
||||||
|
|
||||||
//Create PRDList
|
|
||||||
//@todo
|
|
||||||
//Send messages PRDList
|
|
||||||
//@todo
|
|
||||||
//Receive List Items (PCD)
|
|
||||||
|
|
||||||
Ok(User {
|
Ok(User {
|
||||||
pre_id: pre_id.to_string(),
|
pre_id: pre_id.to_string(),
|
||||||
processes: vec![process],
|
processes: vec![process],
|
||||||
@ -184,6 +210,7 @@ impl User {
|
|||||||
recover_data,
|
recover_data,
|
||||||
revoke_data: Some(revoke_data),
|
revoke_data: Some(revoke_data),
|
||||||
shares,
|
shares,
|
||||||
|
outputs: user_wallets.get_all_outputs(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,6 +219,7 @@ impl User {
|
|||||||
user_password: String,
|
user_password: String,
|
||||||
recover_data: &[u8],
|
recover_data: &[u8],
|
||||||
shares: &[Vec<u8>],
|
shares: &[Vec<u8>],
|
||||||
|
outputs: &[OutputList],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut retrieved_spend_key = [0u8; 32];
|
let mut retrieved_spend_key = [0u8; 32];
|
||||||
let mut retrieved_scan_key = [0u8; 32];
|
let mut retrieved_scan_key = [0u8; 32];
|
||||||
@ -258,6 +286,13 @@ impl User {
|
|||||||
true,
|
true,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let recover_outputs = outputs
|
||||||
|
.iter()
|
||||||
|
.find(|o| o.check_fingerprint(&recover_client))
|
||||||
|
.cloned();
|
||||||
|
|
||||||
|
let recover_wallet = SpWallet::new(recover_client, recover_outputs)?;
|
||||||
|
|
||||||
// Adding user to CONNECTED_USERS
|
// Adding user to CONNECTED_USERS
|
||||||
if let Some(current_users) = CONNECTED_USERS.get() {
|
if let Some(current_users) = CONNECTED_USERS.get() {
|
||||||
let mut lock = current_users.to_owned().lock().unwrap();
|
let mut lock = current_users.to_owned().lock().unwrap();
|
||||||
@ -267,11 +302,11 @@ impl User {
|
|||||||
pre_id
|
pre_id
|
||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
lock.insert(pre_id.clone(), UserKeys::new(None, recover_client, None));
|
lock.insert(pre_id.clone(), UserWallets::new(None, recover_wallet, None));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut user_map = HashMap::new();
|
let mut user_map = HashMap::new();
|
||||||
user_map.insert(pre_id, UserKeys::new(None, recover_client, None));
|
user_map.insert(pre_id, UserWallets::new(None, recover_wallet, None));
|
||||||
let new_value = Mutex::new(user_map);
|
let new_value = Mutex::new(user_map);
|
||||||
if let Err(error) = CONNECTED_USERS.set(new_value) {
|
if let Err(error) = CONNECTED_USERS.set(new_value) {
|
||||||
return Err(Error::msg(
|
return Err(Error::msg(
|
||||||
@ -396,7 +431,7 @@ mod tests {
|
|||||||
const USER_PASSWORD: &str = "correct horse battery staple";
|
const USER_PASSWORD: &str = "correct horse battery staple";
|
||||||
const PROCESS: &str = "example";
|
const PROCESS: &str = "example";
|
||||||
|
|
||||||
fn helper_create_user_keys() -> UserKeys {
|
fn helper_create_user_wallets() -> UserWallets {
|
||||||
let label = "default".to_owned();
|
let label = "default".to_owned();
|
||||||
let sp_main = SpClient::new(
|
let sp_main = SpClient::new(
|
||||||
label.clone(),
|
label.clone(),
|
||||||
@ -422,16 +457,20 @@ mod tests {
|
|||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let user_keys = UserKeys::new(Some(sp_main), sp_recover, Some(sp_revoke));
|
let user_wallets = UserWallets::new(
|
||||||
|
Some(SpWallet::new(sp_main, None).unwrap()),
|
||||||
|
SpWallet::new(sp_recover, None).unwrap(),
|
||||||
|
Some(SpWallet::new(sp_revoke, None).unwrap()),
|
||||||
|
);
|
||||||
|
|
||||||
user_keys
|
user_wallets
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test 1: Create User
|
// Test 1: Create User
|
||||||
#[test]
|
#[test]
|
||||||
fn test_successful_creation() {
|
fn test_successful_creation() {
|
||||||
let user_keys = helper_create_user_keys();
|
let user_wallets = helper_create_user_wallets();
|
||||||
let result = User::new(user_keys, USER_PASSWORD.to_owned(), PROCESS.to_owned());
|
let result = User::new(user_wallets, USER_PASSWORD.to_owned(), PROCESS.to_owned());
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
let user = result.unwrap();
|
let user = result.unwrap();
|
||||||
@ -439,14 +478,20 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_login() {
|
fn test_login() {
|
||||||
let user_keys = helper_create_user_keys();
|
let user_wallets = helper_create_user_wallets();
|
||||||
let user = User::new(user_keys, USER_PASSWORD.to_owned(), PROCESS.to_owned()).unwrap();
|
let user = User::new(
|
||||||
|
user_wallets.clone(),
|
||||||
|
USER_PASSWORD.to_owned(),
|
||||||
|
PROCESS.to_owned(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let res = User::login(
|
let res = User::login(
|
||||||
user.pre_id.clone(),
|
user.pre_id.clone(),
|
||||||
USER_PASSWORD.to_owned(),
|
USER_PASSWORD.to_owned(),
|
||||||
&user.recover_data,
|
&user.recover_data,
|
||||||
&user.shares,
|
&user.shares,
|
||||||
|
&user_wallets.get_all_outputs(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
@ -458,7 +503,11 @@ mod tests {
|
|||||||
assert!(
|
assert!(
|
||||||
format!(
|
format!(
|
||||||
"{}",
|
"{}",
|
||||||
recover.try_get_secret_spend_key().unwrap().display_secret()
|
recover
|
||||||
|
.get_client()
|
||||||
|
.try_get_secret_spend_key()
|
||||||
|
.unwrap()
|
||||||
|
.display_secret()
|
||||||
) == RECOVER_SPEND
|
) == RECOVER_SPEND
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Services from './services';
|
import Services from './services';
|
||||||
import { WebSocketClient } from './websockets';
|
import { WebSocketClient } from './websockets';
|
||||||
|
|
||||||
const wsurl = "ws://127.0.0.1:8090";
|
const wsurl = "ws://192.168.1.44:8090";
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
try {
|
try {
|
||||||
const services = await Services.getInstance();
|
const services = await Services.getInstance();
|
||||||
|
@ -93,21 +93,42 @@ class Services {
|
|||||||
// To comment if test
|
// To comment if test
|
||||||
// if (!Services.instance.isPasswordValid(password)) return;
|
// if (!Services.instance.isPasswordValid(password)) return;
|
||||||
|
|
||||||
let label = null;
|
const label = null;
|
||||||
let birthday_signet = 50000;
|
const birthday_signet = 50000;
|
||||||
let birthday_main = 500000;
|
const birthday_main = 500000;
|
||||||
const user: createUserReturn = this.sdkClient.create_user(password, label, birthday_main, birthday_signet, this.current_process);
|
|
||||||
|
const services = await Services.getInstance();
|
||||||
|
let createUserReturn: createUserReturn = services.sdkClient.create_user(password, label, birthday_main, birthday_signet, this.current_process);
|
||||||
|
|
||||||
|
let user = createUserReturn.user;
|
||||||
|
const outputs_list = createUserReturn.output_list_vec;
|
||||||
|
|
||||||
|
const shares = user.shares;
|
||||||
|
const revokeData = user.revoke_data;
|
||||||
|
|
||||||
|
user.shares = [];
|
||||||
|
user.revoke_data = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const indexedDb = await IndexedDB.getInstance();
|
const indexedDb = await IndexedDB.getInstance();
|
||||||
const db = indexedDb.getDb();
|
const db = indexedDb.getDb();
|
||||||
await indexedDb.writeObject(db, indexedDb.getStoreList().AnkUser, user.user, null);
|
await indexedDb.writeObject(db, indexedDb.getStoreList().AnkUser, user, null);
|
||||||
await indexedDb.writeObject(db, indexedDb.getStoreList().SpOutputs, user.output_list_vec, null);
|
await indexedDb.writeObject(db, indexedDb.getStoreList().SpOutputs, outputs_list, null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to write user object :", error);
|
console.error("Failed to write user object :", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Services.instance.displayRevokeImage();
|
let sp_address = "";
|
||||||
|
try {
|
||||||
|
sp_address = services.sdkClient.get_receiving_address(user.pre_id);
|
||||||
|
console.info('Using sp_address:', sp_address);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
await services.obtainTokenWithFaucet(sp_address);
|
||||||
|
|
||||||
|
await services.displayRevokeImage(new Uint8Array(revokeData));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async displayRecover(): Promise<void> {
|
public async displayRecover(): Promise<void> {
|
||||||
@ -136,7 +157,7 @@ class Services {
|
|||||||
const process = processElement.value;
|
const process = processElement.value;
|
||||||
console.log("JS password: " + password + " process: " + process);
|
console.log("JS password: " + password + " process: " + process);
|
||||||
// To comment if test
|
// To comment if test
|
||||||
if (!Services.instance.isPasswordValid(password)) return;
|
// if (!Services.instance.isPasswordValid(password)) return;
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
alert("Recover submit to do ...");
|
alert("Recover submit to do ...");
|
||||||
@ -439,11 +460,28 @@ class Services {
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public obtainTokenWithFaucet(wsclient: WebSocketClient, spaddress: string): string | null {
|
private async pickWebsocketConnectionRandom(): Promise<WebSocketClient | null> {
|
||||||
|
const services = await Services.getInstance();
|
||||||
|
const websockets = services.websocketConnection;
|
||||||
|
if (websockets.length === 0) {
|
||||||
|
console.error("No websocket connection available at the moment");
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
const random = Math.floor(Math.random() * websockets.length);
|
||||||
|
return websockets[random];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async obtainTokenWithFaucet(spaddress: string): Promise<string | null> {
|
||||||
|
const services = await Services.getInstance();
|
||||||
|
const connection = await services.pickWebsocketConnectionRandom();
|
||||||
|
if (!connection) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
wsclient.sendMessage(spaddress);
|
connection.sendMessage('faucet'+spaddress);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to obtain tokens with relay ", wsclient.getUrl());
|
console.error("Failed to obtain tokens with relay ", connection.getUrl());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user