Compare commits

...

7 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacșu dd83631be1 Rename to avoid conflict 2022-09-20 18:01:12 +03:00
Bogdan-Ștefan Neacșu 405ca63c7b Mixnet execute msg 2022-09-20 17:41:26 +03:00
Bogdan-Ștefan Neacșu 7f17522666 Working send! 2022-09-20 17:41:25 +03:00
Bogdan-Ștefan Neacșu ba158f822d Have Clone and Debug for CosmosLedger 2022-09-20 17:40:23 +03:00
Bogdan-Ștefan Neacșu 0f147750ae Add sign function 2022-09-20 17:40:23 +03:00
Bogdan-Ștefan Neacșu 60d17389d0 Get address and add docs 2022-09-20 17:40:23 +03:00
Bogdan-Ștefan Neacșu 94d70e47fc Get version of ledger 2022-09-20 17:40:23 +03:00
28 changed files with 1223 additions and 130 deletions
Generated
+91 -2
View File
@@ -2384,6 +2384,17 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
[[package]]
name = "hidapi"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d26e1151deaab68f34fbfd16d491a2a0170cf98d69d3efa23873b567a4199e1"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "hkdf"
version = "0.11.0"
@@ -2815,6 +2826,54 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "ledger"
version = "0.1.0"
dependencies = [
"bip32",
"k256",
"ledger-transport",
"ledger-transport-hid",
"thiserror",
]
[[package]]
name = "ledger-apdu"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe435806c197dfeaa5efcded5e623c4b8230fd28fdf1e91e7a86e40ef2acbf90"
dependencies = [
"arrayref",
"no-std-compat",
"snafu 0.7.1",
]
[[package]]
name = "ledger-transport"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1117f2143d92c157197785bf57711d7b02f2cfa101e162f8ca7900fb7f976321"
dependencies = [
"async-trait",
"ledger-apdu",
]
[[package]]
name = "ledger-transport-hid"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45ba81a1f5f24396b37211478aff7fbcd605dd4544df8dbed07b9da3c2057aee"
dependencies = [
"byteorder",
"cfg-if 1.0.0",
"hex",
"hidapi",
"ledger-transport",
"libc",
"log",
"thiserror",
]
[[package]]
name = "libc"
version = "0.2.132"
@@ -3105,6 +3164,12 @@ dependencies = [
"url",
]
[[package]]
name = "no-std-compat"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
[[package]]
name = "nom"
version = "7.1.1"
@@ -3423,7 +3488,7 @@ dependencies = [
"proxy-helpers",
"rand 0.7.3",
"serde",
"snafu",
"snafu 0.6.10",
"socks5-requests",
"task",
"tokio",
@@ -3444,6 +3509,7 @@ dependencies = [
"cosmwasm-std",
"eyre",
"itertools",
"ledger",
"log",
"mixnet-contract-common",
"reqwest",
@@ -5338,7 +5404,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7"
dependencies = [
"doc-comment",
"snafu-derive",
"snafu-derive 0.6.10",
]
[[package]]
name = "snafu"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5177903bf45656592d9eb5c0e22f408fc023aae51dbe2088889b71633ba451f2"
dependencies = [
"doc-comment",
"snafu-derive 0.7.1",
]
[[package]]
@@ -5352,6 +5428,18 @@ dependencies = [
"syn",
]
[[package]]
name = "snafu-derive"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "410b26ed97440d90ced3e2488c868d56a86e2064f5d7d6f417909b286afe25e5"
dependencies = [
"heck 0.4.0",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "socket2"
version = "0.4.7"
@@ -6610,6 +6698,7 @@ dependencies = [
"flate2",
"futures",
"itertools",
"ledger",
"log",
"mixnet-contract-common",
"multisig-contract-common",
+1
View File
@@ -40,6 +40,7 @@ members = [
"common/crypto/dkg",
"common/execute",
"common/inclusion-probability",
"common/ledger",
"common/mixnode-common",
"common/network-defaults",
"common/nonexhaustive-delayqueue",
@@ -42,6 +42,7 @@ sha2 = { version = "0.9.5", optional = true }
itertools = { version = "0.10", optional = true }
cosmwasm-std = { version = "1.0.0", optional = true }
execute = { path = "../../execute" }
ledger = { path = "../../ledger" }
[dev-dependencies]
ts-rs = "6.1.2"
@@ -19,8 +19,11 @@ use validator_api_requests::models::{MixNodeBondAnnotated, UptimeResponse};
#[cfg(feature = "nymd-client")]
use crate::nymd::{
self, error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
WalletNymdClient,
};
#[cfg(feature = "nymd-client")]
use ledger::CosmosLedger;
#[cfg(feature = "nymd-client")]
use mixnet_contract_common::{
mixnode::DelegationEvent, ContractStateParams, Delegation, IdentityKey, Interval,
@@ -28,7 +31,7 @@ use mixnet_contract_common::{
RewardedSetUpdateDetails,
};
#[cfg(feature = "nymd-client")]
use network_defaults::NymNetworkDetails;
use network_defaults::{NymNetworkDetails, COSMOS_DERIVATION_PATH};
#[cfg(feature = "nymd-client")]
use std::collections::{HashMap, HashSet};
@@ -167,6 +170,51 @@ impl Client<SigningNymdClient> {
}
}
#[cfg(feature = "nymd-client")]
impl Client<WalletNymdClient> {
pub fn new_ledger(config: Config) -> Result<Client<WalletNymdClient>, ValidatorClientError> {
let validator_api_client = validator_api::Client::new(config.api_url.clone());
let signer = CosmosLedger::new(
COSMOS_DERIVATION_PATH.parse().unwrap(),
config
.nymd_config
.chain_details
.bech32_account_prefix
.clone(),
)?;
let nymd_client = NymdClient::connect_with_ledger(
config.nymd_config.clone(),
config.nymd_url.as_str(),
signer,
None,
)?;
Ok(Client {
mnemonic: None,
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
rewarded_set_page_limit: config.rewarded_set_page_limit,
validator_api: validator_api_client,
nymd: nymd_client,
})
}
pub fn change_nymd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> {
self.nymd = NymdClient::connect_with_ledger(
self.nymd.current_config().clone(),
new_endpoint.as_ref(),
self.nymd.ledger_signer(),
None,
)?;
Ok(())
}
pub fn set_nymd_simulated_gas_multiplier(&mut self, multiplier: f32) {
self.nymd.set_simulated_gas_multiplier(multiplier)
}
}
#[cfg(feature = "nymd-client")]
impl Client<QueryNymdClient> {
pub fn new_query(config: Config) -> Result<Client<QueryNymdClient>, ValidatorClientError> {
@@ -19,6 +19,10 @@ pub enum ValidatorClientError {
#[error("There was an issue with the Nymd client - {0}")]
NymdError(#[from] crate::nymd::error::NymdError),
#[cfg(feature = "nymd-client")]
#[error("There was an issue with the Ledger client - {0}")]
LedgerError(#[from] ledger::error::LedgerError),
#[error("No validator API url has been provided")]
NoAPIUrlAvailable,
}
@@ -262,6 +262,7 @@ pub trait CosmWasmClient: rpc::Client {
let broadcasted = CosmWasmClient::broadcast_tx_sync(self, tx).await?;
if broadcasted.code.is_err() {
println!("Error {:?}", broadcasted);
let code_val = broadcasted.code.value();
return Err(NymdError::BroadcastTxErrorDeliverTx {
hash: broadcasted.hash,
@@ -5,13 +5,19 @@ use crate::nymd::error::NymdError;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use crate::nymd::GasPrice;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl};
use ledger::CosmosLedger;
use std::convert::TryInto;
use std::time::Duration;
pub mod client;
mod helpers;
pub mod logs;
pub mod signing_client;
pub mod types;
pub mod wallet_client;
pub(crate) const DEFAULT_BROADCAST_POLLING_RATE: Duration = Duration::from_secs(4);
pub(crate) const DEFAULT_BROADCAST_TIMEOUT: Duration = Duration::from_secs(60);
pub fn connect<U>(endpoint: U) -> Result<HttpClient, NymdError>
where
@@ -31,3 +37,14 @@ where
{
signing_client::Client::connect_with_signer(endpoint, signer, gas_price)
}
pub fn connect_with_ledger<U: Clone>(
endpoint: U,
signer: CosmosLedger,
gas_price: GasPrice,
) -> Result<wallet_client::Client, NymdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
wallet_client::Client::connect_with_ledger(endpoint, signer, gas_price)
}
@@ -5,22 +5,23 @@ use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::cosmwasm_client::helpers::{compress_wasm_code, CheckResponse};
use crate::nymd::cosmwasm_client::logs::{self, parse_raw_logs};
use crate::nymd::cosmwasm_client::types::*;
use crate::nymd::cosmwasm_client::{DEFAULT_BROADCAST_POLLING_RATE, DEFAULT_BROADCAST_TIMEOUT};
use crate::nymd::error::NymdError;
use crate::nymd::fee::{Fee, DEFAULT_SIMULATED_GAS_MULTIPLIER};
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use crate::nymd::{Coin, GasAdjustable, GasPrice, TxResponse};
use crate::nymd::{Coin, GasAdjustable, GasPrice, MinimalSigningCosmWasmClient, TxResponse};
use async_trait::async_trait;
use cosmrs::bank::MsgSend;
use cosmrs::distribution::MsgWithdrawDelegatorReward;
use cosmrs::feegrant::{
AllowedMsgAllowance, BasicAllowance, MsgGrantAllowance, MsgRevokeAllowance,
};
use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
use cosmrs::tx::{self, Msg, SignDoc, SignerInfo};
use cosmrs::{cosmwasm, rpc, AccountId, Any, Tx};
use ledger::CosmosLedger;
use log::debug;
use serde::Serialize;
use sha2::Digest;
@@ -28,40 +29,16 @@ use sha2::Sha256;
use std::convert::TryInto;
use std::time::{Duration, SystemTime};
const DEFAULT_BROADCAST_POLLING_RATE: Duration = Duration::from_secs(4);
const DEFAULT_BROADCAST_TIMEOUT: Duration = Duration::from_secs(60);
fn empty_fee() -> tx::Fee {
tx::Fee {
amount: vec![],
gas_limit: Default::default(),
payer: None,
granter: None,
}
}
fn single_unspecified_signer_auth(
public_key: Option<tx::SignerPublicKey>,
sequence_number: tx::SequenceNumber,
) -> tx::AuthInfo {
tx::SignerInfo {
public_key,
mode_info: tx::ModeInfo::Single(tx::mode_info::Single {
mode: SignMode::Unspecified,
}),
sequence: sequence_number,
}
.auth_info(empty_fee())
}
#[async_trait]
pub trait SigningCosmWasmClient: CosmWasmClient {
pub trait SigningCosmWasmClient: MinimalSigningCosmWasmClient {
fn signer(&self) -> &DirectSecp256k1HdWallet;
fn gas_price(&self) -> &GasPrice;
fn signer_public_key(&self, signer_address: &AccountId) -> Option<tx::SignerPublicKey> {
let signer_accounts = self.signer().try_derive_accounts().ok()?;
let signer_accounts = SigningCosmWasmClient::signer(self)
.try_derive_accounts()
.ok()?;
let account_from_signer = signer_accounts
.iter()
.find(|account| &account.address == signer_address)?;
@@ -69,33 +46,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
Some(public_key.into())
}
async fn simulate(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
memo: impl Into<String> + Send + 'static,
) -> Result<SimulateResponse, NymdError> {
let public_key = self.signer_public_key(signer_address);
let sequence_response = self.get_sequence(signer_address).await?;
let partial_tx = Tx {
body: tx::Body::new(messages, memo, 0u32),
auth_info: single_unspecified_signer_auth(public_key, sequence_response.sequence),
signatures: vec![Vec::new()],
};
self.query_simulate(Some(partial_tx), Vec::new()).await
// for completion sake, once we're able to transition into using `tx_bytes`,
// we might want to use something like this instead:
// let tx_raw: tx::Raw = cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
// body_bytes: partial_tx.body.into_bytes().unwrap(),
// auth_info_bytes: partial_tx.auth_info.into_bytes().unwrap(),
// signatures: partial_tx.signatures,
// }
// .into();
// self.query_simulate(None, tx_raw.to_bytes().unwrap()).await
}
async fn upload(
&self,
sender_address: &AccountId,
@@ -541,50 +491,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.check_response()
}
// in this particular case we cannot generalise the argument to `&str` due to lifetime constraints
#[allow(clippy::ptr_arg)]
async fn determine_transaction_fee(
&self,
signer_address: &AccountId,
messages: &[Any],
fee: Fee,
memo: &String,
) -> Result<tx::Fee, NymdError> {
let auto_fee = |multiplier: Option<f32>| async move {
debug!("Trying to simulate gas costs...");
// from what I've seen in manual testing, gas estimation does not exist if transaction
// fails to get executed (for example if you send 'BondMixnode" with invalid signature)
let gas_estimation = self
.simulate(signer_address, messages.to_vec(), memo.clone())
.await?
.gas_info
.ok_or(NymdError::GasEstimationFailure)?
.gas_used;
let multiplier = multiplier.unwrap_or(DEFAULT_SIMULATED_GAS_MULTIPLIER);
let gas = gas_estimation.adjust_gas(multiplier);
debug!("Gas estimation: {}", gas_estimation);
debug!("Multiplying the estimation by {}", multiplier);
debug!("Final gas limit used: {}", gas);
let fee = self.gas_price() * gas;
Ok::<tx::Fee, NymdError>(tx::Fee::from_amount_and_gas(fee, gas))
};
let fee = match fee {
Fee::Manual(fee) => fee,
Fee::Auto(multiplier) => auto_fee(multiplier).await?,
Fee::PayerGranterAuto(auto_feegrant) => {
let mut fee = auto_fee(auto_feegrant.gas_adjustment).await?;
fee.payer = auto_feegrant.payer;
fee.granter = auto_feegrant.granter;
fee
}
};
debug!("Fee used for the transaction: {:?}", fee);
Ok(fee)
}
/// Broadcast a transaction, returning immediately.
async fn sign_and_broadcast_async(
&self,
@@ -597,7 +503,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_raw = SigningCosmWasmClient::sign(self, signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
@@ -617,7 +523,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_raw = SigningCosmWasmClient::sign(self, signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
@@ -638,7 +544,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_raw = SigningCosmWasmClient::sign(self, signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
@@ -659,7 +565,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_raw = SigningCosmWasmClient::sign(self, signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
@@ -675,7 +581,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
memo: impl Into<String> + Send + 'static,
signer_data: SignerData,
) -> Result<tx::Raw, NymdError> {
let signer_accounts = self.signer().try_derive_accounts()?;
let signer_accounts = SigningCosmWasmClient::signer(self).try_derive_accounts()?;
let account_from_signer = signer_accounts
.iter()
.find(|account| &account.address == signer_address)
@@ -701,8 +607,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
)
.map_err(|_| NymdError::SigningFailure)?;
self.signer()
.sign_direct_with_account(account_from_signer, sign_doc)
SigningCosmWasmClient::signer(self).sign_direct_with_account(account_from_signer, sign_doc)
}
async fn sign(
@@ -786,7 +691,16 @@ impl CosmWasmClient for Client {
}
}
#[async_trait]
impl MinimalSigningCosmWasmClient for Client {
fn signer(&self) -> CosmosLedger {
todo!()
}
fn gas_price(&self) -> &GasPrice {
&self.gas_price
}
}
impl SigningCosmWasmClient for Client {
fn signer(&self) -> &DirectSecp256k1HdWallet {
&self.signer
@@ -0,0 +1,490 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::cosmwasm_client::helpers::CheckResponse;
use crate::nymd::cosmwasm_client::logs::{self, parse_raw_logs};
use crate::nymd::cosmwasm_client::types::*;
use crate::nymd::cosmwasm_client::HttpClient;
use crate::nymd::cosmwasm_client::{DEFAULT_BROADCAST_POLLING_RATE, DEFAULT_BROADCAST_TIMEOUT};
use crate::nymd::fee::GasAdjustable;
use crate::nymd::wallet_client::rpc::SimpleRequest;
use crate::nymd::DEFAULT_SIMULATED_GAS_MULTIPLIER;
use crate::nymd::{
DirectSecp256k1HdWallet, GasPrice, HttpClientUrl, NymdError, SigningCosmWasmClient,
SimulateResponse, TendermintRpcError, TxResponse,
};
use async_trait::async_trait;
use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
use cosmrs::rpc;
use cosmrs::tx::mode_info::Single;
use cosmrs::tx::{self, Gas, ModeInfo, Msg, MsgProto, SignDoc, SignerInfo};
use cosmrs::{AccountId, Any};
use ledger::CosmosLedger;
use log::{debug, info};
use mixnet_contract_common::ExecuteMsg;
use serde::Serialize;
use std::str::FromStr;
use std::time::Duration;
fn single_unspecified_signer_auth(
public_key: Option<tx::SignerPublicKey>,
sequence_number: tx::SequenceNumber,
) -> tx::AuthInfo {
tx::SignerInfo {
public_key,
mode_info: tx::ModeInfo::Single(tx::mode_info::Single {
mode: SignMode::Unspecified,
}),
sequence: sequence_number,
}
.auth_info(empty_fee())
}
fn empty_fee() -> tx::Fee {
tx::Fee {
amount: vec![],
gas_limit: Default::default(),
payer: None,
granter: None,
}
}
#[derive(Debug, Clone)]
pub struct Client {
rpc_client: HttpClient,
signer: CosmosLedger,
gas_price: GasPrice,
broadcast_polling_rate: Duration,
broadcast_timeout: Duration,
}
impl Client {
pub fn connect_with_ledger<U: Clone>(
endpoint: U,
signer: CosmosLedger,
gas_price: GasPrice,
) -> Result<Self, NymdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
let rpc_client = HttpClient::new(endpoint)?;
Ok(Client {
rpc_client,
signer,
gas_price,
broadcast_polling_rate: DEFAULT_BROADCAST_POLLING_RATE,
broadcast_timeout: DEFAULT_BROADCAST_TIMEOUT,
})
}
pub fn set_broadcast_polling_rate(&mut self, broadcast_polling_rate: Duration) {
self.broadcast_polling_rate = broadcast_polling_rate
}
pub fn set_broadcast_timeout(&mut self, broadcast_timeout: Duration) {
self.broadcast_timeout = broadcast_timeout
}
pub fn signer(&self) -> CosmosLedger {
self.signer.clone()
}
}
#[async_trait]
impl rpc::Client for Client {
async fn perform<R>(&self, request: R) -> Result<R::Response, rpc::Error>
where
R: SimpleRequest,
{
self.rpc_client.perform(request).await
}
}
#[async_trait]
impl CosmWasmClient for Client {
fn broadcast_polling_rate(&self) -> Duration {
self.broadcast_polling_rate
}
fn broadcast_timeout(&self) -> Duration {
self.broadcast_timeout
}
}
impl MinimalSigningCosmWasmClient for Client {
fn signer(&self) -> CosmosLedger {
self.signer.clone()
}
fn gas_price(&self) -> &GasPrice {
&self.gas_price
}
}
impl SigningCosmWasmClient for Client {
fn signer(&self) -> &DirectSecp256k1HdWallet {
todo!()
}
fn gas_price(&self) -> &GasPrice {
&self.gas_price
}
}
#[derive(Debug, Serialize)]
struct Coin {
amount: String,
denom: String,
}
impl From<crate::nymd::Coin> for Coin {
fn from(coin: crate::nymd::Coin) -> Self {
Coin {
amount: coin.amount.to_string(),
denom: coin.denom.to_string(),
}
}
}
impl From<cosmrs::Coin> for Coin {
fn from(coin: cosmrs::Coin) -> Self {
let nymd_coin: crate::nymd::Coin = coin.into();
nymd_coin.into()
}
}
#[derive(Debug, Serialize)]
struct Fee {
amount: Vec<Coin>,
gas: String,
}
#[derive(Debug, Serialize)]
struct SendValue {
amount: Vec<Coin>,
from_address: String,
to_address: String,
}
#[derive(Debug, Serialize)]
struct MsgSend {
#[serde(rename = "type")]
type_url: String,
value: SendValue,
}
#[derive(Debug, Serialize)]
struct SendTransaction {
account_number: String,
chain_id: String,
fee: Fee,
memo: String,
msgs: Vec<MsgSend>,
sequence: String,
}
#[derive(Serialize)]
struct ExecuteContractValue {
contract: String,
funds: Vec<Coin>,
msg: ExecuteMsg,
sender: String,
}
#[derive(Serialize)]
struct MsgExecuteContract {
#[serde(rename = "type")]
type_url: String,
value: ExecuteContractValue,
}
#[derive(Serialize)]
struct ExecuteContractTransaction {
account_number: String,
chain_id: String,
fee: Fee,
memo: String,
msgs: Vec<MsgExecuteContract>,
sequence: String,
}
#[async_trait]
pub trait MinimalSigningCosmWasmClient: CosmWasmClient {
fn signer(&self) -> CosmosLedger;
fn gas_price(&self) -> &GasPrice;
fn signer_public_key(&self, signer_address: &AccountId) -> Option<tx::SignerPublicKey> {
let response = self.signer().get_addr_secp265k1(false).ok()?;
if response.address == signer_address.to_string() {
let verifying_key: cosmrs::crypto::secp256k1::VerifyingKey = response.public_key.into();
let cosmrs_public_key: cosmrs::crypto::PublicKey = verifying_key.into();
Some(cosmrs_public_key.into())
} else {
None
}
}
async fn simulate(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
memo: impl Into<String> + Send + 'static,
) -> Result<SimulateResponse, NymdError> {
let public_key = self.signer_public_key(signer_address);
let sequence_response = self.get_sequence(signer_address).await?;
let partial_tx = cosmrs::Tx {
body: tx::Body::new(messages, memo, 0u32),
auth_info: single_unspecified_signer_auth(public_key, sequence_response.sequence),
signatures: vec![Vec::new()],
};
self.query_simulate(Some(partial_tx), Vec::new()).await
// for completion sake, once we're able to transition into using `tx_bytes`,
// we might want to use something like this instead:
// let tx_raw: tx::Raw = cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
// body_bytes: partial_tx.body.into_bytes().unwrap(),
// auth_info_bytes: partial_tx.auth_info.into_bytes().unwrap(),
// signatures: partial_tx.signatures,
// }
// .into();
// self.query_simulate(None, tx_raw.to_bytes().unwrap()).await
}
// in this particular case we cannot generalise the argument to `&str` due to lifetime constraints
#[allow(clippy::ptr_arg)]
async fn determine_transaction_fee(
&self,
signer_address: &AccountId,
messages: &[Any],
fee: crate::nymd::fee::Fee,
memo: &String,
) -> Result<tx::Fee, NymdError> {
let auto_fee = |multiplier: Option<f32>| async move {
debug!("Trying to simulate gas costs...");
// from what I've seen in manual testing, gas estimation does not exist if transaction
// fails to get executed (for example if you send 'BondMixnode" with invalid signature)
let gas_estimation = self
.simulate(signer_address, messages.to_vec(), memo.clone())
.await?
.gas_info
.ok_or(NymdError::GasEstimationFailure)?
.gas_used;
let multiplier = multiplier.unwrap_or(DEFAULT_SIMULATED_GAS_MULTIPLIER);
let gas = gas_estimation.adjust_gas(multiplier);
debug!("Gas estimation: {}", gas_estimation);
debug!("Multiplying the estimation by {}", multiplier);
debug!("Final gas limit used: {}", gas);
let fee = self.gas_price() * gas;
Ok::<tx::Fee, NymdError>(tx::Fee::from_amount_and_gas(fee, gas))
};
let fee = match fee {
crate::nymd::fee::Fee::Manual(fee) => fee,
crate::nymd::fee::Fee::Auto(multiplier) => auto_fee(multiplier).await?,
crate::nymd::fee::Fee::PayerGranterAuto(auto_feegrant) => {
let mut fee = auto_fee(auto_feegrant.gas_adjustment).await?;
fee.payer = auto_feegrant.payer;
fee.granter = auto_feegrant.granter;
fee
}
};
debug!("Fee used for the transaction: {:?}", fee);
Ok(fee)
}
fn sign(
&self,
messages: String,
cosmrs_messages: Vec<Any>,
fee: tx::Fee,
memo: impl Into<String> + Send + 'static,
signer_data: SignerData,
) -> Result<tx::Raw, NymdError> {
let signature = self
.signer()
.sign_secp265k1(messages)
.expect("Could not sign")
.signature;
let response = self.signer().get_addr_secp265k1(false)?;
let verifying_key: cosmrs::crypto::secp256k1::VerifyingKey = response.public_key.into();
let cosmrs_public_key: cosmrs::crypto::PublicKey = verifying_key.into();
// TODO: WTF HOW IS TIMEOUT_HEIGHT SUPPOSED TO GET DETERMINED?
// IT DOESNT EXIST IN COSMJS!!
// try to set to 0
let timeout_height = 0u32;
let tx_body = tx::Body::new(cosmrs_messages, memo, timeout_height);
let mut signer_info =
SignerInfo::single_direct(Some(cosmrs_public_key), signer_data.sequence);
signer_info.mode_info = ModeInfo::Single(Single {
mode: SignMode::LegacyAminoJson,
});
let auth_info = signer_info.auth_info(fee);
// ideally I'd prefer to have the entire error put into the NymdError::SigningFailure
// but I'm super hesitant to trying to downcast the eyre::Report to cosmrs::error::Error
let sign_doc = SignDoc::new(
&tx_body,
&auth_info,
&signer_data.chain_id,
signer_data.account_number,
)
.map_err(|_| NymdError::SigningFailure)?;
Ok(cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
body_bytes: sign_doc.body_bytes,
auth_info_bytes: sign_doc.auth_info_bytes,
signatures: vec![signature.as_ref().to_vec()],
}
.into())
}
async fn send_tokens(
&self,
sender_address: &AccountId,
recipient_address: &AccountId,
amount: Vec<crate::nymd::Coin>,
fee: crate::nymd::fee::Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<TxResponse, NymdError> {
let cosmrs_msg = cosmrs::bank::MsgSend {
from_address: sender_address.clone(),
to_address: recipient_address.clone(),
amount: amount.iter().cloned().map(Into::into).collect(),
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
let memo = memo.into();
let fee = self
.determine_transaction_fee(sender_address, &vec![cosmrs_msg.clone()], fee, &memo)
.await?;
let response = self.signer().get_addr_secp265k1(false)?;
let sequence_response = self
.get_sequence(&AccountId::from_str(&response.address).unwrap())
.await?;
let chain_id = self.get_chain_id().await?;
let signer_data = SignerData {
account_number: sequence_response.account_number,
sequence: sequence_response.sequence,
chain_id,
};
let send_msg = MsgSend {
type_url: String::from("cosmos-sdk/MsgSend"),
value: SendValue {
from_address: sender_address.to_string(),
to_address: recipient_address.to_string(),
amount: amount.iter().cloned().map(Into::into).collect(),
},
};
let tx = SendTransaction {
account_number: signer_data.account_number.to_string(),
chain_id: signer_data.chain_id.to_string(),
fee: Fee {
amount: fee.amount.iter().cloned().map(Into::into).collect(),
gas: fee.gas_limit.to_string(),
},
memo: memo.clone(),
msgs: vec![send_msg],
sequence: signer_data.sequence.to_string(),
};
let tx_bytes = self
.sign(
serde_json::to_string(&tx).unwrap(),
vec![cosmrs_msg],
fee,
memo,
signer_data,
)?
.to_bytes()
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
self.broadcast_tx(tx_bytes.into()).await
}
async fn wallet_execute(
&self,
sender_address: &AccountId,
contract_address: &AccountId,
msg: &ExecuteMsg,
fee: crate::nymd::fee::Fee,
memo: impl Into<String> + Send + 'static,
funds: Vec<crate::nymd::Coin>,
) -> Result<ExecuteResult, NymdError> {
let cosmrs_msg = cosmrs::cosmwasm::MsgExecuteContract {
sender: sender_address.clone(),
contract: contract_address.clone(),
msg: serde_json::to_vec(msg)?,
funds: funds.iter().cloned().map(Into::into).collect(),
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))?;
let memo = memo.into();
let fee = self
.determine_transaction_fee(sender_address, &vec![cosmrs_msg.clone()], fee, &memo)
.await?;
let response = self.signer().get_addr_secp265k1(false)?;
let sequence_response = self
.get_sequence(&AccountId::from_str(&response.address).unwrap())
.await?;
let chain_id = self.get_chain_id().await?;
let signer_data = SignerData {
account_number: sequence_response.account_number,
sequence: sequence_response.sequence,
chain_id,
};
let execute_contract_msg = MsgExecuteContract {
type_url: String::from("wasm/MsgExecuteContract"),
value: ExecuteContractValue {
contract: contract_address.to_string(),
funds: funds.iter().cloned().map(Into::into).collect(),
msg: msg.clone(),
sender: sender_address.to_string(),
},
};
let tx = ExecuteContractTransaction {
account_number: signer_data.account_number.to_string(),
chain_id: signer_data.chain_id.to_string(),
fee: Fee {
amount: fee.amount.iter().cloned().map(Into::into).collect(),
gas: fee.gas_limit.to_string(),
},
memo: memo.clone(),
msgs: vec![execute_contract_msg],
sequence: signer_data.sequence.to_string(),
};
let tx_bytes = self
.sign(
serde_json::to_string(&tx).unwrap(),
vec![cosmrs_msg],
fee,
memo,
signer_data,
)?
.to_bytes()
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
let tx_res = self.broadcast_tx(tx_bytes.into()).await?.check_response()?;
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.tx_result.log)?,
data: tx_res.tx_result.data,
transaction_hash: tx_res.hash,
gas_info,
})
}
}
@@ -133,6 +133,9 @@ pub enum NymdError {
#[error("Account had an unexpected bech32 prefix. Expected: {expected}, got: {got}")]
UnexpectedBech32Prefix { got: String, expected: String },
#[error("Ledger error: {0}")]
LedgerError(#[from] ledger::error::LedgerError),
}
impl NymdError {
@@ -6,6 +6,7 @@ use crate::nymd::cosmwasm_client::types::{
Account, ChangeAdminResult, ContractCodeId, ExecuteResult, InstantiateOptions,
InstantiateResult, MigrateResult, SequenceResponse, SimulateResponse, UploadResult,
};
use crate::nymd::cosmwasm_client::wallet_client;
use crate::nymd::error::NymdError;
use crate::nymd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
@@ -28,12 +29,14 @@ use mixnet_contract_common::{
};
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::str::FromStr;
use std::time::SystemTime;
use vesting_contract_common::ExecuteMsg as VestingExecuteMsg;
use vesting_contract_common::QueryMsg as VestingQueryMsg;
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
pub use crate::nymd::cosmwasm_client::wallet_client::MinimalSigningCosmWasmClient;
pub use crate::nymd::fee::Fee;
pub use coin::Coin;
pub use cosmrs::bank::MsgSend;
@@ -52,9 +55,11 @@ pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::{bip32, AccountId, Decimal, Denom};
pub use cosmwasm_std::Coin as CosmWasmCoin;
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
use network_defaults::{ChainDetails, NymNetworkDetails};
use ledger::CosmosLedger;
use network_defaults::{ChainDetails, NymNetworkDetails, COSMOS_DERIVATION_PATH};
pub use signing_client::Client as SigningNymdClient;
pub use traits::{VestingQueryClient, VestingSigningClient};
pub use wallet_client::Client as WalletNymdClient;
pub mod coin;
pub mod cosmwasm_client;
@@ -209,6 +214,34 @@ impl NymdClient<SigningNymdClient> {
}
}
impl NymdClient<WalletNymdClient> {
pub fn connect_with_ledger<U: Clone>(
config: Config,
endpoint: U,
signer: CosmosLedger,
gas_price: Option<GasPrice>,
) -> Result<NymdClient<WalletNymdClient>, NymdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
let denom = &config.chain_details.mix_denom.base;
let client_address =
AccountId::from_str(&signer.get_addr_secp265k1(false).unwrap().address).unwrap();
let gas_price = gas_price.unwrap_or(GasPrice::new_with_default_price(denom)?);
Ok(NymdClient {
client: WalletNymdClient::connect_with_ledger(endpoint, signer, gas_price)?,
config,
client_address: Some(vec![client_address]),
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
})
}
pub fn ledger_signer(&self) -> CosmosLedger {
self.client.signer()
}
}
impl<C> NymdClient<C> {
pub fn current_config(&self) -> &Config {
&self.config
@@ -332,7 +365,7 @@ impl<C> NymdClient<C> {
pub fn address(&self) -> &AccountId
where
C: SigningCosmWasmClient,
C: MinimalSigningCosmWasmClient,
{
// if this is a signing client (as required by the trait bound), it must have the address set
&self.client_address.as_ref().unwrap()[0]
@@ -342,14 +375,14 @@ impl<C> NymdClient<C> {
where
C: SigningCosmWasmClient,
{
self.client.signer()
SigningCosmWasmClient::signer(&self.client)
}
pub fn gas_price(&self) -> &GasPrice
where
C: SigningCosmWasmClient,
C: MinimalSigningCosmWasmClient,
{
self.client.gas_price()
MinimalSigningCosmWasmClient::gas_price(&self.client)
}
pub fn gas_adjustment(&self) -> GasAdjustment {
@@ -896,6 +929,29 @@ impl<C> NymdClient<C> {
) -> Result<TxResponse, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
SigningCosmWasmClient::send_tokens(
&self.client,
self.address(),
recipient,
amount,
fee,
memo,
)
.await
}
/// Send funds from one address to another
pub async fn wallet_send(
&self,
recipient: &AccountId,
amount: Vec<Coin>,
memo: impl Into<String> + Send + 'static,
fee: Option<Fee>,
) -> Result<TxResponse, NymdError>
where
C: MinimalSigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
@@ -980,6 +1036,23 @@ impl<C> NymdClient<C> {
.await
}
pub async fn wallet_execute(
&self,
contract_address: &AccountId,
msg: &ExecuteMsg,
fee: Option<Fee>,
memo: impl Into<String> + Send + 'static,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError>
where
C: MinimalSigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.wallet_execute(self.address(), contract_address, msg, fee, memo, funds)
.await
}
pub async fn execute_multiple<I, M>(
&self,
contract_address: &AccountId,
+6 -1
View File
@@ -17,6 +17,11 @@ pub fn execute(attr: TokenStream, item: TokenStream) -> TokenStream {
} else {
panic!("Only `mixnet` and `vesting` targets are supported!")
};
let exe = if target == "mixnet" {
quote!(wallet_execute)
} else {
quote!(execute)
};
let cl = proc_macro::TokenStream::from(cl);
let cl = parse_macro_input!(cl as ExprMethodCall);
@@ -85,7 +90,7 @@ pub fn execute(attr: TokenStream, item: TokenStream) -> TokenStream {
let (req, fee) = self.#name(#(#execute_args),*);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.execute(
.#exe(
self.address(),
#cl,
&req,
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "ledger"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bip32 = "0.3.0"
k256 = "0.10.4"
ledger-transport = "0.10.0"
ledger-transport-hid = "0.10.0"
thiserror = "1"
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::LedgerError;
use crate::helpers::answer_bytes;
use bip32::{PublicKey, PublicKeyBytes};
use ledger_transport::APDUAnswer;
/// SECP265K1 address of the device.
pub struct AddrSecp265k1Response {
/// SECP265K1 public key.
pub public_key: k256::PublicKey,
/// String representation of the Cosmos address.
pub address: String,
}
impl TryFrom<APDUAnswer<Vec<u8>>> for AddrSecp265k1Response {
type Error = LedgerError;
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
let bytes = answer_bytes(&answer)?;
if bytes.len() < 33 {
return Err(Self::Error::InvalidAnswerLength {
expected: 33,
received: bytes.len(),
});
}
let (pub_key, addr) = bytes.split_at(33);
let public_key = k256::PublicKey::from_bytes(
PublicKeyBytes::try_from(pub_key).expect("Public key should be 33 bytes"),
)?;
let address = String::from_utf8(addr.to_vec()).unwrap();
Ok(AddrSecp265k1Response {
public_key,
address,
})
}
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use thiserror::Error;
pub(crate) type Result<T> = std::result::Result<T, LedgerError>;
/// Ledger specific errors.
#[derive(Debug, Error)]
pub enum LedgerError {
#[error("HID API - {0}")]
HidAPI(#[from] ledger_transport_hid::hidapi::HidError),
#[error("HID transport - {0}")]
HidTransport(#[from] ledger_transport_hid::LedgerHIDError),
#[error("Unknown error code - {err_code}")]
UnknownErrorCode { err_code: u16 },
#[error("APDU error - {reason}")]
APDU { reason: String },
#[error("Not enough bytes in answer. Expected at least {expected}, received {received}")]
InvalidAnswerLength { expected: usize, received: usize },
#[error("Not enough components in derivation path. Expected {expected}, received {received}")]
InvalidDerivationPath { expected: usize, received: usize },
#[error("Bip32 - {0}")]
Bip32(#[from] bip32::Error),
#[error("Signature error - {0}")]
Signature(#[from] k256::ecdsa::Error),
#[error("No message found for signing transaction")]
NoMessageFound,
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::{LedgerError, Result};
use bip32::DerivationPath;
use ledger_transport::{APDUAnswer, APDUErrorCode};
pub(crate) fn answer_bytes(answer: &APDUAnswer<Vec<u8>>) -> Result<&[u8]> {
let error_code = answer
.error_code()
.map_err(|err_code| LedgerError::UnknownErrorCode { err_code })?;
match error_code {
APDUErrorCode::NoError => Ok(answer.data()),
e => Err(LedgerError::APDU {
reason: e.description(),
}),
}
}
pub(crate) fn path_bytes(path: DerivationPath) -> Result<[[u8; 4]; 5]> {
let received = path.len();
let components: Vec<[u8; 4]> = path.into_iter().map(|c| c.0.to_le_bytes()).collect();
if components.len() != 5 {
Err(LedgerError::InvalidDerivationPath {
expected: 5,
received,
})
} else {
Ok([
components[0],
components[1],
components[2],
components[3],
components[4],
])
}
}
+140
View File
@@ -0,0 +1,140 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod addr_secp265k1;
pub mod error;
pub(crate) mod helpers;
mod sign_secp265k1;
pub mod version;
use crate::addr_secp265k1::AddrSecp265k1Response;
use crate::error::LedgerError;
use crate::helpers::path_bytes;
use crate::sign_secp265k1::SignSecp265k1Response;
use crate::version::VersionResponse;
use bip32::DerivationPath;
use error::Result;
use ledger_transport::APDUCommand;
use ledger_transport_hid::hidapi::HidApi;
use ledger_transport_hid::TransportNativeHID;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::sync::Arc;
const CLA: u8 = 0x55;
const INS_GET_VERSION: u8 = 0x00;
const INS_SIGN_SECP256K1: u8 = 0x02;
const INS_GET_ADDR_SECP256K1: u8 = 0x04;
const PAYLOAD_TYPE_INIT: u8 = 0x00;
const PAYLOAD_TYPE_ADD: u8 = 0x01;
const PAYLOAD_TYPE_LAST: u8 = 0x02;
const CHUNK_SIZE: usize = 250;
/// Manage hardware Ledger device with Cosmos specific operations, as described in the
/// specification: https://github.com/cosmos/ledger-cosmos/blob/main/docs/APDUSPEC.md
#[derive(Clone)]
pub struct CosmosLedger {
path: DerivationPath,
prefix: String,
transport: Arc<TransportNativeHID>,
}
impl Debug for CosmosLedger {
fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(f, "()")
}
}
impl CosmosLedger {
/// Create the connection to the first Ledger device that we can find.
pub fn new(path: DerivationPath, prefix: String) -> Result<Self> {
let api = HidApi::new()?;
let transport = Arc::new(TransportNativeHID::new(&api)?);
Ok(CosmosLedger {
path,
prefix,
transport,
})
}
/// Get the version of the device.
pub fn get_version(&self) -> Result<VersionResponse> {
let command = APDUCommand {
cla: CLA,
ins: INS_GET_VERSION,
p1: 0,
p2: 0,
data: vec![],
};
let response = self.transport.exchange(&command)?;
VersionResponse::try_from(response)
}
/// Get the SECP265K1 address of the device.
pub fn get_addr_secp265k1(&self, display: bool) -> Result<AddrSecp265k1Response> {
let display = if display { 1 } else { 0 };
let components = path_bytes(self.path.clone())?;
let data: Vec<u8> = vec![
[self.prefix.len() as u8].as_slice(),
self.prefix.as_bytes(),
components[0].as_slice(),
components[1].as_slice(),
components[2].as_slice(),
components[3].as_slice(),
components[4].as_slice(),
]
.into_iter()
.flatten()
.copied()
.collect();
let command = APDUCommand {
cla: CLA,
ins: INS_GET_ADDR_SECP256K1,
p1: display,
p2: 0,
data,
};
let response = self.transport.exchange(&command)?;
AddrSecp265k1Response::try_from(response)
}
pub fn sign_secp265k1(&self, message: String) -> Result<SignSecp265k1Response> {
let serialized_path: Vec<u8> = path_bytes(self.path.clone())?
.into_iter()
.flatten()
.collect();
let mut chunks = vec![serialized_path];
if message.is_empty() {
return Err(LedgerError::NoMessageFound);
}
for chunk in message.into_bytes().chunks(CHUNK_SIZE) {
chunks.push(chunk.to_vec());
}
let length = chunks.len();
for (idx, chunk) in chunks.into_iter().enumerate() {
let payload_desc = if idx == 0 {
PAYLOAD_TYPE_INIT
} else if idx + 1 == length {
PAYLOAD_TYPE_LAST
} else {
PAYLOAD_TYPE_ADD
};
let command = APDUCommand {
cla: CLA,
ins: INS_SIGN_SECP256K1,
p1: payload_desc,
p2: 0,
data: chunk,
};
let sign_response = self.transport.exchange(&command)?;
if payload_desc == PAYLOAD_TYPE_LAST {
return SignSecp265k1Response::try_from(sign_response);
}
}
// It should never reach this, as the message is not empty
Err(LedgerError::NoMessageFound)
}
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::LedgerError;
use crate::helpers::answer_bytes;
use k256::ecdsa::Signature;
use ledger_transport::APDUAnswer;
/// Version and status data of the device.
pub struct SignSecp265k1Response {
/// DER encoded signature data
pub signature: Signature,
}
impl TryFrom<APDUAnswer<Vec<u8>>> for SignSecp265k1Response {
type Error = LedgerError;
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
let bytes = answer_bytes(&answer)?;
Ok(SignSecp265k1Response {
signature: Signature::from_der(bytes)?,
})
}
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::LedgerError;
use crate::helpers::answer_bytes;
use ledger_transport::APDUAnswer;
/// Version and status data of the device.
pub struct VersionResponse {
/// Activation status of test mode.
pub test_mode: bool,
/// Major part of Cosmos application version.
pub major: u8,
/// Minor part of Cosmos application version.
pub minor: u8,
/// Patch part of Cosmos application version.
pub patch: u8,
/// PIN locked status.
pub device_locked: bool,
}
impl TryFrom<APDUAnswer<Vec<u8>>> for VersionResponse {
type Error = LedgerError;
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
let bytes = answer_bytes(&answer)?;
if bytes.len() != 5 {
return Err(Self::Error::InvalidAnswerLength {
expected: 5,
received: bytes.len(),
});
}
Ok(VersionResponse {
test_mode: bytes[0] != 0,
major: bytes[1],
minor: bytes[2],
patch: bytes[3],
device_locked: bytes[4] != 0,
})
}
}
+1
View File
@@ -22,6 +22,7 @@ ts-rs = "6.1.2"
cosmwasm-std = "1.0.0-beta8"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
ledger = { path = "../../common/ledger" }
validator-client = { path = "../../common/client-libs/validator-client", features = [
"nymd-client",
] }
+6
View File
@@ -70,6 +70,11 @@ pub enum TypesError {
LossyCoinConversion,
#[error("The provided coin has an unknown denomination - {0}")]
UnknownCoinDenom(String),
#[error("{source}")]
LedgerError {
#[from]
source: ledger::error::LedgerError,
},
}
impl Serialize for TypesError {
@@ -88,6 +93,7 @@ impl From<ValidatorClientError> for TypesError {
ValidatorClientError::MalformedUrlProvided(e) => e.into(),
ValidatorClientError::NymdError(e) => e.into(),
ValidatorClientError::NoAPIUrlAvailable => TypesError::NoValidatorApiUrlConfigured,
ValidatorClientError::LedgerError(e) => e.into(),
}
}
}
+102
View File
@@ -116,6 +116,12 @@ dependencies = [
"password-hash 0.4.1",
]
[[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.52"
@@ -1131,6 +1137,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "dotenv"
version = "0.15.0"
@@ -1996,6 +2008,17 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
[[package]]
name = "hidapi"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d26e1151deaab68f34fbfd16d491a2a0170cf98d69d3efa23873b567a4199e1"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "hmac"
version = "0.11.0"
@@ -2387,6 +2410,54 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "ledger"
version = "0.1.0"
dependencies = [
"bip32",
"k256",
"ledger-transport",
"ledger-transport-hid",
"thiserror",
]
[[package]]
name = "ledger-apdu"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe435806c197dfeaa5efcded5e623c4b8230fd28fdf1e91e7a86e40ef2acbf90"
dependencies = [
"arrayref",
"no-std-compat",
"snafu",
]
[[package]]
name = "ledger-transport"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1117f2143d92c157197785bf57711d7b02f2cfa101e162f8ca7900fb7f976321"
dependencies = [
"async-trait",
"ledger-apdu",
]
[[package]]
name = "ledger-transport-hid"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45ba81a1f5f24396b37211478aff7fbcd605dd4544df8dbed07b9da3c2057aee"
dependencies = [
"byteorder",
"cfg-if",
"hex",
"hidapi",
"ledger-transport",
"libc",
"log",
"thiserror",
]
[[package]]
name = "libc"
version = "0.2.119"
@@ -2654,6 +2725,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "no-std-compat"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
[[package]]
name = "nodrop"
version = "0.1.14"
@@ -2771,6 +2848,7 @@ dependencies = [
"cosmwasm-std",
"eyre",
"itertools",
"ledger",
"log",
"mixnet-contract-common",
"reqwest",
@@ -2841,6 +2919,7 @@ dependencies = [
"futures",
"itertools",
"k256",
"ledger",
"log",
"mixnet-contract-common",
"nym-types",
@@ -4342,6 +4421,28 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "snafu"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5177903bf45656592d9eb5c0e22f408fc023aae51dbe2088889b71633ba451f2"
dependencies = [
"doc-comment",
"snafu-derive",
]
[[package]]
name = "snafu-derive"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "410b26ed97440d90ced3e2488c868d56a86e2064f5d7d6f417909b286afe25e5"
dependencies = [
"heck 0.4.0",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "socket2"
version = "0.4.4"
@@ -5280,6 +5381,7 @@ dependencies = [
"flate2",
"futures",
"itertools",
"ledger",
"log",
"mixnet-contract-common",
"multisig-contract-common",
View File
+1
View File
@@ -61,6 +61,7 @@ mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
config = { path = "../../common/config" }
coconut-interface = { path = "../../common/coconut-interface" }
ledger = { path = "../../common/ledger" }
# Used for Type conversion, can be extracted but its a lot of work
vesting-contract = { path = "../../contracts/vesting" }
nym-types = { path = "../../common/types" }
+3
View File
@@ -120,6 +120,8 @@ pub enum BackendError {
SignatureError(String),
#[error("Unable to open a new window")]
NewWindowError,
#[error("{0}")]
LedgerError(#[from] ledger::error::LedgerError),
}
impl Serialize for BackendError {
@@ -140,6 +142,7 @@ impl From<ValidatorClientError> for BackendError {
ValidatorClientError::NoAPIUrlAvailable => {
TypesError::NoValidatorApiUrlConfigured.into()
}
ValidatorClientError::LedgerError(e) => e.into(),
}
}
}
@@ -16,7 +16,7 @@ use std::str::FromStr;
use strum::IntoEnumIterator;
use url::Url;
use validator_client::nymd::wallet::{AccountData, DirectSecp256k1HdWallet};
use validator_client::{nymd::SigningNymdClient, Client};
use validator_client::{nymd::WalletNymdClient, Client};
#[tauri::command]
pub async fn connect_with_mnemonic(
@@ -205,9 +205,9 @@ fn create_clients(
default_api_urls: &HashMap<WalletNetwork, Url>,
config: &Config,
mnemonic: &Mnemonic,
) -> Result<Vec<(WalletNetwork, Client<SigningNymdClient>)>, BackendError> {
) -> Result<Vec<(WalletNetwork, Client<WalletNymdClient>)>, BackendError> {
let mut clients = Vec::new();
for network in WalletNetwork::iter() {
for network in [WalletNetwork::MAINNET] {
let nymd_url = if let Some(url) = config.get_selected_validator_nymd_url(network) {
log::debug!("Using selected nymd_url for {network}: {url}");
url.clone()
@@ -252,7 +252,7 @@ fn create_clients(
let config = validator_client::Config::try_from_nym_network_details(&network_details)?
.with_urls(nymd_url, api_url);
let mut client = validator_client::Client::new_signing(config, mnemonic.clone())?;
let mut client = validator_client::Client::new_ledger(config)?;
client.set_nymd_simulated_gas_multiplier(CUSTOM_SIMULATED_GAS_MULTIPLIER);
clients.push((network, client));
}
@@ -30,7 +30,7 @@ pub async fn send(
let raw_res = guard
.current_client()?
.nymd
.send(&to_address, vec![amount_base], memo, fee)
.wallet_send(&to_address, vec![amount_base], memo, fee)
.await?;
log::info!("<<< tx hash = {}", raw_res.hash.to_string());
let res = SendTxResult::new(
+7 -7
View File
@@ -15,7 +15,7 @@ use strum::IntoEnumIterator;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use url::Url;
use validator_client::nymd::cosmwasm_client::types::SimulateResponse;
use validator_client::nymd::{AccountId as CosmosAccountId, Coin, Fee, SigningNymdClient};
use validator_client::nymd::{AccountId as CosmosAccountId, Coin, Fee, WalletNymdClient};
use validator_client::Client;
// Some hardcoded metadata overrides
@@ -63,7 +63,7 @@ impl WalletState {
#[derive(Default)]
pub struct WalletStateInner {
config: config::Config,
signing_clients: HashMap<Network, Client<SigningNymdClient>>,
signing_clients: HashMap<Network, Client<WalletNymdClient>>,
current_network: Network,
// All the accounts the we get from decrypting the wallet. We hold on to these for being able to
@@ -149,7 +149,7 @@ impl WalletStateInner {
Ok(FeeDetails::new(amount, res.to_fee()))
}
pub fn client(&self, network: Network) -> Result<&Client<SigningNymdClient>, BackendError> {
pub fn client(&self, network: Network) -> Result<&Client<WalletNymdClient>, BackendError> {
self.signing_clients
.get(&network)
.ok_or(BackendError::ClientNotInitialized)
@@ -158,20 +158,20 @@ impl WalletStateInner {
pub fn client_mut(
&mut self,
network: Network,
) -> Result<&mut Client<SigningNymdClient>, BackendError> {
) -> Result<&mut Client<WalletNymdClient>, BackendError> {
self.signing_clients
.get_mut(&network)
.ok_or(BackendError::ClientNotInitialized)
}
pub fn current_client(&self) -> Result<&Client<SigningNymdClient>, BackendError> {
pub fn current_client(&self) -> Result<&Client<WalletNymdClient>, BackendError> {
self.signing_clients
.get(&self.current_network)
.ok_or(BackendError::ClientNotInitialized)
}
#[allow(unused)]
pub fn current_client_mut(&mut self) -> Result<&mut Client<SigningNymdClient>, BackendError> {
pub fn current_client_mut(&mut self) -> Result<&mut Client<WalletNymdClient>, BackendError> {
self.signing_clients
.get_mut(&self.current_network)
.ok_or(BackendError::ClientNotInitialized)
@@ -191,7 +191,7 @@ impl WalletStateInner {
Ok(self.config.save_to_files()?)
}
pub fn add_client(&mut self, network: Network, client: Client<SigningNymdClient>) {
pub fn add_client(&mut self, network: Network, client: Client<WalletNymdClient>) {
self.signing_clients.insert(network, client);
}