diff --git a/applications/tari_swarm_daemon/src/process_manager/processes/wallet_daemon.rs b/applications/tari_swarm_daemon/src/process_manager/processes/wallet_daemon.rs index 17dc926a5d..acec507917 100644 --- a/applications/tari_swarm_daemon/src/process_manager/processes/wallet_daemon.rs +++ b/applications/tari_swarm_daemon/src/process_manager/processes/wallet_daemon.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: BSD-3-Clause use anyhow::anyhow; -use tari_ootle_wallet_sdk::apis::key_manager::KeyBranch; +use tari_ootle_wallet_sdk::models::KeyBranch; use tari_template_lib_types::crypto::RistrettoPublicKeyBytes; use tari_wallet_daemon_client::{ types::{AuthLoginAcceptRequest, AuthLoginRequest, AuthLoginResponse, WebauthnFinishAuthRequest}, diff --git a/applications/tari_wallet_cli/src/command/key.rs b/applications/tari_wallet_cli/src/command/key.rs index b20d8a6a76..9cc0650f3f 100644 --- a/applications/tari_wallet_cli/src/command/key.rs +++ b/applications/tari_wallet_cli/src/command/key.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use clap::Subcommand; -use tari_ootle_wallet_sdk::{apis::key_manager::KeyBranch, models::KeyId}; +use tari_ootle_wallet_sdk::models::{KeyBranch, KeyId}; use tari_template_lib::prelude::RistrettoPublicKeyBytes; use tari_wallet_daemon_client::WalletDaemonClient; diff --git a/applications/tari_walletd/src/handlers/accounts.rs b/applications/tari_walletd/src/handlers/accounts.rs index 83e36084e9..655a7b648e 100644 --- a/applications/tari_walletd/src/handlers/accounts.rs +++ b/applications/tari_walletd/src/handlers/accounts.rs @@ -25,11 +25,10 @@ use tari_ootle_wallet_crypto::{ use tari_ootle_wallet_sdk::{ apis::{ confidential_transfer::ConfidentialTransferParams, - key_manager::KeyBranch, stealth_transfer::StealthTransferParams, substate::ValidatorScanResult, }, - models::NewAccountData, + models::{KeyBranch, KeyId, NewAccountData}, }; use tari_ootle_wallet_sdk_services::events::TransactionSubmittedEvent; use tari_template_builtin::ACCOUNT_TEMPLATE_ADDRESS; @@ -429,6 +428,8 @@ pub async fn handle_claim_burn( .ok_or_else(|| invalid_params("account", Some("cannot claim burn to an account without an owner key")))?; let network = sdk.config_api().get_network()?; + // We derive secrets directly here because claim burn is a unique case, making it difficult to use the higher + // level stealth output api that takes care of keys but assumes that this is a regular transfer. let claim_nonce_keypair = sdk .key_manager_api() .derive_keypair(KeyBranch::Nonce, owner_nonce_key_index)?; @@ -553,7 +554,14 @@ pub async fn handle_claim_burn( .pay_fee_stealth(pay_fee_and_mint_output) }) .add_input(XTR) - .build_and_seal(claim_nonce_keypair.secret_key()); + .build(); + + // The signer does not authorize this transaction, as the claim burn instruction is authorized by the proofs. So we + // can sign with any key. + let nonce = sdk.key_manager_api().next_public_key(KeyBranch::Nonce)?; + let transaction = sdk + .local_signer_api() + .sign(KeyBranch::Nonce, nonce.key_id, transaction)?; let tx_id = context.transaction_service().submit_transaction(transaction).await?; @@ -635,8 +643,6 @@ pub async fn handle_create_free_test_coins( ); } - let account_owner_key = sdk.key_manager_api().get_account_owner_key(account_owner_key_id)?; - let transaction = context .transaction_builder() .with_fee_instructions_builder(|fee_builder| { @@ -656,7 +662,11 @@ pub async fn handle_create_free_test_coins( .call_method(*account.component_address(), "pay_fee", args![max_fee]) }) .with_inputs(inputs.into_iter().map(|input| input.into_unversioned())) - .build_and_seal(&account_owner_key.secret); + .build(); + + let transaction = sdk + .local_signer_api() + .sign(KeyBranch::Account, account_owner_key_id, transaction)?; info!( target: LOG_TARGET, @@ -815,7 +825,6 @@ pub async fn handle_transfer( // build the transaction let max_fee = req.max_fee.unwrap_or(DEFAULT_FEE); - let account_owner_key = sdk.key_manager_api().get_account_owner_key(account_owner_key_id)?; let transaction = builder .with_dry_run(req.dry_run) @@ -844,7 +853,11 @@ pub async fn handle_transfer( } }) .with_inputs(inputs.into_iter().map(|req| req.into_unversioned())) - .build_and_seal(&account_owner_key.secret); + .build(); + + let transaction = sdk + .local_signer_api() + .sign(KeyBranch::Account, account_owner_key_id, transaction)?; // If dry run we can return the result immediately if req.dry_run { @@ -976,18 +989,20 @@ pub async fn handle_stealth_transfer( let must_sign_with_account_key = transfer.fee_inputs.revealed.is_positive() || transfer.transfer_inputs.revealed.is_positive(); - let signer_key = if must_sign_with_account_key { - sdk.key_manager_api().get_account_owner_key(owner_key_id)? + + let transaction = transfer.transaction.authorized_sealed_signer().build(vec![]); + + let (key_branch, key_id) = if must_sign_with_account_key { + (KeyBranch::Account, owner_key_id) } else { // Since we don't require account auth, use a throwaway nonce to sign the transaction - sdk.key_manager_api().next_key(KeyBranch::Nonce)?.into() + ( + KeyBranch::Nonce, + KeyId::derived(sdk.key_manager_api().next_derived_key_index(KeyBranch::Nonce)?), + ) }; - let transaction = transfer - .transaction - .authorized_sealed_signer() - .build(vec![]) - .seal(&signer_key.secret); + let transaction = sdk.local_signer_api().sign(key_branch, key_id, transaction)?; // TODO: if submitting fails we need to unlock the inputs again if req.dry_run { diff --git a/applications/tari_walletd/src/handlers/confidential.rs b/applications/tari_walletd/src/handlers/confidential.rs index d8753b809c..72cac3330d 100644 --- a/applications/tari_walletd/src/handlers/confidential.rs +++ b/applications/tari_walletd/src/handlers/confidential.rs @@ -12,10 +12,7 @@ use serde_json::json; use tari_crypto::{commitment::HomomorphicCommitmentFactory, keys::PublicKey as _, ristretto::RistrettoPublicKey}; use tari_engine_types::{crypto::get_commitment_factory, ToByteType}; use tari_ootle_wallet_crypto::{AlwaysMissLookupTable, IoReaderValueLookup, UnblindedOutputWitness}; -use tari_ootle_wallet_sdk::{ - apis::key_manager::KeyBranch, - models::{ConfidentialOutputModel, OutputStatus}, -}; +use tari_ootle_wallet_sdk::models::{ConfidentialOutputModel, KeyBranch, OutputStatus}; use tari_template_lib::types::Amount; use tari_wallet_daemon_client::{ permissions::JrpcPermission, @@ -290,9 +287,7 @@ pub async fn handle_view_vault_balance( .ok_or_else(|| invalid_params("vault_id", Some("Vault does not contain a confidential resource")))?; // Get view secret key - let view_key = sdk - .key_manager_api() - .derive_key(KeyBranch::ElgamalEncryptionViewKey, req.view_key_id)?; + let view_key = sdk.key_manager_api().get_elgamal_encrypted_view_key(req.view_key_id)?; let value_range = req.minimum_expected_value.unwrap_or(0)..=req.maximum_expected_value.unwrap_or(10_000_000_000); diff --git a/applications/tari_walletd/src/handlers/keys.rs b/applications/tari_walletd/src/handlers/keys.rs index 5cfa5807e9..19df372857 100644 --- a/applications/tari_walletd/src/handlers/keys.rs +++ b/applications/tari_walletd/src/handlers/keys.rs @@ -2,9 +2,8 @@ // SPDX-License-Identifier: BSD-3-Clause use axum_extra::headers::authorization::Bearer; -use tari_crypto::{keys::PublicKey as PublicKeyTrait, ristretto::RistrettoPublicKey}; use tari_engine_types::ToByteType; -use tari_ootle_wallet_sdk::apis::key_manager::KeyBranch; +use tari_ootle_wallet_sdk::models::{KeyBranch, KeyId}; use tari_wallet_daemon_client::{ permissions::JrpcPermission, types::{ @@ -29,11 +28,11 @@ pub async fn handle_create( let key_manager = sdk.key_manager_api(); let key = req .specific_index - .map(|idx| key_manager.derive_key(req.branch, idx)) - .unwrap_or_else(|| key_manager.next_key(req.branch))?; + .map(|idx| key_manager.get_public_key(req.branch, KeyId::derived(idx))) + .unwrap_or_else(|| key_manager.next_public_key(req.branch))?; Ok(KeysCreateResponse { - id: key.key_index, - public_key: RistrettoPublicKey::from_secret_key(&key.key).to_byte_type(), + id: key.key_id.derived_index().expect("Key is derived"), + public_key: key.public_key.to_byte_type(), }) } diff --git a/applications/tari_walletd/src/handlers/nfts.rs b/applications/tari_walletd/src/handlers/nfts.rs index 1ac733372c..7266a6b7b2 100644 --- a/applications/tari_walletd/src/handlers/nfts.rs +++ b/applications/tari_walletd/src/handlers/nfts.rs @@ -13,7 +13,7 @@ use tari_engine_types::{ ToByteType, }; use tari_ootle_common_types::{optional::Optional, SubstateRequirement}; -use tari_ootle_wallet_sdk::apis::substate::ValidatorScanResult; +use tari_ootle_wallet_sdk::{apis::substate::ValidatorScanResult, models::KeyBranch}; use tari_template_builtin::ACCOUNT_TEMPLATE_ADDRESS; use tari_template_lib::{ constants::{NFT_FAUCET_COMPONENT_ADDRESS, NFT_FAUCET_RESOURCE_ADDRESS}, @@ -85,7 +85,6 @@ pub async fn handle_mint_faucet( req: MintFaucetNftRequest, ) -> Result { let sdk = context.wallet_sdk(); - let key_manager_api = sdk.key_manager_api(); context.check_auth(token, &[JrpcPermission::Admin])?; let account = get_account(&req.account, &sdk.accounts_api())?; @@ -95,8 +94,6 @@ pub async fn handle_mint_faucet( .owner_key_id .ok_or_else(|| invalid_params("account", Some("The account does not have an owner key ID")))?; - let signing_key = key_manager_api.get_account_owner_key(account_owner_key_id)?; - info!(target: LOG_TARGET, "🎮 Minting new NFT with metadata {}", req.mutable_data); let mutable_data = convert_json_to_cbor(req.mutable_data).map_err(|e| invalid_params("mutable_data", Some(e)))?; @@ -122,7 +119,11 @@ pub async fn handle_mint_faucet( .with_inputs(inputs.into_iter().map(|input| input.into_unversioned())) .add_input(NFT_FAUCET_COMPONENT_ADDRESS) .add_input(NFT_FAUCET_RESOURCE_ADDRESS) - .build_and_seal(&signing_key.secret); + .build(); + + let transaction = sdk + .local_signer_api() + .sign(KeyBranch::Account, account_owner_key_id, transaction)?; let mut events = context.notifier().subscribe(); let tx_id = context.transaction_service().submit_transaction(transaction).await?; @@ -292,9 +293,9 @@ pub async fn handle_transfer( .call_method(target_account_address, "deposit", args![Workspace(format!("b-{i}"))]); } - let fee_owner_key = sdk.key_manager_api().get_account_owner_key(fee_payer_key_id)?; - - let source_account_secret_key = sdk.key_manager_api().get_account_owner_key(account_owner_key_id)?; + let fee_owner_key = sdk + .key_manager_api() + .get_public_key(KeyBranch::Account, fee_payer_key_id)?; let transaction = builder .with_dry_run(req.dry_run) @@ -302,11 +303,19 @@ pub async fn handle_transfer( .with_inputs(inputs.into_iter().map(|input| input.into_unversioned())) // Seal signer is the fee payer account .with_authorized_seal_signer() - .add_signer( - &fee_owner_key.to_public_key().to_byte_type(), - &source_account_secret_key.secret, - ) - .build_and_seal(&fee_owner_key.secret); + .then(|builder| { + sdk.local_signer_api().sign_with_context( + KeyBranch::Account, + account_owner_key_id, + &fee_owner_key.public_key().to_byte_type(), + builder, + ) + })? + .build(); + + let transaction = sdk + .local_signer_api() + .sign(KeyBranch::Account, account_owner_key_id, transaction)?; // if dry run, we can return the result immediately if req.dry_run { diff --git a/applications/tari_walletd/src/handlers/stealth_utxos.rs b/applications/tari_walletd/src/handlers/stealth_utxos.rs index 1d5c683772..4493262eef 100644 --- a/applications/tari_walletd/src/handlers/stealth_utxos.rs +++ b/applications/tari_walletd/src/handlers/stealth_utxos.rs @@ -8,7 +8,6 @@ use axum_extra::headers::authorization::Bearer; use indexmap::IndexMap; use log::info; use tari_ootle_wallet_crypto::{AlwaysMissLookupTable, IoReaderValueLookup}; -use tari_ootle_wallet_sdk::apis::key_manager::KeyBranch; use tari_template_lib::models::UtxoAddress; use tari_wallet_daemon_client::{ permissions::JrpcPermission, @@ -80,9 +79,7 @@ pub async fn handle_decrypt_value( let substates = sdk.substate_api().get_substates_from_network(utxo_ids).await?; // Get view secret key - let view_key = sdk - .key_manager_api() - .derive_key(KeyBranch::ElgamalEncryptionViewKey, req.view_key_id)?; + let view_key = sdk.key_manager_api().get_elgamal_encrypted_view_key(req.view_key_id)?; let value_range = req.minimum_expected_value.unwrap_or(0)..=req.maximum_expected_value.unwrap_or(10_000_000_000); diff --git a/applications/tari_walletd/src/handlers/transaction.rs b/applications/tari_walletd/src/handlers/transaction.rs index 7d7c5e33fe..36a143eb14 100644 --- a/applications/tari_walletd/src/handlers/transaction.rs +++ b/applications/tari_walletd/src/handlers/transaction.rs @@ -10,7 +10,8 @@ use log::*; use tari_engine_types::ToByteType; use tari_ootle_common_types::{optional::Optional, Epoch, Network}; use tari_ootle_wallet_sdk::{ - apis::{config::ConfigKey, key_manager::KeyBranch, transaction::TransactionApiError}, + apis::{config::ConfigKey, transaction::TransactionApiError}, + models::KeyBranch, network::WalletQueryErrorStatus, }; use tari_ootle_wallet_sdk_services::{events::WalletEvent, transaction_service::TransactionServiceError}; @@ -104,8 +105,7 @@ pub async fn handle_submit( let sdk = context.wallet_sdk(); let key_api = sdk.key_manager_api(); // Fetch the key to sign the transaction - // TODO: Ideally the SDK should take care of signing the transaction internally - let key = key_api.get_key_or_active(KeyBranch::Account, req.signing_key_id)?; + let signing_key = key_api.get_key_or_active(KeyBranch::Account, req.signing_key_id)?; let detected_inputs = if req.detect_inputs { // If we are not overriding inputs, we will use inputs that we know about in the local substate id db @@ -149,7 +149,10 @@ pub async fn handle_submit( .transaction_builder() .with_unsigned_transaction(req.transaction) .with_inputs(detected_inputs) - .build_and_seal(&key.secret); + .build(); + let transaction = sdk + .local_signer_api() + .sign(KeyBranch::Account, signing_key.key_id, transaction)?; if log_enabled!(log::Level::Debug) { for input in transaction.inputs() { @@ -212,7 +215,6 @@ pub async fn handle_submit_dry_run( let sdk = context.wallet_sdk(); let key_api = sdk.key_manager_api(); // Fetch the key to sign the transaction - // TODO: Ideally the SDK should take care of signing the transaction internally let key = key_api.get_key_or_active(KeyBranch::Account, req.signing_key_id)?; let detected_inputs = if req.detect_inputs { @@ -242,7 +244,10 @@ pub async fn handle_submit_dry_run( .with_unsigned_transaction(req.transaction) .with_inputs(detected_inputs) .with_dry_run(true) - .build_and_seal(&key.secret); + .build(); + let transaction = sdk + .local_signer_api() + .sign(KeyBranch::Account, key.key_id, transaction)?; for proof_id in req.proof_ids { // update the proofs table with the corresponding transaction hash @@ -307,13 +312,11 @@ pub async fn handle_submit_manifest( let key = sdk .key_manager_api() .get_key_or_active(KeyBranch::Account, Some(signing_key_id))?; - let seal_signer_pk = key.to_public_key(); let network = context.wallet_sdk().config_api().get::(ConfigKey::Network)?; let fee_amount = req.max_fee; - let acc_key = sdk.key_manager_api().get_account_owner_key(account_owner_key_id)?; let builder = Transaction::builder() .for_network(network.as_byte()) .with_fee_instructions_builder(|builder| { @@ -326,28 +329,32 @@ pub async fn handle_submit_manifest( .with_instructions(instructions.instructions) .then(|builder| { if signing_key_id == account_owner_key_id { - builder + Ok(builder) } else { - builder.add_signer(&seal_signer_pk.to_byte_type(), &acc_key.secret) + sdk.local_signer_api().sign_with_context( + KeyBranch::Account, + signing_key_id, + &key.public_key().to_byte_type(), + builder, + ) } - }); + })?; let signatures = builder.signatures().to_vec(); - let mut transaction = builder.build_unsigned_transaction(); + let transaction = builder.with_dry_run(req.dry_run).build_unsigned_transaction(); // Detect inputs - let substates = transaction.to_referenced_substates()?; - let substates = substates.into_iter().collect::>(); + let substates = transaction.to_referenced_substates()?.into_iter().collect::>(); let dependencies = sdk.substate_api().locate_dependent_substates(&substates, true).await?; let inputs = dependencies.into_iter().map(|input| input.into_unversioned()); - // set currently requested dry run status - transaction.set_dry_run(req.dry_run); - let transaction = transaction .with_inputs(inputs) .authorized_sealed_signer() - .build(signatures) - .seal(&key.secret); + .build(signatures); + + let transaction = sdk + .local_signer_api() + .sign(KeyBranch::Account, key.key_id, transaction)?; if req.dry_run { let exec_result = context diff --git a/applications/tari_walletd/src/handlers/validator.rs b/applications/tari_walletd/src/handlers/validator.rs index 3c7957a65a..440e4c5531 100644 --- a/applications/tari_walletd/src/handlers/validator.rs +++ b/applications/tari_walletd/src/handlers/validator.rs @@ -9,6 +9,7 @@ use either::Either; use log::*; use tari_engine_types::{substate::SubstateId, ToByteType}; use tari_ootle_common_types::{derive_fee_pool_address, SubstateAddress, SubstateRequirement}; +use tari_ootle_wallet_sdk::models::{KeyBranch, KeyId}; use tari_template_lib::constants::XTR; use tari_transaction::args; use tari_wallet_daemon_client::{ @@ -48,11 +49,12 @@ pub async fn handle_get_validator_fees( let account_key_id = account.owner_key_id().ok_or_else(|| { anyhow!("The specified account does not have an associated owner key to derive the claim key from") })?; - sdk.key_manager_api().get_account_owner_key(account_key_id)? + sdk.key_manager_api() + .get_public_key(KeyBranch::Account, account_key_id)? }, - AccountOrKeyId::KeyId(key_id) => sdk.key_manager_api().get_account_owner_key(key_id)?, + AccountOrKeyId::KeyId(key_id) => sdk.key_manager_api().get_public_key(KeyBranch::Account, key_id)?, }; - let claim_public_key = claim_key.to_public_key().to_byte_type(); + let claim_public_key = claim_key.public_key().to_byte_type(); let shards = req .shard_group @@ -115,25 +117,25 @@ pub async fn handle_claim_validator_fees( anyhow!("The specified account does not have an associated owner key to derive the claim key from") })?; let account_component_address = *account.component_address(); - let account_key = sdk.key_manager_api().get_account_owner_key(account_key_id)?; - let (claim_public_key, claim_secret) = match req.claim_key_index { - Some(index) => { - let (claim_key, claim_pk) = sdk.key_manager_api().derive_account_keypair(index)?; - (claim_pk, Some(claim_key)) - }, - None => (account_key.to_public_key(), None), + let claim_public_key = match req.claim_key_index { + Some(index) => sdk + .key_manager_api() + .get_public_key(KeyBranch::Account, KeyId::derived(index))? + .public_key + .to_byte_type(), + None => *account.address.account_public_key(), }; let fee_pool_addresses = req .shards .into_iter() - .map(|shard| derive_fee_pool_address(&claim_public_key.to_byte_type(), NUM_PRESHARDS, shard)); + .map(|shard| derive_fee_pool_address(&claim_public_key, NUM_PRESHARDS, shard)); // build the transaction let max_fee = req.max_fee.unwrap_or(DEFAULT_FEE); - let transaction = context + let unsigned_transaction = context .transaction_builder() .with_dry_run(req.dry_run) .with_fee_instructions_builder(|builder| { @@ -178,16 +180,30 @@ pub async fn handle_claim_validator_fees( .with_inputs(fee_pool_addresses.map(SubstateRequirement::unversioned)) .add_input(XTR) .then(|builder| { - if let Some(secret) = claim_secret { - // If the claim key is different from the account secret, we need to sign with both - builder - .with_authorized_seal_signer() - .add_signer(&account_key.to_public_key().to_byte_type(), &secret.key) + if let Some(index) = req.claim_key_index { + if claim_public_key == *account.address.account_public_key() { + builder + } else { + // If the claim key is different from the account secret, we need to sign with both + sdk.local_signer_api() + .sign_with_context( + KeyBranch::Account, + KeyId::derived(index), + account.address.account_public_key(), + builder.with_authorized_seal_signer(), + ) + // We happen to know that signing with a derived key is infallible + .expect("Signing with should work") + } } else { builder } }) - .build_and_seal(&account_key.secret); + .build(); + + let transaction = sdk + .local_signer_api() + .sign(KeyBranch::Account, account_key_id, unsigned_transaction)?; // send the transaction if req.dry_run { diff --git a/applications/tari_walletd/src/main.rs b/applications/tari_walletd/src/main.rs index c7e133e9e8..73bca6b3fb 100644 --- a/applications/tari_walletd/src/main.rs +++ b/applications/tari_walletd/src/main.rs @@ -29,7 +29,7 @@ use tari_common::initialize_logging; use tari_crypto::tari_utilities::ByteArray; use tari_engine_types::ToByteType; use tari_ootle_app_utilities::configuration::load_configuration; -use tari_ootle_wallet_sdk::{apis::key_manager::KeyBranch, cipher_seed::CipherSeedRestore}; +use tari_ootle_wallet_sdk::{cipher_seed::CipherSeedRestore, models::KeyBranch}; use tari_ootle_walletd::{ cli::{Cli, Subcommand}, config::ApplicationConfig, diff --git a/clients/wallet_daemon_client/src/lib.rs b/clients/wallet_daemon_client/src/lib.rs index 7987ce1695..ce8383e03e 100644 --- a/clients/wallet_daemon_client/src/lib.rs +++ b/clients/wallet_daemon_client/src/lib.rs @@ -37,7 +37,7 @@ use reqwest::{ use serde::{de::DeserializeOwned, Serialize}; use serde_json as json; use serde_json::json; -use tari_ootle_wallet_sdk::apis::key_manager::KeyBranch; +use tari_ootle_wallet_sdk::models::KeyBranch; use types::{ AccountsCreateFreeTestCoinsRequest, AccountsCreateFreeTestCoinsResponse, diff --git a/clients/wallet_daemon_client/src/types.rs b/clients/wallet_daemon_client/src/types.rs index 874fc3738c..5a94a3afbd 100644 --- a/clients/wallet_daemon_client/src/types.rs +++ b/clients/wallet_daemon_client/src/types.rs @@ -39,12 +39,13 @@ use tari_ootle_common_types::{ SubstateRequirement, }; use tari_ootle_wallet_sdk::{ - apis::{confidential_transfer::ConfidentialTransferInputSelection, key_manager::KeyBranch}, + apis::confidential_transfer::ConfidentialTransferInputSelection, crypto::memo::Memo, models::{ Account, AuthoredTemplateModel, DerivedKeyIndex, + KeyBranch, KeyId, NonFungibleToken, OutputStatus, diff --git a/crates/common_types/src/lib.rs b/crates/common_types/src/lib.rs index 5828ff3572..79637421f9 100644 --- a/crates/common_types/src/lib.rs +++ b/crates/common_types/src/lib.rs @@ -27,6 +27,7 @@ pub mod services; pub mod shard; mod shard_group; mod shard_state_versions; +mod signable; mod state_version; mod substate_address; pub mod substate_type; @@ -50,6 +51,7 @@ pub use num_preshards::*; pub use peer_address::*; pub use shard_group::*; pub use shard_state_versions::*; +pub use signable::*; pub use state_version::*; pub use substate_address::*; // Re-export diff --git a/crates/common_types/src/signable.rs b/crates/common_types/src/signable.rs new file mode 100644 index 0000000000..9db0a2631b --- /dev/null +++ b/crates/common_types/src/signable.rs @@ -0,0 +1,16 @@ +// Copyright 2025 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use tari_crypto::ristretto::{RistrettoPublicKey, RistrettoSchnorr}; + +pub trait Signable { + type MessageOutput: AsRef<[u8]>; + + fn as_signing_message(&self, context: Ctx) -> Self::MessageOutput; +} + +pub trait IntoSigned: Signable { + type SignedOutput; + + fn into_signed(self, public_key: RistrettoPublicKey, signature: RistrettoSchnorr) -> Self::SignedOutput; +} diff --git a/crates/engine/src/runtime/working_state.rs b/crates/engine/src/runtime/working_state.rs index 7543d8ecc3..791c77b95e 100644 --- a/crates/engine/src/runtime/working_state.rs +++ b/crates/engine/src/runtime/working_state.rs @@ -1048,16 +1048,18 @@ impl WorkingState { vault_mut.resource_container_mut().deposit(resx.withdraw_all()?)?; } + let total_fees_paid = fee_resource + .amount() + .to_u64_checked() + .expect("FeeState guarantees that the total fee payments fit in an u64"); + Ok(TransactionReceipt { transaction_hash: self.transaction_hash, events: self.events.clone(), logs: self.logs.clone(), fee_receipt: FeeReceipt { total_fee_payment, - total_fees_paid: fee_resource - .amount() - .to_u64_checked() - .expect("FeeState guarantees that the total fee payments fit in an u64"), + total_fees_paid, total_fee_overcharge, cost_breakdown: self.fee_state.take_fee_charges(), }, diff --git a/crates/storage/src/consensus_models/leader_fee.rs b/crates/storage/src/consensus_models/leader_fee.rs index 8354294cc3..d98c685bec 100644 --- a/crates/storage/src/consensus_models/leader_fee.rs +++ b/crates/storage/src/consensus_models/leader_fee.rs @@ -55,10 +55,10 @@ pub fn calculate_leader_fee(transaction_fee: u64, num_involved_shards: NonZeroU6 // Pay each leader 1 more leader_fee += 1; - // We burn a little less due to the remainder + // We burn a little less (< num_involved_shards) due to the remainder target_burn.saturating_sub(num_involved_shards.get() - excess_remainder_burn) } else { - // We burn a little more due to the remainder + // We burn a little more (< num_involved_shards) due to the remainder target_burn + excess_remainder_burn }; diff --git a/crates/transaction/src/builder/mod.rs b/crates/transaction/src/builder/mod.rs index 5aeca39ce8..8b77061a77 100644 --- a/crates/transaction/src/builder/mod.rs +++ b/crates/transaction/src/builder/mod.rs @@ -9,13 +9,14 @@ mod tests; mod workspace_ids; pub use named_component_call::*; -use tari_crypto::ristretto::RistrettoSecretKey; +use tari_crypto::ristretto::{RistrettoPublicKey, RistrettoSchnorr, RistrettoSecretKey}; use tari_engine_types::{ confidential::{ClaimBurnOutputData, MinotariBurnClaimProof}, substate::SubstateId, + ToByteType, ValidatorFeePoolAddress, }; -use tari_ootle_common_types::{Epoch, SubstateRequirement}; +use tari_ootle_common_types::{Epoch, IntoSigned, Signable, SubstateRequirement}; use tari_template_lib::{ auth::OwnerRule, models::{ResourceAddress, StealthTransferStatement}, @@ -65,7 +66,7 @@ impl TransactionBuilder { } } - pub fn then Self>(self, f: F) -> Self { + pub fn then T, T>(self, f: F) -> T { f(self) } @@ -480,3 +481,22 @@ impl TransactionBuilder { WorkspaceOffsetId::new(id).with_offset_opt(parsed.offset) } } + +impl Signable<&RistrettoPublicKeyBytes> for TransactionBuilder { + type MessageOutput = [u8; 64]; + + fn as_signing_message(&self, sealed_signer: &RistrettoPublicKeyBytes) -> Self::MessageOutput { + self.unsigned_transaction.as_signing_message(sealed_signer) + } +} + +impl IntoSigned<&RistrettoPublicKeyBytes> for TransactionBuilder { + type SignedOutput = Self; + + fn into_signed(self, public_key: RistrettoPublicKey, signature: RistrettoSchnorr) -> Self::SignedOutput { + self.add_signature(TransactionSignature::new( + public_key.to_byte_type(), + signature.to_byte_type(), + )) + } +} diff --git a/crates/transaction/src/transaction.rs b/crates/transaction/src/transaction.rs index 3c34036f27..1fdca1b33d 100644 --- a/crates/transaction/src/transaction.rs +++ b/crates/transaction/src/transaction.rs @@ -242,7 +242,7 @@ impl Transaction { } } - pub const fn schema_version(&self) -> u64 { + pub const fn schema_version(&self) -> u16 { match self { Self::V1(tx) => tx.schema_version(), } diff --git a/crates/transaction/src/unsigned_transaction.rs b/crates/transaction/src/unsigned_transaction.rs index 80a1b0904c..a0c95f2429 100644 --- a/crates/transaction/src/unsigned_transaction.rs +++ b/crates/transaction/src/unsigned_transaction.rs @@ -6,8 +6,8 @@ use std::collections::HashSet; use indexmap::IndexSet; use serde::{Deserialize, Serialize}; use tari_engine_types::{indexed_value::IndexedValueError, substate::SubstateId}; -use tari_ootle_common_types::{Epoch, SubstateRequirement}; -use tari_template_lib::models::ComponentAddress; +use tari_ootle_common_types::{Epoch, Signable, SubstateRequirement}; +use tari_template_lib::{models::ComponentAddress, prelude::RistrettoPublicKeyBytes}; use crate::{Instruction, TransactionSignature, UnsealedTransactionV1, UnsignedTransactionV1}; @@ -18,6 +18,12 @@ pub enum UnsignedTransaction { } impl UnsignedTransaction { + pub fn schema_version(&self) -> u16 { + match self { + Self::V1(_) => 1, + } + } + pub fn set_network>(&mut self, network: N) -> &mut Self { match self { Self::V1(tx) => tx.set_network(network), @@ -25,7 +31,7 @@ impl UnsignedTransaction { self } - pub fn set_dry_run(&mut self, dry_run: bool) -> &mut Self { + pub(crate) fn set_dry_run(&mut self, dry_run: bool) -> &mut Self { match self { Self::V1(tx) => tx.set_dry_run(dry_run), }; @@ -148,3 +154,13 @@ impl Default for UnsignedTransaction { Self::V1(UnsignedTransactionV1::default()) } } + +impl Signable<&RistrettoPublicKeyBytes> for UnsignedTransaction { + type MessageOutput = [u8; 64]; + + fn as_signing_message(&self, sealed_signer: &RistrettoPublicKeyBytes) -> Self::MessageOutput { + match &self { + Self::V1(tx) => tx.as_signing_message(sealed_signer), + } + } +} diff --git a/crates/transaction/src/v1/signature.rs b/crates/transaction/src/v1/signature.rs index fbd022d1b6..6741865ab2 100644 --- a/crates/transaction/src/v1/signature.rs +++ b/crates/transaction/src/v1/signature.rs @@ -94,7 +94,7 @@ impl TransactionSignature { transaction: &UnsignedTransactionV1, ) -> Self { let public_key = RistrettoPublicKey::from_secret_key(secret_key); - let message = Self::create_message(seal_signer, transaction); + let message = Self::create_message(1, seal_signer, transaction); Self { signature: RistrettoSchnorr::sign(secret_key, message, &mut OsRng) @@ -104,8 +104,8 @@ impl TransactionSignature { } } - pub fn verify(&self, seal_signer: &RistrettoPublicKeyBytes, transaction: &UnsignedTransactionV1) -> bool { - let message = Self::create_message(seal_signer, transaction); + pub fn verify_v1(&self, seal_signer: &RistrettoPublicKeyBytes, transaction: &UnsignedTransactionV1) -> bool { + let message = Self::create_message(1, seal_signer, transaction); let Ok(public_key) = self.public_key.try_from_byte_type() else { return false; }; @@ -123,9 +123,14 @@ impl TransactionSignature { &self.public_key } - fn create_message(seal_signer: &RistrettoPublicKeyBytes, transaction: &UnsignedTransactionV1) -> [u8; 64] { + pub fn create_message( + schema_version: u16, + seal_signer: &RistrettoPublicKeyBytes, + transaction: &UnsignedTransactionV1, + ) -> [u8; 64] { let signature_fields = TransactionSignatureFields::from(transaction); engine_hasher64(EngineHashDomainLabel::TransactionSignature) + .chain(&schema_version) .chain(seal_signer) .chain(&signature_fields) .result() diff --git a/crates/transaction/src/v1/transaction.rs b/crates/transaction/src/v1/transaction.rs index 4ded686d24..56a123cf3c 100644 --- a/crates/transaction/src/v1/transaction.rs +++ b/crates/transaction/src/v1/transaction.rs @@ -41,7 +41,7 @@ impl TransactionV1 { } } - pub const fn schema_version(&self) -> u64 { + pub const fn schema_version(&self) -> u16 { self.body.schema_version() } diff --git a/crates/transaction/src/v1/unsealed.rs b/crates/transaction/src/v1/unsealed.rs index fc6607b30a..907b4f9ab8 100644 --- a/crates/transaction/src/v1/unsealed.rs +++ b/crates/transaction/src/v1/unsealed.rs @@ -5,9 +5,14 @@ use std::collections::HashSet; use indexmap::IndexSet; use serde::{Deserialize, Serialize}; -use tari_crypto::ristretto::RistrettoSecretKey; -use tari_engine_types::{indexed_value::IndexedValueError, substate::SubstateId}; -use tari_ootle_common_types::{Epoch, SubstateRequirement}; +use tari_crypto::ristretto::{RistrettoPublicKey, RistrettoSchnorr, RistrettoSecretKey}; +use tari_engine_types::{ + hashing::{engine_hasher64, EngineHashDomainLabel}, + indexed_value::IndexedValueError, + substate::SubstateId, + ToByteType, +}; +use tari_ootle_common_types::{Epoch, IntoSigned, Signable, SubstateRequirement}; use tari_template_lib::{models::ComponentAddress, types::crypto::RistrettoPublicKeyBytes}; use crate::{ @@ -34,7 +39,7 @@ impl UnsealedTransactionV1 { } } - pub const fn schema_version(&self) -> u64 { + pub const fn schema_version(&self) -> u16 { 1 } @@ -79,7 +84,7 @@ impl UnsealedTransactionV1 { } self.signatures().iter().enumerate().all(|(i, sig)| { - if sig.verify(seal_signer, &self.transaction) { + if sig.verify_v1(seal_signer, &self.transaction) { true } else { log::debug!(target: LOG_TARGET, "Failed to verify signature at index {}", i); @@ -118,3 +123,25 @@ impl UnsealedTransactionV1 { self.inputs().iter().any(|i| i.version().is_none()) } } + +impl Signable for UnsealedTransactionV1 { + type MessageOutput = [u8; 64]; + + fn as_signing_message(&self, _context: ()) -> Self::MessageOutput { + engine_hasher64(EngineHashDomainLabel::TransactionSignature) + .chain(&self.schema_version()) + .chain(self) + .result() + } +} + +impl IntoSigned for UnsealedTransactionV1 { + type SignedOutput = Transaction; + + fn into_signed(self, public_key: RistrettoPublicKey, signature: RistrettoSchnorr) -> Self::SignedOutput { + self.set_seal_signature(TransactionSealSignature::new( + public_key.to_byte_type(), + signature.to_byte_type(), + )) + } +} diff --git a/crates/transaction/src/v1/unsigned.rs b/crates/transaction/src/v1/unsigned.rs index b0c9e6203c..f131b19e54 100644 --- a/crates/transaction/src/v1/unsigned.rs +++ b/crates/transaction/src/v1/unsigned.rs @@ -9,10 +9,10 @@ use tari_engine_types::{ indexed_value::{IndexedValue, IndexedValueError}, substate::SubstateId, }; -use tari_ootle_common_types::{Epoch, SubstateRequirement}; -use tari_template_lib::models::ComponentAddress; +use tari_ootle_common_types::{Epoch, Signable, SubstateRequirement}; +use tari_template_lib::{models::ComponentAddress, prelude::RistrettoPublicKeyBytes}; -use crate::{builder::TransactionBuilder, ComponentCall, Instruction}; +use crate::{builder::TransactionBuilder, ComponentCall, Instruction, TransactionSignature}; #[derive(Debug, Clone, Serialize, Deserialize, Default, borsh::BorshSerialize)] #[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export))] @@ -144,3 +144,11 @@ impl UnsignedTransactionV1 { self.inputs().iter().any(|i| i.version().is_none()) } } + +impl Signable<&RistrettoPublicKeyBytes> for UnsignedTransactionV1 { + type MessageOutput = [u8; 64]; + + fn as_signing_message(&self, seal_signer: &RistrettoPublicKeyBytes) -> Self::MessageOutput { + TransactionSignature::create_message(1, seal_signer, self) + } +} diff --git a/crates/wallet/sdk/src/apis/confidential_outputs.rs b/crates/wallet/sdk/src/apis/confidential_outputs.rs index df2d35be3d..998991048d 100644 --- a/crates/wallet/sdk/src/apis/confidential_outputs.rs +++ b/crates/wallet/sdk/src/apis/confidential_outputs.rs @@ -15,7 +15,7 @@ use crate::{ confidential_crypto::{ConfidentialCryptoApi, ConfidentialCryptoApiError}, key_manager::{KeyManagerApi, KeyManagerApiError}, }, - models::{Account, ConfidentialOutputModel, Key, OutputStatus, WalletLockId}, + models::{Account, ConfidentialOutputModel, OutputStatus, WalletLockId, WalletSecretKey}, storage::{WalletStorageError, WalletStore, WalletStoreReader, WalletStoreWriter}, }; @@ -265,7 +265,7 @@ where TStore: WalletStore fn validate_output( &self, account: &Account, - key: &Key, + key: &WalletSecretKey, vault_id: VaultId, commitment: PedersenCommitmentBytes, output: &PrivateOutput, diff --git a/crates/wallet/sdk/src/apis/confidential_transfer.rs b/crates/wallet/sdk/src/apis/confidential_transfer.rs index c9fd3f3d8a..f3f453da5b 100644 --- a/crates/wallet/sdk/src/apis/confidential_transfer.rs +++ b/crates/wallet/sdk/src/apis/confidential_transfer.rs @@ -23,10 +23,10 @@ use crate::{ confidential_crypto::{ConfidentialCryptoApi, ConfidentialCryptoApiError}, confidential_outputs::{ConfidentialOutputsApi, ConfidentialOutputsApiError}, config::{ConfigApi, ConfigApiError}, - key_manager::{KeyBranch, KeyManagerApi, KeyManagerApiError}, + key_manager::{KeyManagerApi, KeyManagerApiError}, substate::{SubstateApiError, SubstatesApi}, }, - models::{ConfidentialOutputModel, OutputStatus, WalletLockId}, + models::{ConfidentialOutputModel, KeyBranch, OutputStatus, WalletLockId}, network::WalletNetworkInterface, storage::{WalletStorageError, WalletStore}, }; diff --git a/crates/wallet/sdk/src/apis/key_manager.rs b/crates/wallet/sdk/src/apis/key_manager.rs index e97756eccd..7f0f5f2a6f 100644 --- a/crates/wallet/sdk/src/apis/key_manager.rs +++ b/crates/wallet/sdk/src/apis/key_manager.rs @@ -3,7 +3,6 @@ use blake2::Blake2b; use digest::{consts::U64, crypto_common::rand_core::OsRng}; -use tari_bor::{Deserialize, Serialize}; use tari_crypto::{ keys::{PublicKey as _, SecretKey}, ristretto::{RistrettoPublicKey, RistrettoSecretKey}, @@ -26,59 +25,19 @@ use crate::{ DerivedWalletKey, ImportedKeyId, ImportedWalletKey, - Key, + KeyBranch, KeyId, KeyType, WalletKeyRecord, WalletOotleAddressWithKeyIds, + WalletPublicKey, + WalletSecretKey, }, storage::{WalletStorageError, WalletStore, WalletStoreReader, WalletStoreWriter}, }; pub type WalletKeyManager = TariKeyManager>; -#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export, export_to = "wallet-daemon-client/"))] -#[serde(rename_all = "snake_case")] -pub enum KeyBranch { - /// The account key branch, used for deriving account keys. - Account, - /// The transaction key branch, used to sign transactions that do not need to be signed with the account key. - Transaction, - /// The Elgamal encryption view key branch, used to derive a view key for resources with "viewable balance" - /// enabled. - ElgamalEncryptionViewKey, - /// The stealth mask branch, used to derive masks for stealth addresses. - StealthMask, - /// The confidential mask branch, used to derive masks for confidential transactions. - ConfidentialMask, - /// Used to generate nonces that need to be recreated later, e.g. to derive the DH secret for claim burn - Nonce, - /// Branch used to derive view-only keys. This key is used to derive an encryption key for wallet recovery. But - /// does not allow spending. - ViewOnlyKey, -} - -impl KeyBranch { - pub const fn as_str(&self) -> &'static str { - match self { - Self::Account => "account", - Self::Transaction => "transactions", - Self::ElgamalEncryptionViewKey => "elgamal_view_key", - Self::StealthMask => "stealth_mask", - Self::ConfidentialMask => "confidential_mask", - Self::Nonce => "nonce", - Self::ViewOnlyKey => "view_only_key", - } - } -} - -impl AsRef for KeyBranch { - fn as_ref(&self) -> &str { - self.as_str() - } -} - #[derive(Clone)] pub struct KeyManagerApi<'a, TStore> { network: Network, @@ -164,15 +123,15 @@ impl<'a, TStore: WalletStore> KeyManagerApi<'a, TStore> { Ok(KeyId::imported(id)) } - pub fn get_account_owner_key(&self, key_id: KeyId) -> Result { + pub fn get_account_owner_key(&self, key_id: KeyId) -> Result { self.get_key(KeyBranch::Account, key_id) } - pub fn get_view_only_key(&self, key_id: KeyId) -> Result { + pub fn get_view_only_key(&self, key_id: KeyId) -> Result { self.get_key(KeyBranch::ViewOnlyKey, key_id) } - pub fn get_key(&self, branch: KeyBranch, key_id: KeyId) -> Result { + pub(crate) fn get_key(&self, branch: KeyBranch, key_id: KeyId) -> Result { match key_id { KeyId::Imported { local_key_id } => { let imported_key = self.get_imported_key(local_key_id)?; @@ -185,7 +144,34 @@ impl<'a, TStore: WalletStore> KeyManagerApi<'a, TStore> { } } - pub fn derive_key( + pub fn get_public_key(&self, branch: KeyBranch, key_id: KeyId) -> Result { + match key_id { + KeyId::Imported { local_key_id } => { + // TODO: could be implemented without fetching the secret key, if we stored the public key in the DB + let imported_key = self.get_imported_key(local_key_id)?; + Ok(WalletPublicKey { + public_key: imported_key.to_public_key(), + key_id, + }) + }, + KeyId::Derived { index } => { + let derived_key = self.derive_key(branch, index)?; + Ok(WalletPublicKey { + public_key: derived_key.to_public_key(), + key_id, + }) + }, + } + } + + pub fn get_elgamal_encrypted_view_key( + &self, + index: DerivedKeyIndex, + ) -> Result { + self.derive_key(KeyBranch::ElgamalEncryptionViewKey, index) + } + + pub(crate) fn derive_key( &self, branch: KeyBranch, index: DerivedKeyIndex, @@ -209,15 +195,6 @@ impl<'a, TStore: WalletStore> KeyManagerApi<'a, TStore> { }) } - pub fn derive_account_keypair( - &self, - index: u64, - ) -> Result<(DerivedWalletKey, RistrettoPublicKey), KeyManagerApiError> { - let key = self.derive_account_key(index)?; - let public_key = RistrettoPublicKey::from_secret_key(&key.key); - Ok((key, public_key)) - } - pub fn derive_account_key(&self, index: DerivedKeyIndex) -> Result { self.derive_key(KeyBranch::Account, index) } @@ -280,6 +257,18 @@ impl<'a, TStore: WalletStore> KeyManagerApi<'a, TStore> { Ok(key) } + /// Derives the next key in the specified branch, increments the index, and sets it as the active key. + /// If the branch does not exist, it will be created with index 0 and the first key will be returned. + /// TODO: if there is another active DB transaction this function will block until it can acquire it. + pub fn next_public_key(&self, branch: KeyBranch) -> Result { + let next_key_id = self.next_derived_key_index(branch)?; + let key = self.derive_key(branch, next_key_id)?; + Ok(WalletPublicKey { + public_key: key.to_public_key(), + key_id: key.as_key_id(), + }) + } + pub fn next_derived_key_index(&self, branch: KeyBranch) -> Result { let mut tx = self.store.create_write_tx()?; let next_index = tx @@ -326,9 +315,13 @@ impl<'a, TStore: WalletStore> KeyManagerApi<'a, TStore> { self.derive_key(branch, key_index) } - pub fn get_key_or_active(&self, branch: KeyBranch, maybe_key_id: Option) -> Result { + pub fn get_key_or_active( + &self, + branch: KeyBranch, + maybe_key_id: Option, + ) -> Result { match maybe_key_id { - Some(id) => Ok(self.get_key(branch, id)?), + Some(id) => Ok(self.get_public_key(branch, id)?), None => { let key = self.get_active_key(branch)?; Ok(key.into()) diff --git a/crates/wallet/sdk/src/apis/mod.rs b/crates/wallet/sdk/src/apis/mod.rs index f898321fa9..203d89146b 100644 --- a/crates/wallet/sdk/src/apis/mod.rs +++ b/crates/wallet/sdk/src/apis/mod.rs @@ -10,6 +10,7 @@ pub mod key_manager; pub mod non_fungible_tokens; pub mod password_manager; pub mod resources; +pub mod signer; pub mod stealth_crypto; pub mod stealth_outputs; pub mod stealth_transfer; diff --git a/crates/wallet/sdk/src/apis/signer.rs b/crates/wallet/sdk/src/apis/signer.rs new file mode 100644 index 0000000000..56bf16b07f --- /dev/null +++ b/crates/wallet/sdk/src/apis/signer.rs @@ -0,0 +1,62 @@ +// Copyright 2025 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use tari_ootle_common_types::{IntoSigned, Signable}; + +use crate::{ + key_managers::{KeyManagerBackend, SignatureOutput}, + models::{KeyBranch, KeyId}, +}; + +#[derive(Debug, Clone)] +pub struct SignerApi { + backend: TKm, +} + +impl SignerApi { + pub fn new(backend: TKm) -> Self { + Self { backend } + } + + pub fn get_signature( + &mut self, + branch: KeyBranch, + key_id: KeyId, + context: CTX, + item: &T, + ) -> Result + where + T: Signable, + TKm: KeyManagerBackend, + { + let message = item.as_signing_message(context); + let signature = self.backend.try_sign(branch.as_str(), key_id, message)?; + Ok(signature) + } + + pub fn sign_with_context( + &mut self, + branch: KeyBranch, + key_id: KeyId, + context: Ctx, + item: T, + ) -> Result + where + T: IntoSigned, + TKm: KeyManagerBackend, + { + let output = self.get_signature(branch, key_id, context, &item)?; + let output = item.into_signed(output.public_key, output.signature); + Ok(output) + } + + pub fn sign(&mut self, branch: KeyBranch, key_id: KeyId, item: T) -> Result + where + T: IntoSigned<()>, + TKm: KeyManagerBackend, + { + let output = self.get_signature(branch, key_id, (), &item)?; + let output = item.into_signed(output.public_key, output.signature); + Ok(output) + } +} diff --git a/crates/wallet/sdk/src/apis/stealth_outputs.rs b/crates/wallet/sdk/src/apis/stealth_outputs.rs index f5149859c0..31ca917913 100644 --- a/crates/wallet/sdk/src/apis/stealth_outputs.rs +++ b/crates/wallet/sdk/src/apis/stealth_outputs.rs @@ -21,6 +21,7 @@ use tari_ootle_common_types::{ }; use tari_ootle_wallet_crypto::{ memo::Memo, + DecryptedData, UnblindedOutputWitness, UnblindedStealthInputWitness, UnblindedStealthOutputWitness, @@ -29,7 +30,7 @@ use tari_template_builtin::ACCOUNT_TEMPLATE_ADDRESS; use tari_template_lib::{ models::{ComponentAddress, ResourceAddress, StealthTransferStatement, UtxoAddress, VaultId}, prelude::PedersenCommitmentBytes, - types::Amount, + types::{Amount, EncryptedData}, }; use tari_transaction::TransactionId; @@ -38,13 +39,14 @@ use crate::{ accounts::AccountsApiError, confidential_outputs::ConfidentialOutputsApiError, config::{ConfigApi, ConfigApiError}, - key_manager::{KeyBranch, KeyManagerApi, KeyManagerApiError}, + key_manager::{KeyManagerApi, KeyManagerApiError}, stealth_crypto::{StealthCryptoApi, StealthCryptoApiError}, stealth_transfer::{OutputToCreate, UnblindedInputToSpend}, }, models::{ AccountAndViewKeys, InputSpendData, + KeyBranch, KeyId, OutputStatus, StealthBalance, @@ -230,17 +232,16 @@ impl<'a, TStore: WalletStore> StealthOutputsApi<'a, TStore> { Ok(()) } - pub fn resolve_output_masks_for_spending( + fn resolve_output_masks_for_spending( &self, + spend_key_branch: KeyBranch, owner_key_id: KeyId, view_only_key_id: KeyId, inputs: &[InputSpendData], ) -> Result, StealthOutputsApiError> { let network = self.config_api.get_network()?; - let owner_key_part = self.key_manager_api.get_account_owner_key(owner_key_id)?; - // Derive the view-only secret, of which the public key is used by senders to encrypt the value and mask. - let view_only = self.key_manager_api.get_view_only_key(view_only_key_id)?; + let owner_key_part = self.key_manager_api.get_key(spend_key_branch, owner_key_id)?; let mut inputs_with_masks = Vec::with_capacity(inputs.len()); for input in inputs { // Derive the decryption key from the DHKE(sender's public nonce, encryption secret key); @@ -253,10 +254,12 @@ impl<'a, TStore: WalletStore> StealthOutputsApi<'a, TStore> { reason: format!("Sender public nonce bytes are not a canonical public key: {e}"), })?; - let decrypted = self.crypto_api.decrypt_value_and_mask( + // Derive the view-only secret, of which the public key is used by senders to encrypt the value and mask. + let decrypted = self.decrypt_value_and_mask( &input.encrypted_data, &input.commitment, - &view_only.secret, + KeyBranch::ViewOnlyKey, + view_only_key_id, &nonce, // We dont need to decrypt the memo to spend the output true, @@ -671,8 +674,12 @@ impl<'a, TStore: WalletStore> StealthOutputsApi<'a, TStore> { where I: IntoIterator>, { - let unblinded_inputs = - self.resolve_output_masks_for_spending(params.spend_key_id, params.view_only_key_id, params.inputs)?; + let unblinded_inputs = self.resolve_output_masks_for_spending( + params.spend_key_branch, + params.spend_key_id, + params.view_only_key_id, + params.inputs, + )?; let outputs = params .outputs .into_iter() @@ -708,9 +715,45 @@ impl<'a, TStore: WalletStore> StealthOutputsApi<'a, TStore> { )?; Ok(statement) } + + pub fn decrypt_value_and_mask( + &self, + output_encrypted_value: &EncryptedData, + output_commitment: &PedersenCommitmentBytes, + key_branch: KeyBranch, + claim_secret_key_id: KeyId, + reciprocal_public_key: &RistrettoPublicKey, + skip_memo: bool, + ) -> Result { + let key = self.key_manager_api.get_key(key_branch, claim_secret_key_id)?; + let decrypted = self.crypto_api.decrypt_value_and_mask( + output_encrypted_value, + output_commitment, + &key.secret, + reciprocal_public_key, + skip_memo, + )?; + Ok(decrypted) + } + + pub fn encrypt_value_and_mask( + &self, + amount: u64, + public_key: &RistrettoPublicKey, + memo: Option<&Memo>, + ) -> Result<(RistrettoPublicKey, EncryptedData), StealthOutputsApiError> { + let nonce_secret = self.key_manager_api.create_throwaway_nonce(); + let public_nonce = RistrettoPublicKey::from_secret_key(&nonce_secret); + let mask = self.key_manager_api.next_key(KeyBranch::StealthMask)?; + let data = self + .crypto_api + .encrypt_value_and_mask(amount, &mask.key, public_key, &nonce_secret, memo)?; + Ok((public_nonce, data)) + } } pub struct TransferStatementParams<'a, I> { + pub spend_key_branch: KeyBranch, pub spend_key_id: KeyId, pub view_only_key_id: KeyId, pub resource_address: &'a ResourceAddress, diff --git a/crates/wallet/sdk/src/apis/stealth_transfer.rs b/crates/wallet/sdk/src/apis/stealth_transfer.rs index 2736c75763..87754fa00a 100644 --- a/crates/wallet/sdk/src/apis/stealth_transfer.rs +++ b/crates/wallet/sdk/src/apis/stealth_transfer.rs @@ -38,7 +38,7 @@ use crate::{ stealth_outputs::{StealthOutputsApi, StealthOutputsApiError, TransferStatementParams}, substate::{SubstateApiError, SubstatesApi, ValidatorScanResult}, }, - models::{Account, AccountWithAddress, InputSpendData, OutputStatus, StealthOutputModel, WalletLockId}, + models::{Account, AccountWithAddress, InputSpendData, KeyBranch, OutputStatus, StealthOutputModel, WalletLockId}, network::WalletNetworkInterface, storage::{WalletStorageError, WalletStore}, }; @@ -450,6 +450,7 @@ where // Generate fee transfer statement let fee_transfer_statement = self.outputs_api.generate_transfer_statement(TransferStatementParams { + spend_key_branch: KeyBranch::Account, spend_key_id: owner_key_id, view_only_key_id: owner_account.view_only_key_id(), resource_address: ¶ms.resource_address, @@ -546,6 +547,7 @@ where .filter(|o| o.amount.is_positive()); let transfer_statement = self.outputs_api.generate_transfer_statement(TransferStatementParams { + spend_key_branch: KeyBranch::Account, spend_key_id: owner_key_id, view_only_key_id: owner_account.view_only_key_id(), resource_address: ¶ms.resource_address, diff --git a/crates/wallet/sdk/src/key_managers/backend.rs b/crates/wallet/sdk/src/key_managers/backend.rs new file mode 100644 index 0000000000..c625ff9092 --- /dev/null +++ b/crates/wallet/sdk/src/key_managers/backend.rs @@ -0,0 +1,23 @@ +// Copyright 2025 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use tari_crypto::ristretto::{RistrettoPublicKey, RistrettoSchnorr, RistrettoSecretKey}; + +use crate::models::KeyId; + +pub struct SignatureOutput { + pub signature: RistrettoSchnorr, + pub public_key: RistrettoPublicKey, +} + +pub trait KeyManagerBackend { + type Error; + + fn try_sign(&mut self, branch: &str, key_id: KeyId, message: M) -> Result; +} + +pub trait WalletKeyStore { + type Error; + + fn get_imported_secret(&self, key: K) -> Result; +} diff --git a/crates/wallet/sdk/src/key_managers/local.rs b/crates/wallet/sdk/src/key_managers/local.rs new file mode 100644 index 0000000000..8a39350815 --- /dev/null +++ b/crates/wallet/sdk/src/key_managers/local.rs @@ -0,0 +1,84 @@ +// Copyright 2025 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use blake2::Blake2b; +use digest::{consts::U64, crypto_common::rand_core::OsRng}; +use tari_common_types::seeds::cipher_seed::CipherSeed; +use tari_crypto::{ + keys::PublicKey, + ristretto::{RistrettoPublicKey, RistrettoSchnorr}, +}; +use tari_ootle_common_types::optional::IsNotFoundError; +use tari_transaction_components::key_manager::tari_key_manager::TariKeyManager; + +use crate::{ + apis::password_manager::PasswordManagerApiError, + key_managers::{backend::WalletKeyStore, KeyManagerBackend, SignatureOutput}, + models::{ImportedKeyId, KeyId}, + storage::WalletStorageError, +}; + +type WalletKeyManager = TariKeyManager>; + +#[derive(Debug, Clone)] +pub struct LocalKeyManager<'a, TKeyStore> { + cipher_seed: &'a CipherSeed, + key_store: TKeyStore, +} + +impl<'a, TKeyStore: WalletKeyStore> LocalKeyManager<'a, TKeyStore> { + pub fn new(cipher_seed: &'a CipherSeed, key_store: TKeyStore) -> Self { + Self { cipher_seed, key_store } + } + + /// WARNING: dont use next_key on the key manager because this will always return the same key + fn get_key_manager(&mut self, branch: &str) -> WalletKeyManager { + WalletKeyManager::from(self.cipher_seed.clone(), branch.to_string(), 0) + } +} + +impl KeyManagerBackend for LocalKeyManager<'_, TKeyStore> +where + M: AsRef<[u8]>, + TKeyStore: WalletKeyStore, +{ + type Error = LocalKeyManagerError; + + fn try_sign(&mut self, branch: &str, key_id: KeyId, message: M) -> Result { + let secret = match key_id { + KeyId::Derived { index } => { + let km = self.get_key_manager(branch); + let key = km + .derive_key(index) + .expect("BUG: Key derivation is infallible because it internally hashes to a canonical form."); + key.key + }, + KeyId::Imported { local_key_id } => self + .key_store + .get_imported_secret(local_key_id) + .map_err(LocalKeyManagerError::KeyStoreError)?, + }; + let signature = RistrettoSchnorr::sign(&secret, message, &mut OsRng) + .expect("RistrettoSchnorr::sign is infallible as it internally hashes the message into canonical form"); + let public_key = RistrettoPublicKey::from_secret_key(&secret); + Ok(SignatureOutput { signature, public_key }) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum LocalKeyManagerError { + #[error("Store error: {0}")] + StoreError(#[from] WalletStorageError), + #[error("Password manager error: {0}")] + PasswordManagerApiError(#[from] PasswordManagerApiError), + #[error("Key manager is in read only mode")] + ReadOnlyMode, + #[error("Cipher error: {0}")] + KeyStoreError(TKeyStoreErr), +} + +impl IsNotFoundError for LocalKeyManagerError { + fn is_not_found_error(&self) -> bool { + matches!(self, LocalKeyManagerError::StoreError(e) if e.is_not_found_error()) + } +} diff --git a/crates/wallet/sdk/src/key_managers/mod.rs b/crates/wallet/sdk/src/key_managers/mod.rs new file mode 100644 index 0000000000..4de0bae273 --- /dev/null +++ b/crates/wallet/sdk/src/key_managers/mod.rs @@ -0,0 +1,7 @@ +// Copyright 2025 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +mod backend; +pub mod local; + +pub use backend::*; diff --git a/crates/wallet/sdk/src/lib.rs b/crates/wallet/sdk/src/lib.rs index a10685da52..61ef6d5137 100644 --- a/crates/wallet/sdk/src/lib.rs +++ b/crates/wallet/sdk/src/lib.rs @@ -11,9 +11,11 @@ pub use sdk::{WalletSdk, WalletSdkConfig}; pub use tari_common_types::seeds::cipher_seed::CipherSeed; pub mod cipher_seed; +pub mod key_managers; +mod local_key_store; pub mod network; -pub type WalletSecretKey = tari_transaction_components::key_manager::tari_key_manager::DerivedKey; +pub type WalletDerivedSecretKey = tari_transaction_components::key_manager::tari_key_manager::DerivedKey; // Re-export commonly used types pub use tari_common_types::seeds::seed_words::SeedWords; diff --git a/crates/wallet/sdk/src/local_key_store.rs b/crates/wallet/sdk/src/local_key_store.rs new file mode 100644 index 0000000000..c68ef73bcd --- /dev/null +++ b/crates/wallet/sdk/src/local_key_store.rs @@ -0,0 +1,57 @@ +// Copyright 2025 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use tari_crypto::{ristretto::RistrettoSecretKey, tari_utilities::ByteArray}; +use tari_ootle_wallet_crypto::encryption::{decrypt_with_password, CipherError}; + +use crate::{ + apis::password_manager::{PasswordManagerApi, PasswordManagerApiError}, + key_managers::WalletKeyStore, + models::ImportedKeyId, + storage::{WalletStorageError, WalletStore, WalletStoreReader}, +}; + +#[derive(Clone)] +pub struct LocalKeyStore<'a, TStore> { + password_manager_api: PasswordManagerApi<'a, TStore>, + wallet_store: &'a TStore, +} + +impl<'a, TStore> LocalKeyStore<'a, TStore> { + pub fn new(password_manager_api: PasswordManagerApi<'a, TStore>, wallet_store: &'a TStore) -> Self { + Self { + password_manager_api, + wallet_store, + } + } +} + +impl WalletKeyStore for LocalKeyStore<'_, TStore> { + type Error = LocalKeyStoreError; + + fn get_imported_secret(&self, key: ImportedKeyId) -> Result { + let password = self.password_manager_api.get_cipher_seed_password()?; + let (_ty, encrypted) = self + .wallet_store + .with_read_tx(|tx| tx.key_manager_get_raw_imported_key(key))?; + let decrypted = decrypt_with_password(&encrypted, password.reveal())?; + let secret = RistrettoSecretKey::from_canonical_bytes(&decrypted).map_err(|e| { + LocalKeyStoreError::WalletStorage(WalletStorageError::DecodingError { + operation: "get_imported_secret", + item: "imported secret key", + details: format!("Imported key at id {key} is non-canonical {e}"), + }) + })?; + Ok(secret) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum LocalKeyStoreError { + #[error("Password manager error: {0}")] + PasswordManager(#[from] PasswordManagerApiError), + #[error("Wallet storage error: {0}")] + WalletStorage(#[from] WalletStorageError), + #[error("Cipher error: {0}")] + Cipher(#[from] CipherError), +} diff --git a/crates/wallet/sdk/src/models/key.rs b/crates/wallet/sdk/src/models/key.rs index 17c23afb32..1f7a5093f4 100644 --- a/crates/wallet/sdk/src/models/key.rs +++ b/crates/wallet/sdk/src/models/key.rs @@ -3,13 +3,56 @@ use std::{fmt::Display, str::FromStr}; +use tari_bor::{Deserialize, Serialize}; use tari_crypto::{ - keys::PublicKey, + keys::PublicKey as _, ristretto::{RistrettoPublicKey, RistrettoSecretKey}, }; use tari_ootle_address::RistrettoOotleAddress; use tari_template_lib::prelude::RistrettoPublicKeyBytes; +#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export, export_to = "wallet-daemon-client/"))] +#[serde(rename_all = "snake_case")] +pub enum KeyBranch { + /// The account key branch, used for deriving account keys. + Account, + /// The transaction key branch, used to sign transactions that do not need to be signed with the account key. + Transaction, + /// The Elgamal encryption view key branch, used to derive a view key for resources with "viewable balance" + /// enabled. + ElgamalEncryptionViewKey, + /// The stealth mask branch, used to derive masks for stealth addresses. + StealthMask, + /// The confidential mask branch, used to derive masks for confidential transactions. + ConfidentialMask, + /// Used to generate nonces that need to be recreated later, e.g. to derive the DH secret for claim burn + Nonce, + /// Branch used to derive view-only keys. This key is used to derive an encryption key for wallet recovery. But + /// does not allow spending. + ViewOnlyKey, +} + +impl KeyBranch { + pub const fn as_str(&self) -> &'static str { + match self { + Self::Account => "account", + Self::Transaction => "transactions", + Self::ElgamalEncryptionViewKey => "elgamal_view_key", + Self::StealthMask => "stealth_mask", + Self::ConfidentialMask => "confidential_mask", + Self::Nonce => "nonce", + Self::ViewOnlyKey => "view_only_key", + } + } +} + +impl AsRef for KeyBranch { + fn as_ref(&self) -> &str { + self.as_str() + } +} + #[derive(Clone)] pub struct WalletKeyRecord { pub(crate) key_id: KeyId, @@ -82,12 +125,37 @@ impl From &RistrettoPublicKey { + &self.public_key + } + + pub fn key_id(&self) -> KeyId { + self.key_id + } +} + +impl From for WalletPublicKey { + fn from(derived: DerivedWalletKey) -> Self { + Self { + key_id: derived.as_key_id(), + public_key: derived.to_public_key(), + } + } +} + +#[derive(Clone)] +pub struct WalletSecretKey { pub secret: RistrettoSecretKey, pub key_id: KeyId, } -impl Key { +impl WalletSecretKey { pub fn secret(&self) -> &RistrettoSecretKey { &self.secret } @@ -101,7 +169,7 @@ impl Key { } } -impl From for Key { +impl From for WalletSecretKey { fn from(pair: DerivedKeyPair) -> Self { Self { key_id: pair.derived_key.as_key_id(), @@ -110,7 +178,7 @@ impl From for Key { } } -impl From for Key { +impl From for WalletSecretKey { fn from(derived: DerivedWalletKey) -> Self { Self { key_id: derived.as_key_id(), @@ -119,7 +187,7 @@ impl From for Key { } } -impl From for Key { +impl From for WalletSecretKey { fn from(imported: ImportedWalletKey) -> Self { Self { key_id: imported.as_key_id(), @@ -128,7 +196,7 @@ impl From for Key { } } -impl From for Key { +impl From for WalletSecretKey { fn from(record: WalletKeyRecord) -> Self { Self { secret: record.secret_key, @@ -140,8 +208,8 @@ impl From for Key { #[derive(Clone)] pub struct AccountAndViewKeys { pub account_public_key: RistrettoPublicKeyBytes, - pub account_key: Option, - pub view_only_key: Key, + pub account_key: Option, + pub view_only_key: WalletSecretKey, } #[derive(Clone)] diff --git a/crates/wallet/sdk/src/sdk.rs b/crates/wallet/sdk/src/sdk.rs index 4e0e3ffe63..969abf98df 100644 --- a/crates/wallet/sdk/src/sdk.rs +++ b/crates/wallet/sdk/src/sdk.rs @@ -27,6 +27,7 @@ use crate::{ non_fungible_tokens::NonFungibleTokensApi, password_manager::{PasswordManagerApi, PasswordManagerApiError}, resources::ResourcesApi, + signer::SignerApi, stealth_crypto::StealthCryptoApi, stealth_outputs::StealthOutputsApi, stealth_transfer::StealthTransferApi, @@ -36,12 +37,16 @@ use crate::{ viewable_balance::ViewableBalanceApi, }, cipher_seed::{CipherSeedRestore, WalletCipherSeed}, + key_managers::local::LocalKeyManager, + local_key_store::LocalKeyStore, network::{StatusResponseError, WalletNetworkInterface}, storage::{WalletStorageError, WalletStore}, }; const LOG_TARGET: &str = "wallet::sdk::api"; +pub type LocalSignerApi<'a, TStore> = SignerApi>>; + #[derive(Debug, Clone)] pub struct WalletSdkConfig { pub network: Network, @@ -151,10 +156,6 @@ where } /// Returns the KeyManager API for the wallet. - /// - /// ## Panics - /// This function will panic if the cipher seed has not been initialized i.e. `initialize_cipher_seed` has not been - /// called once before calling this. pub fn key_manager_api(&self) -> KeyManagerApi<'_, TStore> { let network = self.config.network; KeyManagerApi::new( @@ -165,6 +166,24 @@ where ) } + /// Returns the Signer API for the wallet if the cipher seed has been initialized. This signer uses the local key + /// store where key material is kept in the local database. + /// + /// ## Panics + /// This function will panic if the cipher seed has not been initialized i.e. `initialize_cipher_seed` has not been + /// called once before calling this. + pub fn local_signer_api(&self) -> LocalSignerApi<'_, TStore> { + let cipher_seed = self + .loaded_cipher_seed + .cipher_seed() + .expect("Cipher seed not initialized"); + let backend = LocalKeyManager::new( + cipher_seed, + LocalKeyStore::new(self.password_manager_api(), &self.store), + ); + SignerApi::new(backend) + } + pub(crate) fn password_manager_api(&self) -> PasswordManagerApi<'_, TStore> { PasswordManagerApi::new(self.config_api(), &self.config) } diff --git a/crates/wallet/sdk_services/src/account_recovery/service.rs b/crates/wallet/sdk_services/src/account_recovery/service.rs index cf89916190..9525339d2b 100644 --- a/crates/wallet/sdk_services/src/account_recovery/service.rs +++ b/crates/wallet/sdk_services/src/account_recovery/service.rs @@ -12,8 +12,8 @@ use tari_ootle_common_types::{ substate_type::SubstateType, }; use tari_ootle_wallet_sdk::{ - apis::{config::ConfigKey, key_manager::KeyBranch}, - models::{DerivedWalletKey, KeyId}, + apis::config::ConfigKey, + models::{DerivedWalletKey, KeyBranch, KeyId}, network::{StatusResponseError, WalletNetworkInterface}, storage::WalletStore, WalletSdk, diff --git a/crates/wallet/sdk_services/src/indexer_rest_api.rs b/crates/wallet/sdk_services/src/indexer_rest_api.rs index cfc95aadca..acf3e3f831 100644 --- a/crates/wallet/sdk_services/src/indexer_rest_api.rs +++ b/crates/wallet/sdk_services/src/indexer_rest_api.rs @@ -26,7 +26,13 @@ use tari_indexer_client::{ SubmitTransactionRequest, }, }; -use tari_ootle_common_types::{array_utils::copy_fixed_checked, optional::IsNotFoundError, shard::Shard, StateVersion}; +use tari_ootle_common_types::{ + array_utils::copy_fixed_checked, + displayable::Displayable, + optional::IsNotFoundError, + shard::Shard, + StateVersion, +}; use tari_ootle_wallet_sdk::{ models::{EndOfShard, StartOfShard, UtxoBurnt, UtxoSpent, UtxoUnspent, UtxoUpdatePayload, WalletUtxoUpdate}, network::{ @@ -303,6 +309,17 @@ impl StatusResponseError for IndexerRestApiNetworkInterfaceError { message: format!("Indexer request failed with status {code}: {message}"), } }, + IndexerRestClientError::ErrorResponse { source, details } => { + if source.status().map(|s| s.as_u16()) == Some(INVALID_REQUEST_CODE as u16) { + WalletQueryErrorStatus::TransactionRejected { + message: format!("Indexer error response: {}. Details: {}", source, details.display()), + } + } else { + WalletQueryErrorStatus::InternalError { + message: format!("Indexer error response: {}", source), + } + } + }, _ => WalletQueryErrorStatus::InternalError { message: format!("Indexer client error: {err}"), }, diff --git a/crates/wallet/sdk_services/src/utxo_scanner/scanner_round.rs b/crates/wallet/sdk_services/src/utxo_scanner/scanner_round.rs index feb6b08bd1..87a379055e 100644 --- a/crates/wallet/sdk_services/src/utxo_scanner/scanner_round.rs +++ b/crates/wallet/sdk_services/src/utxo_scanner/scanner_round.rs @@ -15,7 +15,7 @@ use tari_ootle_common_types::{ StateVersion, }; use tari_ootle_wallet_sdk::{ - models::{AccountWithAddress, Key, StartOfShard, UtxoSpent, UtxoUnspent, WalletUtxoUpdate}, + models::{AccountWithAddress, StartOfShard, UtxoSpent, UtxoUnspent, WalletSecretKey, WalletUtxoUpdate}, network::{StatusResponseError, UtxoUpdateStream, WalletNetworkInterface}, storage::{WalletStorageError, WalletStore, WalletStoreReader, WalletStoreWriter}, WalletSdk, @@ -35,7 +35,7 @@ const NUM_PRESHARDS: NumPreshards = NumPreshards::P256; pub struct UtxoScannerRound<'a, TStore, TNetworkInterface> { network: Network, account: &'a AccountWithAddress, - view_key: &'a Key, + view_key: &'a WalletSecretKey, resource_address: &'a ResourceAddress, sdk: &'a WalletSdk, @@ -57,7 +57,7 @@ where network: Network, sdk: &'a WalletSdk, account: &'a AccountWithAddress, - view_key: &'a Key, + view_key: &'a WalletSecretKey, resource_address: &'a ResourceAddress, notify: &'a Notify, ) -> Self { diff --git a/integration_tests/tests/steps/wallet.rs b/integration_tests/tests/steps/wallet.rs index 7d9cffa35d..2eb34665a2 100644 --- a/integration_tests/tests/steps/wallet.rs +++ b/integration_tests/tests/steps/wallet.rs @@ -11,7 +11,7 @@ use minotari_app_grpc::{ tari_rpc::{GetBalanceRequest, SubmitValidatorEvictionProofRequest, ValidateRequest}, }; use tari_engine_types::confidential::{AbridgedTransactionKernel, EncodedMerkleProof, MinotariBurnClaimProof}; -use tari_ootle_wallet_sdk::apis::key_manager::KeyBranch; +use tari_ootle_wallet_sdk::models::KeyBranch; use tari_template_lib::{ prelude::{PedersenCommitmentBytes, RistrettoPublicKeyBytes, Scalar32Bytes, SchnorrSignatureBytes}, types::EncryptedData, diff --git a/integration_tests/tests/steps/wallet_daemon.rs b/integration_tests/tests/steps/wallet_daemon.rs index f5cb9244b3..da24d99819 100644 --- a/integration_tests/tests/steps/wallet_daemon.rs +++ b/integration_tests/tests/steps/wallet_daemon.rs @@ -12,7 +12,7 @@ use integration_tests::{ }; use rand::{rngs::OsRng, Rng}; use tari_engine_types::commit_result::FinalizeResult; -use tari_ootle_wallet_sdk::apis::key_manager::KeyBranch; +use tari_ootle_wallet_sdk::models::KeyBranch; use tari_template_lib::{ constants::XTR, types::{bytes::Bytes, crypto::PedersenCommitmentBytes, Amount}, diff --git a/utilities/tariswap_test_bench/src/accounts.rs b/utilities/tariswap_test_bench/src/accounts.rs index 064df4ce25..fee4f27f97 100644 --- a/utilities/tariswap_test_bench/src/accounts.rs +++ b/utilities/tariswap_test_bench/src/accounts.rs @@ -4,14 +4,13 @@ use std::ops::RangeInclusive; use log::info; -use tari_crypto::{keys::PublicKey as _, ristretto::RistrettoPublicKey}; use tari_engine_types::{ component::derive_component_address_from_public_key, indexed_value::IndexedWellKnownTypes, ToByteType, }; use tari_ootle_common_types::SubstateRequirement; -use tari_ootle_wallet_sdk::models::{Account, KeyId}; +use tari_ootle_wallet_sdk::models::{Account, KeyBranch, KeyId}; use tari_template_builtin::ACCOUNT_TEMPLATE_ADDRESS; use tari_template_lib::{ constants::{XTR, XTR_FAUCET_COMPONENT_ADDRESS, XTR_FAUCET_VAULT_ADDRESS}, @@ -23,8 +22,11 @@ use crate::{faucet::Faucet, runner::Runner}; impl Runner { pub async fn create_account_with_free_coins(&mut self) -> anyhow::Result { - let key = self.sdk.key_manager_api().derive_account_key(0)?; - let owner_public_key = RistrettoPublicKey::from_secret_key(&key.key).to_byte_type(); + let owner_key = self + .sdk + .key_manager_api() + .get_public_key(KeyBranch::Account, KeyId::derived(0))?; + let owner_public_key = owner_key.public_key.to_byte_type(); let account_address = derive_component_address_from_public_key(&ACCOUNT_TEMPLATE_ADDRESS, &owner_public_key); @@ -41,7 +43,12 @@ impl Runner { SubstateRequirement::unversioned(XTR_FAUCET_COMPONENT_ADDRESS), SubstateRequirement::unversioned(XTR_FAUCET_VAULT_ADDRESS), ]) - .build_and_seal(&key.key); + .build(); + + let transaction = self + .sdk + .local_signer_api() + .sign(KeyBranch::Account, owner_key.key_id, transaction)?; let finalize = self.submit_transaction_and_wait(transaction).await?; let diff = finalize.result.any_accept().unwrap(); @@ -71,35 +78,46 @@ impl Runner { pay_fee_account: &Account, account_key_indexes: RangeInclusive, ) -> anyhow::Result> { - let key = self.sdk.key_manager_api().derive_account_key(0)?; + let key = self + .sdk + .key_manager_api() + .get_public_key(KeyBranch::Account, KeyId::derived(0))?; let key_index_start = *account_key_indexes.start(); let num_accounts = *account_key_indexes.end() as usize - key_index_start as usize + 1; let owners = account_key_indexes .map(|idx| { - let key = self.sdk.key_manager_api().derive_account_key(idx)?; + let key = self + .sdk + .key_manager_api() + .get_public_key(KeyBranch::Account, KeyId::derived(idx))?; Ok(key) }) .collect::>>()?; - let mut builder = self - .new_transaction_builder() - .fee_transaction_pay_from_component(pay_fee_account.component_address, 1000 * owners.len()); - for owner in &owners { - builder = builder.create_account(RistrettoPublicKey::from_secret_key(&owner.key).to_byte_type()); - } - let pay_fee_vault = self .sdk .accounts_api() .get_vault_by_resource(&pay_fee_account.component_address, &XTR)?; - let transaction = builder + let transaction = self + .new_transaction_builder() + .fee_transaction_pay_from_component(pay_fee_account.component_address, 1000 * owners.len()) + .then(|builder| { + owners.iter().fold(builder, |builder, owner| { + builder.create_account(owner.public_key.to_byte_type()) + }) + }) .with_inputs([ SubstateRequirement::unversioned(pay_fee_account.component_address), SubstateRequirement::unversioned(pay_fee_vault.id), SubstateRequirement::unversioned(pay_fee_vault.resource_address), ]) - .build_and_seal(&key.key); + .build(); + + let transaction = self + .sdk + .local_signer_api() + .sign(KeyBranch::Account, key.key_id, transaction)?; let finalize = self.submit_transaction_and_wait(transaction).await?; let diff = finalize.result.any_accept().unwrap(); @@ -114,19 +132,14 @@ impl Runner { .find(|addr| { derive_component_address_from_public_key( &ACCOUNT_TEMPLATE_ADDRESS, - &RistrettoPublicKey::from_secret_key(&owner.key).to_byte_type(), + &owner.public_key.to_byte_type(), ) == *addr }) .expect("New account not found in diff"); - self.sdk.accounts_api().add_account( - None, - &account_addr, - owner.as_key_id(), - owner.as_key_id(), - true, - false, - )?; + self.sdk + .accounts_api() + .add_account(None, &account_addr, owner.key_id, owner.key_id, true, false)?; let account = self.sdk.accounts_api().get_account_by_address(&account_addr)?; accounts.push(account.account); } diff --git a/utilities/tariswap_test_bench/src/tariswap.rs b/utilities/tariswap_test_bench/src/tariswap.rs index d8f1ff08c7..25eefdd152 100644 --- a/utilities/tariswap_test_bench/src/tariswap.rs +++ b/utilities/tariswap_test_bench/src/tariswap.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use log::info; use tari_engine_types::{indexed_value::decode_value_at_path, ToByteType}; use tari_ootle_common_types::{optional::Optional, SubstateRequirement}; -use tari_ootle_wallet_sdk::models::Account; +use tari_ootle_wallet_sdk::models::{Account, KeyBranch}; use tari_template_lib::{ models::{ComponentAddress, VaultId}, prelude::{ResourceAddress, ResourceType, XTR}, @@ -91,12 +91,12 @@ impl Runner { amount_b: Amount, faucet: &Faucet, ) -> anyhow::Result<()> { - let primary_account_key = self - .sdk - .key_manager_api() - .get_account_owner_key(primary_account.owner_key_id.expect("no owner key id"))?; + let primary_account_key = self.sdk.key_manager_api().get_public_key( + KeyBranch::Account, + primary_account.owner_key_id.expect("no owner key id"), + )?; let mut tx_ids = Vec::with_capacity(200); - let primary_account_pk = primary_account_key.to_public_key().to_byte_type(); + let primary_account_pk = primary_account_key.public_key().to_byte_type(); for i in 0..5 { let _timer = TraceTimer::info("tariswap", "add_liquidity") @@ -104,10 +104,6 @@ impl Runner { for (i, tariswap) in tariswaps.iter().enumerate().skip(i * 200).take(200) { let account = &accounts[i % accounts.len()]; - let key = self - .sdk - .key_manager_api() - .get_account_owner_key(account.owner_key_id.expect("no owner key id"))?; let xtr_vault = self .sdk .accounts_api() @@ -150,8 +146,22 @@ impl Runner { .put_last_instruction_output_on_workspace("lp") .call_method(account.component_address, "deposit", args![Workspace("lp")]) .with_authorized_seal_signer() - .add_signer(&primary_account_pk, key.secret()) - .build_and_seal(primary_account_key.secret()); + .then(|builder| { + // First sign with the account key to authorize the use of the account component + self.sdk.local_signer_api().sign_with_context( + KeyBranch::Account, + account.owner_key_id.expect("no owner key id"), + &primary_account_pk, + builder, + ) + })? + .build(); + + // Then sign with the primary account key to pay the fee + let transaction = + self.sdk + .local_signer_api() + .sign(KeyBranch::Account, primary_account_key.key_id(), transaction)?; assert!( transaction.verify_all_signatures(), @@ -209,21 +219,17 @@ impl Runner { amount_b_for_a: Amount, faucet: &Faucet, ) -> anyhow::Result<()> { - let primary_account_key = self - .sdk - .key_manager_api() - .get_account_owner_key(primary_account.owner_key_id.expect("no owner key id"))?; - let primary_account_pk = primary_account_key.to_public_key().to_byte_type(); + let primary_account_key = self.sdk.key_manager_api().get_public_key( + KeyBranch::Account, + primary_account.owner_key_id.expect("no owner key id"), + )?; + let primary_account_pk = primary_account_key.public_key.to_byte_type(); let mut tx_ids = vec![]; // Swap XTR for faucet for i in 0..5 { for (i, account) in accounts.iter().enumerate().skip(i * 200).take(200) { let tariswap = &tariswaps[i % tariswaps.len()]; - let key = self - .sdk - .key_manager_api() - .get_account_owner_key(account.owner_key_id.expect("no owner key id"))?; let xtr_vault = self .sdk .accounts_api() @@ -268,9 +274,20 @@ impl Runner { ]) .put_last_instruction_output_on_workspace("swapped") .call_method(account.component_address, "deposit", args![Workspace("swapped")]) - .with_authorized_seal_signer() - .add_signer(&primary_account_pk, key.secret()) - .build_and_seal(primary_account_key.secret()); + .with_authorized_seal_signer(); + + let transaction = self.sdk.local_signer_api().sign_with_context( + KeyBranch::Account, + account.owner_key_id.expect("no owner key id"), + &primary_account_pk, + transaction, + )?; + + let transaction = self.sdk.local_signer_api().sign( + KeyBranch::Account, + primary_account_key.key_id(), + transaction.build(), + )?; tx_ids.push(self.submit_transaction(transaction).await?); } @@ -294,10 +311,6 @@ impl Runner { // Swap faucet for XTR for i in 0..5 { for (i, account) in accounts.iter().enumerate().skip(i * 200).take(200) { - let key = self - .sdk - .key_manager_api() - .get_account_owner_key(account.owner_key_id.expect("no owner key id"))?; let xtr_vault = self .sdk .accounts_api() @@ -337,7 +350,13 @@ impl Runner { .call_method(tariswap.component_address, "swap", args![Workspace("b"), XTR,]) .put_last_instruction_output_on_workspace("swapped") .call_method(account.component_address, "deposit", args![Workspace("swapped")]) - .build_and_seal(key.secret()); + .build(); + + let transaction = self.sdk.local_signer_api().sign( + KeyBranch::Account, + account.owner_key_id.expect("no owner key id"), + transaction, + )?; tx_ids.push(self.submit_transaction(transaction).await?); }