From 2f8748134894c5d185cdfb5973d15b748023fc0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 23 Feb 2023 17:19:13 +0000 Subject: [PATCH] in-wallet verification of mix bonding signature --- .../validator-client/src/nyxd/mod.rs | 10 + .../src/nyxd/traits/mixnet_query_client.rs | 8 + .../contracts-common/src/signing.rs | 130 ++++-- .../mixnet-contract/src/lib.rs | 2 + .../mixnet-contract/src/msg.rs | 54 +-- .../mixnet-contract/src/signing_types.rs | 118 ++++++ contracts/mixnet/src/contract.rs | 3 + contracts/mixnet/src/signing/queries.rs | 2 +- gateway/src/commands/sign.rs | 2 +- nym-wallet/Cargo.lock | 256 ++++++++++-- nym-wallet/src-tauri/Cargo.toml | 5 + nym-wallet/src-tauri/src/error.rs | 13 + .../src-tauri/src/operations/helpers.rs | 389 ++++++++++++++++++ .../src-tauri/src/operations/mixnet/bond.rs | 31 +- nym-wallet/src-tauri/src/operations/mod.rs | 4 + 15 files changed, 909 insertions(+), 118 deletions(-) create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs create mode 100644 nym-wallet/src-tauri/src/operations/helpers.rs diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index 4990f1b030..b7612c5df3 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -43,6 +43,7 @@ pub use cosmrs::tendermint::Time as TendermintTime; pub use cosmrs::tx::{self, Gas}; pub use cosmrs::Coin as CosmosCoin; pub use cosmrs::{bip32, AccountId, Decimal, Denom}; +use cosmwasm_std::Addr; pub use cosmwasm_std::Coin as CosmWasmCoin; pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment}; pub use signing_client::Client as SigningNyxdClient; @@ -369,6 +370,15 @@ where &self.client_address.as_ref().unwrap()[0] } + pub fn cw_address(&self) -> Addr + where + C: SigningCosmWasmClient, + { + // the call to unchecked is fine here as we're converting directly from `AccountId` + // which must have been a valid bech32 address + Addr::unchecked(self.address().as_ref()) + } + pub fn signer(&self) -> &DirectSecp256k1HdWallet where C: SigningCosmWasmClient, diff --git a/common/client-libs/validator-client/src/nyxd/traits/mixnet_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/mixnet_query_client.rs index 2ff48d1113..fd97892420 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/mixnet_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/mixnet_query_client.rs @@ -6,6 +6,7 @@ use crate::nyxd::error::NyxdError; use crate::nyxd::NyxdClient; use async_trait::async_trait; use cosmrs::AccountId; +use nym_contracts_common::signing::Nonce; use nym_mixnet_contract_common::delegation::{MixNodeDelegationResponse, OwnerProxySubKey}; use nym_mixnet_contract_common::families::Family; use nym_mixnet_contract_common::mixnode::{ @@ -390,6 +391,13 @@ pub trait MixnetQueryClient { .await } + async fn get_signing_nonce(&self, address: &AccountId) -> Result { + self.query_mixnet_contract(MixnetQueryMsg::GetSigningNonce { + address: address.to_string(), + }) + .await + } + async fn get_node_family_by_label(&self, label: &str) -> Result, NyxdError> { self.query_mixnet_contract(MixnetQueryMsg::GetFamilyByLabel { label: label.to_string(), diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/signing.rs b/common/cosmwasm-smart-contracts/contracts-common/src/signing.rs index 7648c5183e..85d2073a6d 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/src/signing.rs +++ b/common/cosmwasm-smart-contracts/contracts-common/src/signing.rs @@ -3,14 +3,90 @@ use cosmwasm_std::{from_slice, to_vec, Addr, Coin, MessageInfo, StdResult}; use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt::{Display, Formatter}; pub type Nonce = u32; +// define this type explicitly for [hopefully] better usability +// (so you wouldn't need to worry about whether you should use bytes, bs58, etc.) +#[derive(Clone)] +pub struct MessageSignature(Vec); + +impl MessageSignature { + pub fn as_bs58_string(&self) -> String { + bs58::encode(&self.0).into_string() + } +} + +impl<'a> From<&'a [u8]> for MessageSignature { + fn from(value: &'a [u8]) -> Self { + MessageSignature(value.to_vec()) + } +} + +impl From> for MessageSignature { + fn from(value: Vec) -> Self { + MessageSignature(value) + } +} + +impl TryFrom for MessageSignature { + type Error = bs58::decode::Error; + + fn try_from(value: String) -> Result { + Ok(MessageSignature(bs58::decode(value).into_vec()?)) + } +} + +impl AsRef<[u8]> for MessageSignature { + #[inline] + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl<'de> Deserialize<'de> for MessageSignature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let inner = String::deserialize(deserializer)?; + let bytes = bs58::decode(inner).into_vec().map_err(de::Error::custom)?; + Ok(MessageSignature(bytes)) + } +} + +impl Serialize for MessageSignature { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let bs58_encoded = self.as_bs58_string(); + bs58_encoded.serialize(serializer) + } +} + +impl Display for MessageSignature { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_bs58_string()) + } +} + +pub trait SigningPurpose { + fn message_type() -> MessageType; +} + #[derive(Serialize)] #[serde(transparent)] pub struct MessageType(String); +impl MessageType { + pub fn new>(typ: S) -> Self { + MessageType(typ.into()) + } +} + impl From for MessageType where T: ToString, @@ -20,7 +96,7 @@ where } } -#[derive(Default, Serialize, Deserialize)] +#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)] #[serde(rename_all = "lowercase")] pub enum SigningAlgorithm { #[default] @@ -28,12 +104,20 @@ pub enum SigningAlgorithm { Secp256k1, } +impl SigningAlgorithm { + pub fn is_ed25519(&self) -> bool { + matches!(self, SigningAlgorithm::Ed25519) + } +} + // TODO: maybe move this one to repo-wide common? +// TODO: should it perhaps also include the public key itself? #[derive(Serialize, Deserialize)] pub struct SignableMessage { - nonce: u32, - algorithm: SigningAlgorithm, - content: T, + pub nonce: u32, + pub algorithm: SigningAlgorithm, + + pub content: T, } impl SignableMessage { @@ -83,24 +167,21 @@ impl SignableMessage { } #[derive(Serialize)] -struct ContractMessageContent { - message_type: MessageType, - signer: Addr, - proxy: Option, - funds: Vec, - data: T, +pub struct ContractMessageContent { + pub message_type: MessageType, + pub signer: Addr, + pub proxy: Option, + pub funds: Vec, + pub data: T, } -impl ContractMessageContent { - pub fn new( - message_type: MessageType, - signer: Addr, - proxy: Option, - funds: Vec, - data: T, - ) -> Self { +impl ContractMessageContent +where + T: SigningPurpose, +{ + pub fn new(signer: Addr, proxy: Option, funds: Vec, data: T) -> Self { ContractMessageContent { - message_type, + message_type: T::message_type(), signer, proxy, funds, @@ -108,12 +189,7 @@ impl ContractMessageContent { } } - pub fn new_with_info( - message_type: MessageType, - info: MessageInfo, - signer: Addr, - data: T, - ) -> Self { + pub fn new_with_info(info: MessageInfo, signer: Addr, data: T) -> Self { let proxy = if info.sender == signer { None } else { @@ -121,7 +197,7 @@ impl ContractMessageContent { }; ContractMessageContent { - message_type, + message_type: T::message_type(), signer, proxy, funds: info.funds, diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs index 4adc351471..738aeb6daa 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs @@ -17,6 +17,7 @@ mod msg; pub mod pending_events; pub mod reward_params; pub mod rewarding; +pub mod signing_types; mod types; pub use contracts_common::types::*; @@ -43,4 +44,5 @@ pub use pending_events::{ PendingIntervalEventData, PendingIntervalEventKind, }; pub use reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate, RewardingParams}; +pub use signing_types::*; pub use types::*; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index 1d79802ee9..c43b591d42 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -10,7 +10,6 @@ use crate::reward_params::{ }; use crate::{delegation, ContractStateParams, Layer, LayerAssignment, MixId, Percent}; use crate::{Gateway, IdentityKey, MixNode}; -use contracts_common::signing::MessageType; use cosmwasm_std::Decimal; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -241,54 +240,6 @@ pub enum ExecuteMsg { } impl ExecuteMsg { - pub fn message_type(&self) -> MessageType { - match self { - ExecuteMsg::AssignNodeLayer { .. } => {} - ExecuteMsg::CreateFamily { .. } => {} - ExecuteMsg::JoinFamily { .. } => {} - ExecuteMsg::LeaveFamily { .. } => {} - ExecuteMsg::KickFamilyMember { .. } => {} - ExecuteMsg::CreateFamilyOnBehalf { .. } => {} - ExecuteMsg::JoinFamilyOnBehalf { .. } => {} - ExecuteMsg::LeaveFamilyOnBehalf { .. } => {} - ExecuteMsg::KickFamilyMemberOnBehalf { .. } => {} - ExecuteMsg::UpdateRewardingValidatorAddress { .. } => {} - ExecuteMsg::UpdateContractStateParams { .. } => {} - ExecuteMsg::UpdateActiveSetSize { .. } => {} - ExecuteMsg::UpdateRewardingParams { .. } => {} - ExecuteMsg::UpdateIntervalConfig { .. } => {} - ExecuteMsg::BeginEpochTransition => {} - ExecuteMsg::AdvanceCurrentEpoch { .. } => {} - ExecuteMsg::ReconcileEpochEvents { .. } => {} - ExecuteMsg::BondMixnode { .. } => {} - ExecuteMsg::BondMixnodeOnBehalf { .. } => {} - ExecuteMsg::PledgeMore { .. } => {} - ExecuteMsg::PledgeMoreOnBehalf { .. } => {} - ExecuteMsg::UnbondMixnode { .. } => {} - ExecuteMsg::UnbondMixnodeOnBehalf { .. } => {} - ExecuteMsg::UpdateMixnodeCostParams { .. } => {} - ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { .. } => {} - ExecuteMsg::UpdateMixnodeConfig { .. } => {} - ExecuteMsg::UpdateMixnodeConfigOnBehalf { .. } => {} - ExecuteMsg::BondGateway { .. } => {} - ExecuteMsg::BondGatewayOnBehalf { .. } => {} - ExecuteMsg::UnbondGateway { .. } => {} - ExecuteMsg::UnbondGatewayOnBehalf { .. } => {} - ExecuteMsg::DelegateToMixnode { .. } => {} - ExecuteMsg::DelegateToMixnodeOnBehalf { .. } => {} - ExecuteMsg::UndelegateFromMixnode { .. } => {} - ExecuteMsg::UndelegateFromMixnodeOnBehalf { .. } => {} - ExecuteMsg::RewardMixnode { .. } => {} - ExecuteMsg::WithdrawOperatorReward { .. } => {} - ExecuteMsg::WithdrawOperatorRewardOnBehalf { .. } => {} - ExecuteMsg::WithdrawDelegatorReward { .. } => {} - ExecuteMsg::WithdrawDelegatorRewardOnBehalf { .. } => {} - #[cfg(feature = "contract-testing")] - ExecuteMsg::TestingResolveAllPendingEvents { .. } => {} - } - todo!() - } - pub fn default_memo(&self) -> String { match self { ExecuteMsg::AssignNodeLayer { mix_id, layer } => { @@ -549,6 +500,11 @@ pub enum QueryMsg { start_after: Option, }, GetNumberOfPendingEvents {}, + + // signing-related + GetSigningNonce { + address: String, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs new file mode 100644 index 0000000000..b539291ce5 --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs @@ -0,0 +1,118 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::{Gateway, MixNode, MixNodeCostParams}; +use contracts_common::signing::{MessageType, SigningPurpose}; +use serde::Serialize; + +#[derive(Serialize)] +pub struct MixnodeBondingPayload { + mix_node: MixNode, + cost_params: MixNodeCostParams, +} + +impl MixnodeBondingPayload { + pub fn new(mix_node: MixNode, cost_params: MixNodeCostParams) -> Self { + Self { + mix_node, + cost_params, + } + } +} + +impl SigningPurpose for MixnodeBondingPayload { + fn message_type() -> MessageType { + MessageType::new("mixnode-bonding") + } +} + +#[derive(Serialize)] +pub struct GatewayBondingPayload { + gateway: Gateway, +} + +impl GatewayBondingPayload { + pub fn new(gateway: Gateway) -> Self { + Self { gateway } + } +} + +impl SigningPurpose for GatewayBondingPayload { + fn message_type() -> MessageType { + MessageType::new("gateway-bonding") + } +} + +#[derive(Serialize)] +pub struct FamilyCreationSignature { + label: String, + // TODO: add any extra fields? +} + +impl FamilyCreationSignature { + pub fn new(label: String) -> Self { + Self { label } + } +} + +impl SigningPurpose for FamilyCreationSignature { + fn message_type() -> MessageType { + MessageType::new("family-creation") + } +} + +#[derive(Serialize)] +pub struct FamilyJoinSignature { + family_head: String, + // TODO: add any extra fields? +} + +impl FamilyJoinSignature { + pub fn new(family_head: String) -> Self { + Self { family_head } + } +} + +impl SigningPurpose for FamilyJoinSignature { + fn message_type() -> MessageType { + MessageType::new("family-join") + } +} + +#[derive(Serialize)] +pub struct FamilyLeaveSignature { + family_head: String, + // TODO: add any extra fields? +} + +impl FamilyLeaveSignature { + pub fn new(family_head: String) -> Self { + Self { family_head } + } +} + +impl SigningPurpose for FamilyLeaveSignature { + fn message_type() -> MessageType { + MessageType::new("family-leave") + } +} + +#[derive(Serialize)] +pub struct FamilyKickSignature { + member: String, + // TODO: add any extra fields? +} + +impl FamilyKickSignature { + pub fn new(member: String) -> Self { + Self { member } + } +} + +impl SigningPurpose for FamilyKickSignature { + fn message_type() -> MessageType { + MessageType::new("family-member-removal") + } +} + +// TODO: depending on our threat model, we should perhaps extend it to include all _on_behalf methods diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 923b7614b5..623e9c0a1c 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -581,6 +581,9 @@ pub fn query( QueryMsg::GetNumberOfPendingEvents {} => to_binary( &crate::interval::queries::query_number_of_pending_events(deps)?, ), + QueryMsg::GetSigningNonce { address } => to_binary( + &crate::signing::queries::query_current_signing_nonce(deps, address)?, + ), }; Ok(query_res?) diff --git a/contracts/mixnet/src/signing/queries.rs b/contracts/mixnet/src/signing/queries.rs index 46c0e0ce35..72ca4aed1c 100644 --- a/contracts/mixnet/src/signing/queries.rs +++ b/contracts/mixnet/src/signing/queries.rs @@ -5,7 +5,7 @@ use crate::signing::storage::get_signing_nonce; use cosmwasm_std::{Deps, StdResult}; use nym_contracts_common::signing::Nonce; -pub fn try_get_current_signing_nonce(deps: Deps<'_>, address: String) -> StdResult { +pub fn query_current_signing_nonce(deps: Deps<'_>, address: String) -> StdResult { let address = deps.api.addr_validate(&address)?; get_signing_nonce(deps.storage, address) } diff --git a/gateway/src/commands/sign.rs b/gateway/src/commands/sign.rs index ae9ebf3a06..3bffdf8e01 100644 --- a/gateway/src/commands/sign.rs +++ b/gateway/src/commands/sign.rs @@ -16,7 +16,7 @@ use validator_client::nyxd; #[derive(Args, Clone)] #[clap(group(ArgGroup::new("sign").required(true).args(&["wallet_address", "text", "contract_msg"])))] -pub(crate) struct Sign { +pub struct Sign { /// The id of the mixnode you want to sign with #[clap(long)] id: String, diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index d88f92b3ea..2be6c2fb75 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -20,7 +20,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -32,7 +32,8 @@ dependencies = [ "cfg-if", "cipher", "cpufeatures", - "opaque-debug", + "ctr", + "opaque-debug 0.3.0", ] [[package]] @@ -46,7 +47,7 @@ dependencies = [ "cipher", "ctr", "ghash", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -97,7 +98,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25df3c03f1040d0069fcd3907e24e36d59f9b6fa07ba49be0eb25a794f036ba7" dependencies = [ "base64ct", - "blake2", + "blake2 0.10.6", "password-hash 0.3.2", ] @@ -108,10 +109,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a27e27b63e4a34caee411ade944981136fdfa535522dc9944d6700196cbd899f" dependencies = [ "base64ct", - "blake2", + "blake2 0.10.6", "password-hash 0.4.2", ] +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + [[package]] name = "async-trait" version = "0.1.64" @@ -227,7 +234,7 @@ dependencies = [ "rand_core 0.6.4", "ripemd160", "sha2 0.9.9", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -268,6 +275,18 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" +dependencies = [ + "byte-tools", + "crypto-mac 0.7.0", + "digest 0.8.1", + "opaque-debug 0.2.3", +] + [[package]] name = "blake2" version = "0.10.6" @@ -290,7 +309,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "block-padding", - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -299,7 +318,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -318,7 +337,7 @@ dependencies = [ "group", "pairing", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -368,6 +387,12 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "bytemuck" version = "1.13.0" @@ -469,13 +494,23 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" +dependencies = [ + "byteorder", + "keystream", +] + [[package]] name = "cipher" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -785,9 +820,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ - "generic-array", + "generic-array 0.14.6", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -797,18 +832,28 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.6", "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.4", + "subtle 1.0.0", +] + [[package]] name = "crypto-mac" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array", - "subtle", + "generic-array 0.14.6", + "subtle 2.4.1", ] [[package]] @@ -881,7 +926,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1017,13 +1062,22 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -1034,7 +1088,7 @@ checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.3", "crypto-common", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1146,6 +1200,8 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", + "rand 0.7.3", + "serde", "sha2 0.9.9", "zeroize", ] @@ -1181,11 +1237,11 @@ dependencies = [ "crypto-bigint", "der", "ff", - "generic-array", + "generic-array 0.14.6", "group", "rand_core 0.6.4", "sec1", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1294,7 +1350,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1580,6 +1636,15 @@ dependencies = [ "windows 0.39.0", ] +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -1597,8 +1662,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1632,7 +1699,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" dependencies = [ - "opaque-debug", + "opaque-debug 0.3.0", "polyval", ] @@ -1763,7 +1830,7 @@ dependencies = [ "byteorder", "ff", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1939,13 +2006,23 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" +[[package]] +name = "hkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" +dependencies = [ + "digest 0.9.0", + "hmac", +] + [[package]] name = "hmac" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac", + "crypto-mac 0.11.1", "digest 0.9.0", ] @@ -2338,6 +2415,12 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keystream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" + [[package]] name = "kuchiki" version = "0.8.1" @@ -2374,6 +2457,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libm" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + [[package]] name = "libz-sys" version = "1.1.8" @@ -2401,6 +2490,18 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "lioness" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" +dependencies = [ + "arrayref", + "blake2 0.8.1", + "chacha", + "keystream", +] + [[package]] name = "lock_api" version = "0.4.9" @@ -2650,6 +2751,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg 1.1.0", + "libm", ] [[package]] @@ -2797,6 +2899,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-crypto" +version = "0.1.0" +dependencies = [ + "bs58", + "ed25519-dalek", + "nym-pemstore", + "nym-sphinx-types", + "rand 0.7.3", + "subtle-encoding", + "thiserror", + "x25519-dalek", +] + [[package]] name = "nym-execute" version = "0.1.0" @@ -2866,6 +2982,13 @@ dependencies = [ "pem", ] +[[package]] +name = "nym-sphinx-types" +version = "0.1.0" +dependencies = [ + "sphinx-packet", +] + [[package]] name = "nym-types" version = "1.0.0" @@ -2961,6 +3084,7 @@ version = "1.1.11" dependencies = [ "aes-gcm", "argon2 0.3.4", + "async-trait", "base64 0.13.1", "bip39", "cfg-if", @@ -2977,6 +3101,8 @@ dependencies = [ "log", "nym-coconut-interface", "nym-config", + "nym-contracts-common", + "nym-crypto", "nym-mixnet-contract-common", "nym-types", "nym-vesting-contract", @@ -2985,6 +3111,7 @@ dependencies = [ "once_cell", "pretty_env_logger", "rand 0.6.5", + "rand_chacha 0.2.2", "reqwest", "serde", "serde_json", @@ -3071,6 +3198,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "opaque-debug" version = "0.3.0" @@ -3209,7 +3342,7 @@ checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" dependencies = [ "base64ct", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -3220,7 +3353,7 @@ checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -3241,7 +3374,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" dependencies = [ - "crypto-mac", + "crypto-mac 0.11.1", ] [[package]] @@ -3513,7 +3646,7 @@ checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ "cfg-if", "cpufeatures", - "opaque-debug", + "opaque-debug 0.3.0", "universal-hash", ] @@ -3764,6 +3897,16 @@ dependencies = [ "getrandom 0.2.8", ] +[[package]] +name = "rand_distr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" +dependencies = [ + "num-traits", + "rand 0.7.3", +] + [[package]] name = "rand_hc" version = "0.1.0" @@ -4012,7 +4155,7 @@ checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" dependencies = [ "block-buffer 0.9.0", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.0", ] [[package]] @@ -4162,9 +4305,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der", - "generic-array", + "generic-array 0.14.6", "pkcs8", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -4396,7 +4539,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.0", ] [[package]] @@ -4419,7 +4562,7 @@ dependencies = [ "block-buffer 0.9.0", "digest 0.9.0", "keccak", - "opaque-debug", + "opaque-debug 0.3.0", ] [[package]] @@ -4518,6 +4661,30 @@ dependencies = [ "system-deps 5.0.0", ] +[[package]] +name = "sphinx-packet" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" +dependencies = [ + "aes", + "arrayref", + "blake2 0.8.1", + "bs58", + "byteorder", + "chacha", + "curve25519-dalek", + "digest 0.9.0", + "hkdf", + "hmac", + "lioness", + "log", + "rand 0.7.3", + "rand_distr", + "sha2 0.9.9", + "subtle 2.4.1", +] + [[package]] name = "spin" version = "0.5.2" @@ -4609,6 +4776,12 @@ dependencies = [ "syn", ] +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "subtle" version = "2.4.1" @@ -4951,7 +5124,7 @@ dependencies = [ "serde_repr", "sha2 0.9.9", "signature", - "subtle", + "subtle 2.4.1", "subtle-encoding", "tendermint-proto", "time", @@ -5369,8 +5542,8 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array", - "subtle", + "generic-array 0.14.6", + "subtle 2.4.1", ] [[package]] @@ -6029,6 +6202,17 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "x25519-dalek" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" +dependencies = [ + "curve25519-dalek", + "rand_core 0.5.1", + "zeroize", +] + [[package]] name = "xattr" version = "0.2.3" diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 406e9fb4c1..00a6f7f63b 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -19,6 +19,7 @@ tauri-codegen = "=1.2.1" tauri-macros = "=1.2.1" [dependencies] +async-trait = "0.1.64" bip39 = "1.0" cfg-if = "1.0.0" colored = "2.0" @@ -57,6 +58,8 @@ cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegr validator-client = { path = "../../common/client-libs/validator-client", features = [ "nyxd-client", ] } +nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] } +nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } nym-config = { path = "../../common/config" } @@ -67,6 +70,8 @@ nym-types = { path = "../../common/types" } nym-wallet-types = { path = "../nym-wallet-types" } [dev-dependencies] +nym-crypto = { path = "../../common/crypto", features = ["rand"] } +rand_chacha = "0.2" tempfile = "3.3.0" ts-rs = "6.1.2" diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index 0166435c3e..2748cd636f 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -1,3 +1,6 @@ +use nym_contracts_common::signing::SigningAlgorithm; +use nym_crypto::asymmetric::identity; +use nym_crypto::asymmetric::identity::{Ed25519RecoveryError, SignatureError}; use nym_types::error::TypesError; use nym_wallet_types::network::Network; use serde::{Serialize, Serializer}; @@ -122,6 +125,16 @@ pub enum BackendError { #[error("Unable to open a new window")] NewWindowError, + #[error("received unexpected signing algorithm: {received:?}. Expected to get {expected:?}")] + UnexpectedSigningAlgorithm { + received: SigningAlgorithm, + expected: SigningAlgorithm, + }, + #[error(transparent)] + Ed25519Recovery(#[from] Ed25519RecoveryError), + #[error(transparent)] + Ed25519SignatureError(SignatureError), + #[error("This command ({name}) has been removed. Please try to use {alternative} instead.")] RemovedCommand { name: String, alternative: String }, } diff --git a/nym-wallet/src-tauri/src/operations/helpers.rs b/nym-wallet/src-tauri/src/operations/helpers.rs new file mode 100644 index 0000000000..924a4199e5 --- /dev/null +++ b/nym-wallet/src-tauri/src/operations/helpers.rs @@ -0,0 +1,389 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::BackendError; +use async_trait::async_trait; +use cosmrs::AccountId; +use cosmwasm_std::Addr; +use nym_contracts_common::signing::{ + ContractMessageContent, MessageSignature, Nonce, SignableMessage, SigningAlgorithm, +}; +use nym_crypto::asymmetric::identity; +use nym_mixnet_contract_common::{ + Gateway, GatewayBondingPayload, MixNode, MixNodeCostParams, MixnodeBondingPayload, +}; +use validator_client::nyxd::error::NyxdError; +use validator_client::nyxd::traits::MixnetQueryClient; +use validator_client::nyxd::{Coin, SigningNyxdClient}; +use validator_client::Client; + +type SignableMixNodeBondingMsg = SignableMessage>; +type SignableGatewayBondingMsg = SignableMessage>; + +// define this as a separate trait for mocking purposes +#[async_trait] +pub(crate) trait AddressAndNonceProvider { + async fn get_signing_nonce(&self) -> Result; + fn vesting_contract_address(&self) -> &AccountId; + fn cw_address(&self) -> Addr; +} + +#[async_trait] +impl AddressAndNonceProvider for Client { + async fn get_signing_nonce(&self) -> Result { + self.nyxd.get_signing_nonce(self.nyxd.address()).await + } + + fn vesting_contract_address(&self) -> &AccountId { + self.nyxd.vesting_contract_address() + } + + fn cw_address(&self) -> Addr { + self.nyxd.cw_address() + } +} + +fn proxy(client: &P, vesting: bool) -> Option { + if vesting { + // the call to unchecked is fine here as we're converting directly from `AccountId` + // which must have been a valid bech32 address + Some(Addr::unchecked(client.vesting_contract_address().as_ref())) + } else { + None + } +} + +// since the message has to go back to the user due to the increasing nonce, we might as well sign the entire payload +pub(crate) async fn create_mixnode_bonding_sign_payload( + client: &P, + mix_node: MixNode, + cost_params: MixNodeCostParams, + pledge: Coin, + vesting: bool, +) -> Result { + let payload = MixnodeBondingPayload::new(mix_node, cost_params); + let signer = client.cw_address(); + let proxy = proxy(client, vesting); + let content = ContractMessageContent::new(signer, proxy, vec![pledge.into()], payload); + let nonce = client.get_signing_nonce().await?; + + Ok(SignableMessage::new(nonce, content)) +} + +pub(crate) async fn verify_mixnode_bonding_sign_payload( + client: &P, + mix_node: &MixNode, + cost_params: &MixNodeCostParams, + pledge: &Coin, + vesting: bool, + msg_signature: &MessageSignature, +) -> Result<(), BackendError> { + let identity_key = identity::PublicKey::from_base58_string(&mix_node.identity_key)?; + let signature = identity::Signature::from_bytes(msg_signature.as_ref())?; + + // recreate the plaintext + let msg = create_mixnode_bonding_sign_payload( + client, + mix_node.clone(), + cost_params.clone(), + pledge.clone(), + vesting, + ) + .await?; + let plaintext = msg.to_plaintext()?; + + if !msg.algorithm.is_ed25519() { + return Err(BackendError::UnexpectedSigningAlgorithm { + received: msg.algorithm, + expected: SigningAlgorithm::Ed25519, + }); + } + + // TODO: possibly provide better error message if this check fails + identity_key.verify(&plaintext, &signature)?; + Ok(()) +} + +// since the message has to go back to the user due to the increasing nonce, we might as well sign the entire payload +pub(crate) async fn create_gateway_bonding_sign_payload( + client: &P, + gateway: Gateway, + pledge: Coin, + vesting: bool, +) -> Result { + let payload = GatewayBondingPayload::new(gateway); + let signer = client.cw_address(); + let proxy = proxy(client, vesting); + let content = ContractMessageContent::new(signer, proxy, vec![pledge.into()], payload); + let nonce = client.get_signing_nonce().await?; + + Ok(SignableMessage::new(nonce, content)) +} + +pub(crate) async fn verify_gateway_bonding_sign_payload( + client: &P, + gateway: &Gateway, + pledge: &Coin, + vesting: bool, + msg_signature: &MessageSignature, +) -> Result<(), BackendError> { + let identity_key = identity::PublicKey::from_base58_string(&gateway.identity_key)?; + let signature = identity::Signature::from_bytes(msg_signature.as_ref())?; + + // recreate the plaintext + let msg = create_gateway_bonding_sign_payload(client, gateway.clone(), pledge.clone(), vesting) + .await?; + let plaintext = msg.to_plaintext()?; + + if !msg.algorithm.is_ed25519() { + return Err(BackendError::UnexpectedSigningAlgorithm { + received: msg.algorithm, + expected: SigningAlgorithm::Ed25519, + }); + } + + // TODO: possibly provide better error message if this check fails + identity_key.verify(&plaintext, &signature)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::coin; + use nym_contracts_common::Percent; + use rand_chacha::rand_core::SeedableRng; + use rand_chacha::ChaCha20Rng; + + // use rng with constant seed for all tests so that they would be deterministic + pub fn test_rng() -> ChaCha20Rng { + let dummy_seed = [42u8; 32]; + rand_chacha::ChaCha20Rng::from_seed(dummy_seed) + } + + struct MockClient { + address: Addr, + vesting_contract: AccountId, + signing_nonce: Nonce, + } + + #[async_trait] + impl AddressAndNonceProvider for MockClient { + async fn get_signing_nonce(&self) -> Result { + Ok(self.signing_nonce) + } + + fn vesting_contract_address(&self) -> &AccountId { + &self.vesting_contract + } + + fn cw_address(&self) -> Addr { + self.address.clone() + } + } + + #[tokio::test] + async fn dummy_mix_bonding_signature_verification() { + let mut rng = test_rng(); + let identity_keypair = identity::KeyPair::new(&mut rng); + let dummy_mixnode = MixNode { + host: "1.2.3.4".to_string(), + mix_port: 1234, + verloc_port: 2345, + http_api_port: 3456, + sphinx_key: "totally-legit-sphinx-key".to_string(), + identity_key: identity_keypair.public_key().to_base58_string(), + version: "v1.2.3".to_string(), + }; + let dummy_cost_params = MixNodeCostParams { + profit_margin_percent: Percent::from_percentage_value(42).unwrap(), + interval_operating_cost: coin(1111111, "unym"), + }; + let dummy_pledge: Coin = coin(10000000000, "unym").into(); + + let dummy_account = Addr::unchecked("n16t2umcd83zjpl5puyuuq6lgmy4p3qedjd8ynn6"); + let dummy_client = MockClient { + address: dummy_account, + vesting_contract: "n17tj0a0w6v7r2dc54rnkzfza6s8hxs87rj273a5".parse().unwrap(), + signing_nonce: 42, + }; + + let signing_msg_liquid = create_mixnode_bonding_sign_payload( + &dummy_client, + dummy_mixnode.clone(), + dummy_cost_params.clone(), + dummy_pledge.clone(), + false, + ) + .await + .unwrap(); + + let plaintext_liquid = signing_msg_liquid.to_plaintext().unwrap(); + let sig_liquid: MessageSignature = identity_keypair + .private_key() + .sign(&plaintext_liquid) + .to_bytes() + .as_ref() + .into(); + + let signing_msg_vesting = create_mixnode_bonding_sign_payload( + &dummy_client, + dummy_mixnode.clone(), + dummy_cost_params.clone(), + dummy_pledge.clone(), + true, + ) + .await + .unwrap(); + + let plaintext_vesting = signing_msg_vesting.to_plaintext().unwrap(); + let sig_vesting: MessageSignature = identity_keypair + .private_key() + .sign(&plaintext_vesting) + .to_bytes() + .as_ref() + .into(); + + let res = verify_mixnode_bonding_sign_payload( + &dummy_client, + &dummy_mixnode, + &dummy_cost_params, + &dummy_pledge, + false, + &sig_liquid, + ) + .await; + assert!(res.is_ok()); + + let res = verify_mixnode_bonding_sign_payload( + &dummy_client, + &dummy_mixnode, + &dummy_cost_params, + &dummy_pledge, + true, + &sig_vesting, + ) + .await; + assert!(res.is_ok()); + + let res = verify_mixnode_bonding_sign_payload( + &dummy_client, + &dummy_mixnode, + &dummy_cost_params, + &dummy_pledge, + false, + &sig_vesting, + ) + .await; + assert!(res.is_err()); + + let res = verify_mixnode_bonding_sign_payload( + &dummy_client, + &dummy_mixnode, + &dummy_cost_params, + &dummy_pledge, + true, + &sig_liquid, + ) + .await; + assert!(res.is_err()) + } + + #[tokio::test] + async fn dummy_gateway_bonding_signature_verification() { + let mut rng = test_rng(); + let identity_keypair = identity::KeyPair::new(&mut rng); + let dummy_gateway = Gateway { + host: "1.2.3.4".to_string(), + mix_port: 1234, + clients_port: 2345, + location: "whatever".to_string(), + sphinx_key: "totally-legit-sphinx-key".to_string(), + identity_key: identity_keypair.public_key().to_base58_string(), + version: "v1.2.3".to_string(), + }; + + let dummy_pledge: Coin = coin(10000000000, "unym").into(); + + let dummy_account = Addr::unchecked("n16t2umcd83zjpl5puyuuq6lgmy4p3qedjd8ynn6"); + let dummy_client = MockClient { + address: dummy_account, + vesting_contract: "n17tj0a0w6v7r2dc54rnkzfza6s8hxs87rj273a5".parse().unwrap(), + signing_nonce: 42, + }; + + let signing_msg_liquid = create_gateway_bonding_sign_payload( + &dummy_client, + dummy_gateway.clone(), + dummy_pledge.clone(), + false, + ) + .await + .unwrap(); + + let plaintext_liquid = signing_msg_liquid.to_plaintext().unwrap(); + let sig_liquid: MessageSignature = identity_keypair + .private_key() + .sign(&plaintext_liquid) + .to_bytes() + .as_ref() + .into(); + + let signing_msg_vesting = create_gateway_bonding_sign_payload( + &dummy_client, + dummy_gateway.clone(), + dummy_pledge.clone(), + true, + ) + .await + .unwrap(); + + let plaintext_vesting = signing_msg_vesting.to_plaintext().unwrap(); + let sig_vesting: MessageSignature = identity_keypair + .private_key() + .sign(&plaintext_vesting) + .to_bytes() + .as_ref() + .into(); + + let res = verify_gateway_bonding_sign_payload( + &dummy_client, + &dummy_gateway, + &dummy_pledge, + false, + &sig_liquid, + ) + .await; + assert!(res.is_ok()); + + let res = verify_gateway_bonding_sign_payload( + &dummy_client, + &dummy_gateway, + &dummy_pledge, + true, + &sig_vesting, + ) + .await; + assert!(res.is_ok()); + + let res = verify_gateway_bonding_sign_payload( + &dummy_client, + &dummy_gateway, + &dummy_pledge, + false, + &sig_vesting, + ) + .await; + assert!(res.is_err()); + + let res = verify_gateway_bonding_sign_payload( + &dummy_client, + &dummy_gateway, + &dummy_pledge, + true, + &sig_liquid, + ) + .await; + assert!(res.is_err()) + } +} diff --git a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs index 216dd12841..45b8496844 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs @@ -2,8 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::BackendError; +use crate::operations::helpers::verify_mixnode_bonding_sign_payload; use crate::state::WalletState; use crate::{nyxd_client, Gateway, MixNode}; +use nym_contracts_common::signing::MessageSignature; use nym_mixnet_contract_common::{MixId, MixNodeConfigUpdate}; use nym_types::currency::DecCoin; use nym_types::gateway::GatewayBond; @@ -73,7 +75,7 @@ pub async fn unbond_gateway( pub async fn bond_mixnode( mixnode: MixNode, cost_params: MixNodeCostParams, - owner_signature: String, + msg_signature: MessageSignature, pledge: DecCoin, fee: Option, state: tauri::State<'_, WalletState>, @@ -90,10 +92,31 @@ pub async fn bond_mixnode( pledge_base, fee, ); - let res = guard - .current_client()? + + let client = guard.current_client()?; + // check the signature to make sure the user copied it correctly + if let Err(err) = verify_mixnode_bonding_sign_payload( + client, + &mixnode, + &cost_params, + &pledge_base, + false, + &msg_signature, + ) + .await + { + todo!() + } + + let res = client .nyxd - .bond_mixnode(mixnode, cost_params, owner_signature, pledge_base, fee) + .bond_mixnode( + mixnode, + cost_params, + msg_signature.as_bs58_string(), + pledge_base, + fee, + ) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); diff --git a/nym-wallet/src-tauri/src/operations/mod.rs b/nym-wallet/src-tauri/src/operations/mod.rs index c6c175ee12..70c6928b0e 100644 --- a/nym-wallet/src-tauri/src/operations/mod.rs +++ b/nym-wallet/src-tauri/src/operations/mod.rs @@ -1,4 +1,8 @@ +// Copyright 2021-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + pub mod help; +pub(crate) mod helpers; pub mod mixnet; pub mod nym_api; pub mod signatures;