Skip to content

Commit

Permalink
adding build tx functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Corbin committed Jun 20, 2023
1 parent 1e417de commit 103e9da
Show file tree
Hide file tree
Showing 15 changed files with 601 additions and 91 deletions.
388 changes: 377 additions & 11 deletions Cargo.lock

Large diffs are not rendered by default.

32 changes: 22 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ strip = "symbols"
# Fork and rename to use "OG" dalek-cryptography with latest dependencies.
bulletproofs-og = { git = "https://github.com/mobilecoinfoundation/bulletproofs.git", rev = "9abfdc054d9ba65f1e185ea1e6eff3947ce879dc" }

curve25519-dalek = { git = "https://github.com/dalek-cryptography/curve25519-dalek", rev = "99c0520aa79401b69fb51d38172cd58c6a256cfb" }

# Fix issues with recent nightlies, bump curve25519-dalek version
ed25519-dalek = { git = "https://github.com/dalek-cryptography/ed25519-dalek.git", rev = "2931c688eb11341a1145e257bc41d8ecbe36277c" }

# mbedtls patched to allow certificate verification with a profile
mbedtls = { git = "https://github.com/mobilecoinfoundation/rust-mbedtls.git", rev = "e966091845f27d49b0d9ba91fc99125e0c5f111c" }
mbedtls-sys-auto = { git = "https://github.com/mobilecoinfoundation/rust-mbedtls.git", rev = "e966091845f27d49b0d9ba91fc99125e0c5f111c" }
Expand All @@ -46,10 +41,27 @@ lmdb-rkv = { git = "https://github.com/mozilla/lmdb-rs", rev = "df1c2f5" }
# Fork and rename to use "OG" dalek-cryptography.
schnorrkel-og = { git = "https://github.com/mobilecoinfoundation/schnorrkel.git", rev = "b76d8c3a50671b08af0874b25b2543d3302d794d" }

# Fixes the following:
# * Allow enabling `serde/std` without also requiring `serde_cbor/std` to be enabled.
# See: https://github.com/pyfisch/cbor/pull/198
serde_cbor = { git = "https://github.com/mobilecoinofficial/cbor", rev = "4c886a7c1d523aae1ec4aa7386f402cb2f4341b5" }

# Fix issues with recent nightlies, bump curve25519-dalek version
x25519-dalek = { git = "https://github.com/mobilecoinfoundation/x25519-dalek.git", rev = "4fbaa3343301c62cfdbc3023c9f485257e6b718a" }

# Override ledger requirements with local versions
ledger-apdu = { path = "./ledger-mob/vendor/rs/ledger-apdu" }
ledger-transport = { path = "./ledger-mob/vendor/rs/ledger-transport" }

# Patch mc lib for git dependencies in ledger-mob
mc-core = { path = "./mobilecoin/core" }
mc-crypto-digestible = { path ="./mobilecoin/crypto/digestible" }
mc-crypto-hashes = { path ="./mobilecoin/crypto/hashes" }
mc-crypto-keys = { path ="./mobilecoin/crypto/keys" }
mc-crypto-ring-signature = { path ="./mobilecoin/crypto/ring-signature" }
mc-crypto-ring-signature-signer = { path ="./mobilecoin/crypto/ring-signature/signer" }
mc-transaction-core = { path = "./mobilecoin/transaction/core" }
mc-transaction-extra = { path = "./mobilecoin/transaction/extra" }
mc-transaction-signer = { path = "./mobilecoin/transaction/signer" }
mc-transaction-summary = { path = "./mobilecoin/transaction/summary" }
mc-transaction-types = { path = "./mobilecoin/transaction/types" }
mc-util-from-random = { path = "./mobilecoin/util/from-random" }

# latest mbedtls needs spin `^0.9.4`, but `mc-util-vec-map` resolves spin to `^0.9.2` through `heapless` `^0.7`,
# This specifies we use the latest version of heapless ~`0.9.4` to solve the dependency constraints.
heapless = { git = "https://github.com/mobilecoinofficial/heapless", rev = "2726f63bdc767d025f370d88341b1eb785178f2b" }
4 changes: 4 additions & 0 deletions full-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ mc-transaction-builder = { path = "../mobilecoin/transaction/builder" }
mc-transaction-core = { path = "../mobilecoin/transaction/core" }
mc-transaction-extra = { path = "../mobilecoin/transaction/extra" }
mc-transaction-signer = { path = "../mobilecoin/transaction/signer" }
mc-transaction-summary = { path = "../mobilecoin/transaction/summary"}
mc-transaction-types = { path = "../mobilecoin/transaction/types" }
mc-util-from-random = { path = "../mobilecoin/util/from-random" }
mc-util-parse = { path = "../mobilecoin/util/parse" }
Expand All @@ -47,6 +48,9 @@ mc-validator-connection = { path = "../validator/connection" }
mc-watcher = { path = "../mobilecoin/watcher" }
mc-watcher-api = { path = "../mobilecoin/watcher/api" }

ledger-mob = { path = "../ledger-mob/lib" }

async-trait = "0.1.59"
base64 = "0.21.2"
chrono = { version = "0.4", default-features = false, features = ["alloc"] }
clap = { version = "4.3", features = ["derive", "env"] }
Expand Down
10 changes: 5 additions & 5 deletions full-service/src/db/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,8 +389,8 @@ pub trait AccountModel {
/// * None
///
/// # Returns:
/// * Either an AccountKey or None
fn account_key(&self) -> Result<Option<AccountKey>, WalletDbError>;
/// * AccountKey
fn account_key(&self) -> Result<AccountKey, WalletDbError>;

/// Get the view only account key for the current account.
///
Expand Down Expand Up @@ -800,13 +800,13 @@ impl AccountModel for Account {
Ok(highest_subaddress_index as u64 + 1)
}

fn account_key(&self) -> Result<Option<AccountKey>, WalletDbError> {
fn account_key(&self) -> Result<AccountKey, WalletDbError> {
if self.view_only {
return Ok(None);
return Err(WalletDbError::AccountKeyNotAvailableForViewOnlyAccount);
}

let account_key: AccountKey = mc_util_serial::decode(&self.account_key)?;
Ok(Some(account_key))
Ok(account_key)
}

fn view_account_key(&self) -> Result<ViewAccountKey, WalletDbError> {
Expand Down
3 changes: 3 additions & 0 deletions full-service/src/db/wallet_db_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ pub enum WalletDbError {

/// ed25519-dalek error
Dalek(ed25519_dalek::ed25519::Error),

/// Account key is not available for a view only account
AccountKeyNotAvailableForViewOnlyAccount,
}

impl From<diesel::result::Error> for WalletDbError {
Expand Down
10 changes: 7 additions & 3 deletions full-service/src/json_rpc/v1/api/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ use rocket::{self, serde::json::Json};
use serde_json::Map;
use std::{collections::HashMap, convert::TryFrom, iter::FromIterator};

pub fn generic_wallet_api<T, FPR>(
pub async fn generic_wallet_api<T, FPR>(
_api_key_guard: ApiKeyGuard,
state: &rocket::State<WalletState<T, FPR>>,
command: Json<JsonRPCRequest>,
Expand All @@ -87,7 +87,7 @@ where
}
};

match wallet_api_inner(&state.service, request) {
match wallet_api_inner(&state.service, request).await {
Ok(command_response) => {
global_log::info!("Command executed successfully");
response.result = Some(command_response);
Expand All @@ -107,7 +107,7 @@ where
/// take explicit Rocket state, and then pass the service to the inner method.
/// This allows us to properly construct state with Mock Connection Objects in
/// tests. This also allows us to version the overall API easily.
pub fn wallet_api_inner<T, FPR>(
pub async fn wallet_api_inner<T, FPR>(
service: &WalletService<T, FPR>,
command: JsonCommandRequest,
) -> Result<JsonCommandResponse, JsonRPCError>
Expand Down Expand Up @@ -172,6 +172,7 @@ where
TransactionMemo::RTH(None, None),
None,
)
.await
.map_err(format_error)?;

JsonCommandResponse::build_and_submit_transaction {
Expand Down Expand Up @@ -209,6 +210,7 @@ where
.transpose()
.map_err(format_error)?,
)
.await
.map_err(format_error)?;
JsonCommandResponse::build_gift_code {
tx_proposal: TxProposal::try_from(&tx_proposal).map_err(format_error)?,
Expand All @@ -234,6 +236,7 @@ where
Some(Mob::ID.to_string()),
tombstone_block,
)
.await
.map_err(format_error)?;
JsonCommandResponse::build_split_txo_transaction {
tx_proposal: TxProposal::try_from(&tx_proposal).map_err(format_error)?,
Expand Down Expand Up @@ -283,6 +286,7 @@ where
TransactionMemo::RTH(None, None),
None,
)
.await
.map_err(format_error)?;

JsonCommandResponse::build_transaction {
Expand Down
4 changes: 4 additions & 0 deletions full-service/src/json_rpc/v2/api/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ where
),
block_version,
)
.await
.map_err(format_error)?;

JsonCommandResponse::build_and_submit_transaction {
Expand Down Expand Up @@ -257,6 +258,7 @@ where
TransactionMemo::BurnRedemption(memo_data),
block_version,
)
.await
.map_err(format_error)?;

JsonCommandResponse::build_burn_transaction {
Expand Down Expand Up @@ -316,6 +318,7 @@ where
),
block_version,
)
.await
.map_err(format_error)?;

JsonCommandResponse::build_transaction {
Expand Down Expand Up @@ -442,6 +445,7 @@ where
b58_data.insert("value".to_string(), payment_request.value.to_string());
b58_data.insert("token_id".to_string(), payment_request.token_id.to_string());
b58_data.insert("memo".to_string(), payment_request.memo);
b58_data.insert("token_id".to_string(), payment_request.token_id.to_string());
}
}
JsonCommandResponse::check_b58_type {
Expand Down
8 changes: 4 additions & 4 deletions full-service/src/json_rpc/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,21 +131,21 @@ fn wallet_help_v1() -> Result<String, String> {

/// The route for the Full Service Wallet API.
#[post("/wallet", format = "json", data = "<command>")]
fn consensus_backed_wallet_api_v1(
async fn consensus_backed_wallet_api_v1(
_api_key_guard: ApiKeyGuard,
state: &rocket::State<WalletState<ThickClient<HardcodedCredentialsProvider>, FogResolver>>,
command: Json<JsonRPCRequest>,
) -> Result<Json<JsonRPCResponse<JsonCommandResponse_v1>>, String> {
generic_wallet_api_v1(_api_key_guard, state, command)
generic_wallet_api_v1(_api_key_guard, state, command).await
}

#[post("/wallet", format = "json", data = "<command>")]
fn validator_backed_wallet_api_v1(
async fn validator_backed_wallet_api_v1(
_api_key_guard: ApiKeyGuard,
state: &rocket::State<WalletState<ValidatorConnection, FogResolver>>,
command: Json<JsonRPCRequest>,
) -> Result<Json<JsonRPCResponse<JsonCommandResponse_v1>>, String> {
generic_wallet_api_v1(_api_key_guard, state, command)
generic_wallet_api_v1(_api_key_guard, state, command).await
}

#[get("/wallet/v2")]
Expand Down
16 changes: 6 additions & 10 deletions full-service/src/service/gift_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ pub enum GiftCodeStatus {
/// gift codes.
#[rustfmt::skip]
#[allow(clippy::result_large_err)]
#[async_trait]
pub trait GiftCodeService {
/// Builds a new gift code.
///
Expand Down Expand Up @@ -372,7 +373,7 @@ pub trait GiftCodeService {
///| `max_spendable_value` | The maximum amount for an input TXO selected for this transaction. | |
///
#[allow(clippy::too_many_arguments)]
fn build_gift_code(
async fn build_gift_code(
&self,
from_account_id: &AccountID,
value: u64,
Expand Down Expand Up @@ -476,12 +477,13 @@ pub trait GiftCodeService {
) -> Result<bool, GiftCodeServiceError>;
}

#[async_trait]
impl<T, FPR> GiftCodeService for WalletService<T, FPR>
where
T: BlockchainConnection + UserTxConnection + 'static,
FPR: FogPubkeyResolver + Send + Sync + 'static,
{
fn build_gift_code(
async fn build_gift_code(
&self,
from_account_id: &AccountID,
value: u64,
Expand Down Expand Up @@ -524,7 +526,7 @@ where

let fee_value = fee.map(|f| f.to_string());

let signing_data = self.build_transaction(
let unsigned_tx_proposal = self.build_transaction(
&from_account.id,
&[(
gift_code_account_main_subaddress_b58,
Expand All @@ -542,13 +544,7 @@ where
None,
)?;

let account_key: AccountKey = mc_util_serial::decode(&from_account.account_key)?;
let fee_map = if self.offline {
None
} else {
Some(self.get_network_fees()?)
};
let tx_proposal = signing_data.sign(&account_key, fee_map.as_ref())?;
let tx_proposal = unsigned_tx_proposal.sign(&from_account).await?;

if tx_proposal.payload_txos.len() != 1 {
return Err(GiftCodeServiceError::UnexpectedTxProposalFormat);
Expand Down
97 changes: 97 additions & 0 deletions full-service/src/service/hardware_wallet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) 2020-2021 MobileCoin Inc.

//! Service for managing ledger materials and MobileCoin protocol objects.
use std::convert::TryFrom;

use ledger_mob::{transport::GenericTransport, Connect, DeviceHandle, LedgerProvider};

use mc_common::logger::global_log;
use mc_core::keys::TxOutPublic;
use mc_crypto_keys::RistrettoPublic;
use strum::Display;

use crate::service::models::tx_proposal::{InputTxo, TxProposal, UnsignedTxProposal};

/// Errors for the Address Service.
#[derive(Display, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum HardwareWalletServiceError {
NoHardwareWalletsFound,
LedgerMob(ledger_mob::Error),
PresignedRingsNotSupported,
KeyImageNotFoundForSignedInput,
RingCT(mc_transaction_core::ring_ct::Error),
CryptoKeys(mc_crypto_keys::KeyError),
}

impl From<mc_transaction_core::ring_ct::Error> for HardwareWalletServiceError {
fn from(src: mc_transaction_core::ring_ct::Error) -> Self {
HardwareWalletServiceError::RingCT(src)
}
}

impl From<mc_crypto_keys::KeyError> for HardwareWalletServiceError {
fn from(src: mc_crypto_keys::KeyError) -> Self {
HardwareWalletServiceError::CryptoKeys(src)
}
}

impl From<ledger_mob::Error> for HardwareWalletServiceError {
fn from(src: ledger_mob::Error) -> Self {
HardwareWalletServiceError::LedgerMob(src)
}
}

async fn get_device_handle() -> Result<DeviceHandle<GenericTransport>, HardwareWalletServiceError> {
let ledger_provider = LedgerProvider::new().unwrap();
let devices = ledger_provider.list_devices(ledger_mob::Filter::Hid).await;

if devices.len() == 0 {
return Err(HardwareWalletServiceError::NoHardwareWalletsFound);
}

global_log::info!("Found devices: {:04x?}", devices);

// Connect to the first device
//
// This CBB - we should iterate through each device if signing fails on the
// current one and more are available
Ok(Connect::<GenericTransport>::connect(&ledger_provider, &devices[0]).await?)
}

pub async fn sign_tx_proposal(
unsigned_tx_proposal: UnsignedTxProposal,
) -> Result<TxProposal, HardwareWalletServiceError> {
let device_handle = get_device_handle().await?;

global_log::debug!("Signing tx proposal with hardware device");
let (tx, txos_synced) = device_handle
.transaction(0, 60, unsigned_tx_proposal.unsigned_tx)
.await?;

let mut input_txos = vec![];

for txo in unsigned_tx_proposal.unsigned_input_txos {
let tx_out_public_key = RistrettoPublic::try_from(&txo.tx_out.public_key)?;
let key_image = txos_synced
.iter()
.find(|txo| txo.tx_out_public_key == TxOutPublic::from(tx_out_public_key))
.ok_or(HardwareWalletServiceError::KeyImageNotFoundForSignedInput)?
.key_image;

input_txos.push(InputTxo {
tx_out: txo.tx_out,
subaddress_index: txo.subaddress_index,
key_image,
amount: txo.amount,
});
}

Ok(TxProposal {
tx,
input_txos,
payload_txos: unsigned_tx_proposal.payload_txos,
change_txos: unsigned_tx_proposal.change_txos,
})
}
1 change: 1 addition & 0 deletions full-service/src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod address;
pub mod balance;
pub mod confirmation_number;
pub mod gift_code;
pub mod hardware_wallet;
pub mod ledger;
pub mod models;
pub mod network;
Expand Down
Loading

0 comments on commit 103e9da

Please sign in to comment.