Merge branch 'tauri-wallet' into tauri-wallet-frontend

rust updates
This commit is contained in:
fmtabbara
2021-09-07 21:53:38 +01:00
12 changed files with 429 additions and 143 deletions
Generated
+3
View File
@@ -3612,9 +3612,11 @@ name = "nym_wallet"
version = "0.1.0"
dependencies = [
"bip39",
"coconut-interface",
"config",
"cosmos_sdk",
"cosmwasm-std",
"credentials",
"dirs",
"mixnet-contract",
"serde",
@@ -3625,6 +3627,7 @@ dependencies = [
"thiserror",
"tokio",
"ts-rs",
"url",
"validator-client",
]
@@ -5,6 +5,7 @@ use crate::nymd::GasPrice;
use cosmos_sdk::tx::{Fee, Gas};
use cosmos_sdk::Coin;
use cosmwasm_std::Uint128;
use std::fmt;
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum Operation {
@@ -37,6 +38,27 @@ pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
}
}
impl fmt::Display for Operation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Operation::Upload => f.write_str("Upload"),
Operation::Init => f.write_str("Init"),
Operation::Migrate => f.write_str("Migrate"),
Operation::ChangeAdmin => f.write_str("ChangeAdmin"),
Operation::Send => f.write_str("Send"),
Operation::BondMixnode => f.write_str("BondMixnode"),
Operation::UnbondMixnode => f.write_str("UnbondMixnode"),
Operation::DelegateToMixnode => f.write_str("DelegateToMixnode"),
Operation::UndelegateFromMixnode => f.write_str("UndelegateFromMixnode"),
Operation::BondGateway => f.write_str("BondGateway"),
Operation::UnbondGateway => f.write_str("UnbondGateway"),
Operation::DelegateToGateway => f.write_str("DelegateToGateway"),
Operation::UndelegateFromGateway => f.write_str("UndelegateFromGateway"),
Operation::UpdateStateParams => f.write_str("UpdateStateParams"),
}
}
}
impl Operation {
// TODO: some value tweaking
pub(crate) fn default_gas_limit(&self) -> Gas {
@@ -122,6 +122,14 @@ impl<C> NymdClient<C> {
self.custom_gas_limits.insert(operation, limit);
}
pub fn get_gas_price(&self) -> GasPrice {
self.gas_price.clone()
}
pub fn get_custom_gas_limits(&self) -> HashMap<Operation, Gas> {
self.custom_gas_limits.clone()
}
pub fn contract_address(&self) -> Result<&AccountId, NymdError> {
self.contract_address
.as_ref()
+1 -1
View File
@@ -3,7 +3,7 @@
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ValidatorDetails {
// it is assumed those values are always valid since they're being provided in our defaults file
pub nymd_url: String,
+3
View File
@@ -25,6 +25,7 @@ bip39 = "1.0"
thiserror = "1.0"
tendermint-rpc = "0.21.0"
ts-rs = "3.0"
url = "2.0"
cosmos_sdk = { git = "https://github.com/cosmos/cosmos-rust/", rev = "ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30", features = [
"rpc",
@@ -38,6 +39,8 @@ validator-client = { path = "../../common/client-libs/validator-client", feature
] }
mixnet-contract = { path = "../../common/mixnet-contract" }
config = { path = "../../common/config" }
coconut-interface = { path = "../../common/coconut-interface" }
credentials = { path = "../../common/credentials" }
[features]
default = ["custom-protocol"]
+143
View File
@@ -0,0 +1,143 @@
use std::sync::Arc;
use tokio::sync::RwLock;
use coconut_interface::{
self, Credential, Signature, Theta, VerificationKey,
};
use crate::state::State;
use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key};
use url::Url;
#[tauri::command]
pub async fn randomise_credential(
idx: usize,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<Signature>, String> {
let mut state = state.write().await;
let signature = state.signatures.remove(idx);
let new = signature.randomise(state.params()?);
state.signatures.insert(idx, new);
Ok(state.signatures.clone())
}
#[tauri::command]
pub async fn delete_credential(
idx: usize,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<Signature>, String> {
let mut state = state.write().await;
state.signatures.remove(idx);
Ok(state.signatures.clone())
}
#[tauri::command]
pub async fn list_credentials(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<Signature>, String> {
let state = state.read().await;
Ok(state.signatures.clone())
}
#[tauri::command]
pub async fn verify_credential(
idx: usize,
validator_urls: Vec<String>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<bool, String> {
// the API needs to be improved but at least it should compile (in theory)
let verification_key =
get_aggregated_verification_key(validator_urls.clone(), state.clone()).await?;
let theta = prove_credential(idx, validator_urls, state.clone()).await?;
let state = state.read().await;
let credential = Credential::new(
state.n_attributes(),
theta,
&state.public_attributes(),
state
.signatures
.get(idx)
.ok_or("Got invalid signature idx")?,
);
Ok(credential.verify(&verification_key).await)
}
async fn prove_credential(
idx: usize,
validator_urls: Vec<String>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Theta, String> {
let verification_key = get_aggregated_verification_key(validator_urls, state.clone()).await?;
let state = state.read().await;
if let Some(signature) = state.signatures.get(idx) {
match coconut_interface::prove_credential(
state.params()?,
&verification_key,
signature,
&state.private_attributes(),
) {
Ok(theta) => Ok(theta),
Err(e) => Err(format!("{}", e)),
}
} else {
Err("Got invalid Signature idx".to_string())
}
}
async fn get_aggregated_verification_key(
validator_urls: Vec<String>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<VerificationKey, String> {
if let Some(verification_key) = &state.read().await.aggregated_verification_key {
return Ok(verification_key.clone());
}
let parsed_urls = parse_url_validators(&validator_urls)?;
let key = obtain_aggregate_verification_key(&parsed_urls)
.await
.map_err(|err| format!("failed to obtain aggregate verification key - {:?}", err))?;
state
.write()
.await
.aggregated_verification_key
.replace(key.clone());
Ok(key)
}
fn parse_url_validators(raw: &[String]) -> Result<Vec<Url>, String> {
let mut parsed_urls = Vec::with_capacity(raw.len());
for url in raw {
let parsed_url: Url = url
.parse()
.map_err(|err| format!("one of validator urls is malformed - {}", err))?;
parsed_urls.push(parsed_url)
}
Ok(parsed_urls)
}
#[tauri::command]
pub async fn get_credential(
validator_urls: Vec<String>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<Signature>, String> {
let guard = state.read().await;
let parsed_urls = parse_url_validators(&validator_urls)?;
let signature = obtain_aggregate_signature(
guard.params()?,
&guard.public_attributes(),
&guard.private_attributes(),
&parsed_urls,
)
.await
.map_err(|err| format!("failed to obtain aggregate signature - {:?}", err))?;
let mut state = state.write().await;
state.signatures.push(signature);
Ok(state.signatures.clone())
}
+2 -2
View File
@@ -14,13 +14,13 @@ use template::config_template;
use crate::error::BackendError;
#[derive(Debug, Default, Deserialize, Serialize)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct Config {
base: Base,
}
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct Base {
validators: Vec<ValidatorDetails>,
+160 -140
View File
@@ -12,22 +12,30 @@ use cosmwasm_std::Coin as CosmWasmCoin;
use error::BackendError;
use mixnet_contract::{Gateway, MixNode};
use serde::{Deserialize, Serialize};
use tendermint_rpc::endpoint::broadcast::tx_commit::Response;
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::fmt;
use std::str::FromStr;
use ts_rs::{export, TS};
use validator_client::nymd::{NymdClient, SigningNymdClient};
mod config;
mod error;
// mod nymd_client;
use std::sync::Arc;
use tokio::sync::RwLock;
use ts_rs::{export, TS};
use validator_client::nymd::GasPrice;
use validator_client::nymd::{NymdClient, SigningNymdClient};
mod coconut;
mod config;
mod error;
mod state;
use crate::coconut::{
delete_credential, get_credential, list_credentials, randomise_credential, verify_credential,
};
use crate::state::State;
use crate::config::Config;
#[macro_export]
macro_rules! format_err {
($e:expr) => {
format!("line {}: {}", line!(), $e)
@@ -71,6 +79,36 @@ impl FromStr for Denom {
}
}
#[derive(Deserialize, Serialize, TS)]
struct TauriTxResult {
code: u32,
gas_wanted: u64,
gas_used: u64,
block_height: u64,
details: TransactionDetails,
}
#[derive(Deserialize, Serialize, TS)]
struct TransactionDetails {
from_address: String,
to_address: String,
amount: Coin
}
impl TauriTxResult {
fn new(t: Response, details: TransactionDetails) -> TauriTxResult {
TauriTxResult {
code: t.check_tx.code.value(),
gas_wanted: t.check_tx.gas_wanted.value(),
gas_used: t.check_tx.gas_used.value(),
block_height: t.height.value(),
details
}
}
}
// Proxy types to allow TS generation
#[derive(TS, Serialize, Deserialize, Clone)]
struct Coin {
@@ -78,6 +116,15 @@ struct Coin {
denom: String,
}
impl From<GasPrice> for Coin {
fn from(g: GasPrice) -> Coin {
Coin {
amount: g.amount.to_string(),
denom: g.denom.to_string(),
}
}
}
impl fmt::Display for Coin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&format!("{} {}", self.amount, self.denom))
@@ -153,12 +200,6 @@ impl From<CosmosCoin> for Coin {
}
}
#[derive(Debug, Default)]
struct State {
config: Config,
signing_client: Option<NymdClient<SigningNymdClient>>,
}
#[tauri::command]
fn major_to_minor(amount: String) -> Result<Coin, String> {
let coin = Coin {
@@ -189,7 +230,7 @@ async fn connect_with_mnemonic(
let client;
{
let r_state = state.read().await;
client = _connect_with_mnemonic(mnemonic, &r_state.config);
client = _connect_with_mnemonic(mnemonic, &r_state.config());
}
let mut ret = HashMap::new();
@@ -209,7 +250,7 @@ async fn connect_with_mnemonic(
},
);
let mut w_state = state.write().await;
w_state.signing_client = Some(client);
w_state.set_client(client);
Ok(ret)
}
@@ -217,73 +258,53 @@ async fn connect_with_mnemonic(
#[tauri::command]
async fn get_balance(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<Balance, String> {
let r_state = state.read().await;
if let Some(client) = &r_state.signing_client {
match client.get_balance(client.address()).await {
Ok(Some(coin)) => {
let coin = Coin {
amount: coin.amount.to_string(),
denom: coin.denom.to_string(),
};
Ok(Balance {
coin: coin.clone(),
printable_balance: coin.to_major().to_string(),
})
}
Ok(None) => Err(format!(
"No balance available for address {}",
client.address()
)),
Err(e) => Err(BackendError::from(e).to_string()),
let client = r_state.client()?;
match client.get_balance(client.address()).await {
Ok(Some(coin)) => {
let coin = Coin {
amount: coin.amount.to_string(),
denom: coin.denom.to_string(),
};
Ok(Balance {
coin: coin.clone(),
printable_balance: coin.to_major().to_string(),
})
}
} else {
Err(String::from(
"Client has not been initialized yet, connect with mnemonic to initialize",
))
Ok(None) => Err(format!(
"No balance available for address {}",
client.address()
)),
Err(e) => Err(BackendError::from(e).to_string()),
}
}
#[tauri::command]
async fn owns_mixnode(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<bool, String> {
let r_state = state.read().await;
if let Some(client) = &r_state.signing_client {
match client.owns_mixnode(client.address()).await {
Ok(o) => Ok(o),
Err(e) => Err(format_err!(e)),
}
} else {
Err(String::from(
"Client has not been initialized yet, connect with mnemonic to initialize",
))
let client = r_state.client()?;
match client.owns_mixnode(client.address()).await {
Ok(o) => Ok(o),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
async fn owns_gateway(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<bool, String> {
let r_state = state.read().await;
if let Some(client) = &r_state.signing_client {
match client.owns_gateway(client.address()).await {
Ok(o) => Ok(o),
Err(e) => Err(format_err!(e)),
}
} else {
Err(String::from(
"Client has not been initialized yet, connect with mnemonic to initialize",
))
let client = r_state.client()?;
match client.owns_gateway(client.address()).await {
Ok(o) => Ok(o),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
async fn unbond_mixnode(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<(), String> {
let r_state = state.read().await;
if let Some(client) = &r_state.signing_client {
match client.unbond_mixnode().await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
} else {
Err(String::from(
"Client has not been initialized yet, connect with mnemonic to initialize",
))
let client = r_state.client()?;
match client.unbond_mixnode().await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
@@ -298,15 +319,10 @@ async fn bond_mixnode(
Ok(b) => b,
Err(e) => return Err(format_err!(e)),
};
if let Some(client) = &r_state.signing_client {
match client.bond_mixnode(mixnode, bond).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
} else {
Err(String::from(
"Client has not been initialized yet, connect with mnemonic to initialize",
))
let client = r_state.client()?;
match client.bond_mixnode(mixnode, bond).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
@@ -321,15 +337,10 @@ async fn delegate_to_mixnode(
Ok(b) => b,
Err(e) => return Err(format_err!(e)),
};
if let Some(client) = &r_state.signing_client {
match client.delegate_to_mixnode(identity, bond).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
} else {
Err(String::from(
"Client has not been initialized yet, connect with mnemonic to initialize",
))
let client = r_state.client()?;
match client.delegate_to_mixnode(identity, bond).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
@@ -339,15 +350,10 @@ async fn undelegate_from_mixnode(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), String> {
let r_state = state.read().await;
if let Some(client) = &r_state.signing_client {
match client.remove_mixnode_delegation(identity).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
} else {
Err(String::from(
"Client has not been initialized yet, connect with mnemonic to initialize",
))
let client = r_state.client()?;
match client.remove_mixnode_delegation(identity).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
@@ -362,15 +368,10 @@ async fn delegate_to_gateway(
Ok(b) => b,
Err(e) => return Err(format_err!(e)),
};
if let Some(client) = &r_state.signing_client {
match client.delegate_to_gateway(identity, bond).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
} else {
Err(String::from(
"Client has not been initialized yet, connect with mnemonic to initialize",
))
let client = r_state.client()?;
match client.delegate_to_gateway(identity, bond).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
@@ -380,15 +381,10 @@ async fn undelegate_from_gateway(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), String> {
let r_state = state.read().await;
if let Some(client) = &r_state.signing_client {
match client.remove_gateway_delegation(identity).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
} else {
Err(String::from(
"Client has not been initialized yet, connect with mnemonic to initialize",
))
let client = r_state.client()?;
match client.remove_gateway_delegation(identity).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
@@ -403,56 +399,71 @@ async fn bond_gateway(
Ok(b) => b,
Err(e) => return Err(format_err!(e)),
};
if let Some(client) = &r_state.signing_client {
match client.bond_gateway(gateway, bond).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
} else {
Err(String::from(
"Client has not been initialized yet, connect with mnemonic to initialize",
))
let client = r_state.client()?;
match client.bond_gateway(gateway, bond).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
async fn unbond_gateway(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<(), String> {
let r_state = state.read().await;
if let Some(client) = &r_state.signing_client {
match client.unbond_gateway().await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
} else {
Err(String::from(
"Client has not been initialized yet, connect with mnemonic to initialize",
))
let client = r_state.client()?;
match client.unbond_gateway().await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
async fn send(address: &str, amount: Coin, memo: String, state: tauri::State<'_, Arc<RwLock<State>>>,) -> Result<(), String> {
async fn send(
address: &str,
amount: Coin,
memo: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TauriTxResult, String> {
let address = match AccountId::from_str(address) {
Ok(addy) => addy,
Err(e) => return Err(format_err!(e))
Err(e) => return Err(format_err!(e)),
};
let amount: CosmosCoin = match amount.try_into() {
let cosmos_amount: CosmosCoin = match amount.clone().try_into() {
Ok(b) => b,
Err(e) => return Err(format_err!(e)),
};
let r_state = state.read().await;
if let Some(client) = &r_state.signing_client {
match client.send(&address, vec!(amount), memo).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
} else {
Err(String::from(
"Client has not been initialized yet, connect with mnemonic to initialize",
))
let client = r_state.client()?;
match client.send(&address, vec![cosmos_amount], memo).await {
Ok(result) => Ok(TauriTxResult::new(result, TransactionDetails {
from_address: client.address().to_string(),
to_address: address.to_string(),
amount: amount.into()
})),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
async fn get_gas_price(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<Coin, String> {
let r_state = state.read().await;
let client = r_state.client()?;
let coin = client.get_gas_price().into();
Ok(coin)
}
#[tauri::command]
async fn get_gas_limits(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<HashMap<String, u64>, String> {
let r_state = state.read().await;
let client = r_state.client()?;
let mut limits = HashMap::new();
for (k, v) in client.get_custom_gas_limits() {
limits.insert(k.to_string(), v.value());
}
Ok(limits)
}
fn _connect_with_mnemonic(mnemonic: Mnemonic, config: &Config) -> NymdClient<SigningNymdClient> {
match NymdClient::connect_with_mnemonic(
config.get_nymd_validator_url().unwrap(),
@@ -482,7 +493,14 @@ fn main() {
undelegate_from_mixnode,
delegate_to_gateway,
undelegate_from_gateway,
send
send,
get_gas_price,
get_gas_limits,
get_credential,
randomise_credential,
delete_credential,
list_credentials,
verify_credential
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
@@ -492,5 +510,7 @@ export! {
MixNode => "../src/types/rust/mixnode.ts",
Coin => "../src/types/rust/coin.ts",
Balance => "../src/types/rust/balance.ts",
Gateway => "../src/types/rust/gateway.ts"
Gateway => "../src/types/rust/gateway.ts",
TauriTxResult => "../src/types/rust/tauritxresult.ts",
TransactionDetails => "../src/types/rust/transactiondetails.ts"
}
+55
View File
@@ -0,0 +1,55 @@
use crate::config::Config;
use crate::format_err;
use coconut_interface::{
self, Attribute, Parameters, Signature, VerificationKey,
};
use validator_client::nymd::{NymdClient, SigningNymdClient};
#[derive(Default)]
pub struct State {
config: Config,
signing_client: Option<NymdClient<SigningNymdClient>>,
// Coconut stuff
pub signatures: Vec<Signature>,
n_attributes: u32,
params: Option<Parameters>,
public_attributes: Vec<Attribute>,
private_attributes: Vec<Attribute>,
pub aggregated_verification_key: Option<VerificationKey>,
}
impl State {
pub fn client(&self) -> Result<&NymdClient<SigningNymdClient>, String> {
self.signing_client.as_ref().ok_or_else(|| {
"Client has not been initialized yet, connect with mnemonic to initialize".to_string()
})
}
pub fn config(&self) -> Config {
self.config.clone()
}
pub fn set_client(&mut self, signing_client: NymdClient<SigningNymdClient>) {
self.signing_client = Some(signing_client)
}
pub fn params(&self) -> Result<&Parameters, String> {
self
.params
.as_ref()
.ok_or_else(|| format_err!("Parameters are not set!"))
}
pub fn private_attributes(&self) -> Vec<Attribute> {
self.private_attributes.clone()
}
pub fn public_attributes(&self) -> Vec<Attribute> {
self.public_attributes.clone()
}
pub fn n_attributes(&self) -> u32 {
self.n_attributes
}
}
@@ -136,6 +136,22 @@ export const ApiList = () => {
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: "get_gas_price",
args: [],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: "get_gas_limits",
args: [],
}}
/>
</ListItem>
</List>
)
}
@@ -0,0 +1,9 @@
import { TransactionDetails } from "./transactiondetails";
export interface TauriTxResult {
code: number;
gas_wanted: number;
gas_used: number;
block_height: number;
details: TransactionDetails;
}
@@ -0,0 +1,7 @@
import { Coin } from "./coin";
export interface TransactionDetails {
from_address: string;
to_address: string;
amount: Coin;
}