Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions applications/tari_walletd/src/handlers/confidential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ pub async fn handle_view_vault_balance(
let mut lookup = IoReaderValueLookup::load(&mut file)?;

block_in_place(|| {
sdk.confidential_crypto_api().try_brute_force_commitment_balances(
sdk.viewable_balance_api().try_brute_force_commitment_balances(
&view_key.key,
commitments.values(),
value_range,
Expand All @@ -304,7 +304,7 @@ pub async fn handle_view_vault_balance(
})?
},
None => block_in_place(|| {
sdk.confidential_crypto_api().try_brute_force_commitment_balances(
sdk.viewable_balance_api().try_brute_force_commitment_balances(
&view_key.key,
commitments.values(),
value_range,
Expand Down
100 changes: 98 additions & 2 deletions applications/tari_walletd/src/handlers/stealth_utxos.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
// Copyright 2025 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

use std::fs;

use anyhow::anyhow;
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,
types::{StealthUtxosListRequest, StealthUtxosListResponse, UtxoInfo},
types::{
StealthUtxosDecryptValueRequest,
StealthUtxosDecryptValueResponse,
StealthUtxosListRequest,
StealthUtxosListResponse,
UtxoInfo,
},
};
use tokio::{task::block_in_place, time::Instant};

use crate::handlers::{helpers::invalid_params, HandlerContext};

use crate::handlers::HandlerContext;
const LOG_TARGET: &str = "tari::walletd::handlers::stealth_utxos";

pub async fn handle_list(
context: &HandlerContext,
Expand Down Expand Up @@ -36,3 +53,82 @@ pub async fn handle_list(
.collect(),
})
}

pub async fn handle_decrypt_value(
context: &HandlerContext,
token: Option<&Bearer>,
req: StealthUtxosDecryptValueRequest,
) -> Result<StealthUtxosDecryptValueResponse, anyhow::Error> {
let sdk = context.wallet_sdk();
context.check_auth(token, &[JrpcPermission::Admin])?;

if req.ids.len() > 10 {
return Err(invalid_params(
"ids",
Some("Cannot request more than 10 UTXOs at a time"),
));
}

let utxo_ids = req
.ids
.into_iter()
.map(|id| UtxoAddress::new(req.resource_address, id))
.map(Into::into)
.collect::<Vec<_>>();

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 value_range = req.minimum_expected_value.unwrap_or(0)..=req.maximum_expected_value.unwrap_or(10_000_000_000);

// NOTE: we iterate in a random order (HashMap) but collect into a deterministic order (IndexMap) so that the
Comment thread
sdbondi marked this conversation as resolved.
// results are always in the same order for the same input
let outputs = substates
.iter()
.filter_map(|(id, s)| {
let id = id.as_utxo_address().map(|a| a.into_contents().id)?;
let output = s
.substate_value()
.as_utxo()
.and_then(|u| u.output())
.map(|o| &o.output)?;
Some((id, output))
})
.collect::<IndexMap<_, _>>();

let timer = Instant::now();
let balances = match context.config().value_lookup_table_file.as_ref() {
Some(file) => {
let mut file = fs::File::open(file)
.map_err(|e| anyhow!("Unable to load value lookup file '{}': {e}", file.display()))?;
let mut lookup = IoReaderValueLookup::load(&mut file)?;

block_in_place(|| {
sdk.viewable_balance_api().try_brute_force_commitment_balances(
&view_key.key,
outputs.values().copied(), // Copying the reference, not the PrivateOutput
value_range,
&mut lookup,
)
})?
},
None => block_in_place(|| {
sdk.viewable_balance_api().try_brute_force_commitment_balances(
&view_key.key,
outputs.values().copied(),
value_range,
&mut AlwaysMissLookupTable,
)
})?,
};

info!(target: LOG_TARGET, "Brute force balance lookup took {:.2?}", timer.elapsed());

Ok(StealthUtxosDecryptValueResponse {
balances: outputs.into_keys().zip(balances).collect(),
})
}
11 changes: 4 additions & 7 deletions applications/tari_walletd/src/jrpc_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,13 +182,10 @@ async fn handler(
"claim_fees" => call_handler(context, value, token, validator::handle_claim_validator_fees).await,
_ => Ok(value.method_not_found(&value.method)),
},
Some(("stealth_utxos", method)) =>
{
#[allow(clippy::collapsible_match)]
match method {
"list" => call_handler(context, value, token, stealth_utxos::handle_list).await,
_ => Ok(value.method_not_found(&value.method)),
}
Some(("stealth_utxos", method)) => match method {
"list" => call_handler(context, value, token, stealth_utxos::handle_list).await,
"decrypt_value" => call_handler(context, value, token, stealth_utxos::handle_decrypt_value).await,
_ => Ok(value.method_not_found(&value.method)),
},
Some(("wallet", "get_info")) => call_handler(context, value, token, wallet::handle_get_info).await,
_ => Ok(value.method_not_found(&value.method)),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ResourceAddress } from "../ResourceAddress";
import type { UtxoId } from "../UtxoId";

export type StealthUtxosDecryptValueRequest = {
resource_address: ResourceAddress;
ids: Array<UtxoId>;
view_key_id: bigint;
minimum_expected_value: bigint | null;
maximum_expected_value: bigint | null;
};
Comment thread
sdbondi marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { UtxoId } from "../UtxoId";

export type StealthUtxosDecryptValueResponse = { balances: { [key in UtxoId]?: bigint | null } };
2 changes: 2 additions & 0 deletions bindings/src/wallet-daemon-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export * from "./types/wallet-daemon-client/TransactionGetAllRequest";
export * from "./types/wallet-daemon-client/WebauthnAlreadyRegisteredRequest";
export * from "./types/wallet-daemon-client/AuthMethod";
export * from "./types/wallet-daemon-client/AuthLoginDenyResponse";
export * from "./types/wallet-daemon-client/StealthUtxosDecryptValueResponse";
export * from "./types/wallet-daemon-client/MintFaucetNftRequest";
export * from "./types/wallet-daemon-client/AuthLoginDenyRequest";
export * from "./types/wallet-daemon-client/PublishTemplateRequest";
Expand Down Expand Up @@ -108,6 +109,7 @@ export * from "./types/wallet-daemon-client/ProofsCancelRequest";
export * from "./types/wallet-daemon-client/AccountSetDefaultResponse";
export * from "./types/wallet-daemon-client/GetNftRequest";
export * from "./types/wallet-daemon-client/SubstatesListResponse";
export * from "./types/wallet-daemon-client/StealthUtxosDecryptValueRequest";
export * from "./types/wallet-daemon-client/AccountOrKeyIndex";
export * from "./types/wallet-daemon-client/WebauthnStartAuthResponse";
export * from "./types/wallet-daemon-client/TransferNftRequest";
Expand Down
2 changes: 1 addition & 1 deletion clients/javascript/wallet_daemon_client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tari-project/wallet_jrpc_client",
"version": "1.9.1",
"version": "1.9.2",
"description": "Tari wallet JSON-RPC client library",
"homepage": "https://github.com/tari-project/tari-ootle#readme",
"bugs": {
Expand Down
18 changes: 15 additions & 3 deletions clients/javascript/wallet_daemon_client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import type {
AccountGetDefaultRequest,
AccountGetRequest,
AccountGetResponse, AccountsAssociateStealthResourceRequest, AccountsAssociateStealthResourceResponse,
AccountGetResponse,
AccountsAssociateStealthResourceRequest,
AccountsAssociateStealthResourceResponse,
AccountsCreateFreeTestCoinsRequest,
AccountsCreateFreeTestCoinsResponse,
AccountsCreateRequest,
Expand All @@ -16,7 +18,9 @@ import type {
AccountsGetBalancesRequest,
AccountsGetBalancesResponse,
AccountsListRequest,
AccountsListResponse, AccountsRenameRequest, AccountsRenameResponse,
AccountsListResponse,
AccountsRenameRequest,
AccountsRenameResponse,
AccountsTransferRequest,
AccountsTransferResponse,
AuthGetAllJwtRequest,
Expand Down Expand Up @@ -50,7 +54,11 @@ import type {
rejectReasonToString,
SettingsGetResponse,
SettingsSetRequest,
SettingsSetResponse, StealthTransferRequest, StealthTransferResponse, StealthUtxosListRequest,
SettingsSetResponse,
StealthTransferRequest,
StealthTransferResponse,
StealthUtxosDecryptValueRequest, StealthUtxosDecryptValueResponse,
StealthUtxosListRequest,
StealthUtxosListResponse,
stringToSubstateId,
substateIdToString,
Expand Down Expand Up @@ -342,6 +350,10 @@ export class WalletDaemonClient {
}


public stealthUtxosDecryptValue(params: StealthUtxosDecryptValueRequest): Promise<StealthUtxosDecryptValueResponse> {
return this.__invokeRpc("stealth_utxos.decrypt_value", params);
}

async __invokeRpc(method: string, params: object = null) {
const id = this.id++;
const response = await this.transport.sendRequest<any>(
Expand Down
9 changes: 9 additions & 0 deletions clients/wallet_daemon_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ use crate::{
SettingsGetResponse,
StealthTransferRequest,
StealthTransferResponse,
StealthUtxosDecryptValueRequest,
StealthUtxosDecryptValueResponse,
StealthUtxosListRequest,
StealthUtxosListResponse,
TransactionGetAllRequest,
Expand Down Expand Up @@ -479,6 +481,13 @@ impl WalletDaemonClient {
self.send_request("stealth_utxos.list", request.borrow()).await
}

pub async fn stealth_utxos_decrypt_value<T: Borrow<StealthUtxosDecryptValueRequest>>(
&mut self,
request: T,
) -> Result<StealthUtxosDecryptValueResponse, WalletDaemonClientError> {
self.send_request("stealth_utxos.decrypt_value", request.borrow()).await
}

pub async fn get_settings(&mut self) -> Result<SettingsGetResponse, WalletDaemonClientError> {
self.send_request("settings.get", &json!({})).await
}
Expand Down
26 changes: 25 additions & 1 deletion clients/wallet_daemon_client/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,15 @@ use tari_ootle_wallet_sdk::{
};
use tari_template_abi::{FunctionDef, TemplateDef};
use tari_template_lib::{
models::{ConfidentialOutputStatement, EncryptedData, NonFungibleId, ResourceAddress, UtxoAddress, VaultId},
models::{
ConfidentialOutputStatement,
EncryptedData,
NonFungibleId,
ResourceAddress,
UtxoAddress,
UtxoId,
VaultId,
},
prelude::{ComponentAddress, ConfidentialWithdrawProof, ResourceType, RistrettoPublicKeyBytes},
types::{crypto::PedersenCommitmentBytes, Amount, TemplateAddress},
};
Expand Down Expand Up @@ -1118,3 +1126,19 @@ pub struct UtxoInfo {
pub is_frozen: bool,
pub is_on_chain: bool,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export, export_to = "wallet-daemon-client/"))]
pub struct StealthUtxosDecryptValueRequest {
pub resource_address: ResourceAddress,
pub ids: Vec<UtxoId>,
pub view_key_id: u64,
pub minimum_expected_value: Option<u64>,
pub maximum_expected_value: Option<u64>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export, export_to = "wallet-daemon-client/"))]
pub struct StealthUtxosDecryptValueResponse {
pub balances: HashMap<UtxoId, Option<u64>>,
}
Comment thread
sdbondi marked this conversation as resolved.
8 changes: 6 additions & 2 deletions crates/template_lib/src/models/utxo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ impl UtxoAddress {
pub fn id(&self) -> &UtxoId {
&self.0.inner().id
}

pub fn into_contents(self) -> UtxoAddressContents {
self.0.into_inner()
}
}

impl FromStr for UtxoAddress {
Expand Down Expand Up @@ -120,8 +124,8 @@ impl Display for UtxoId {
#[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export))]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
pub struct UtxoAddressContents {
resource_address: ResourceAddress,
id: UtxoId,
pub resource_address: ResourceAddress,
pub id: UtxoId,
}

#[cfg(feature = "borsh")]
Expand Down
41 changes: 0 additions & 41 deletions crates/wallet/sdk/src/apis/confidential_crypto.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
// Copyright 2023 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

use std::ops::RangeInclusive;

use tari_common_types::types::PrivateKey;
use tari_crypto::ristretto::RistrettoPublicKey;
use tari_engine_types::{
crypto::{ElgamalVerifiableBalance, PrivateOutput, ValueLookupTable},
ConvertFromByteType,
};
use tari_ootle_wallet_crypto::{
confidential,
encrypted_data::{encrypt_value_and_mask, extract_value_and_mask, unblind_output},
Expand Down Expand Up @@ -119,39 +113,6 @@ impl ConfidentialCryptoApi {
)?;
Ok(unmasked_output)
}

pub fn try_brute_force_commitment_balances<'a, TLookup, TOutputsIter>(
&self,
secret_view_key: &PrivateKey,
outputs: TOutputsIter,
value_range: RangeInclusive<u64>,
lookup: &mut TLookup,
) -> Result<Vec<Option<u64>>, ConfidentialCryptoApiError>
where
TLookup: ValueLookupTable,
TOutputsIter: Iterator<Item = &'a PrivateOutput>,
{
let outputs_viewable_balance_decompressed = outputs
.filter_map(|output| output.viewable_balance.as_ref())
.map(ElgamalVerifiableBalance::convert_from_byte_type)
.collect::<Result<Vec<_>, _>>()
.map_err(|_| WalletCryptoError::InvalidArgument {
name: "outputs",
details: "Malformed viewable balance in output when decompressing ElgamalVerifiableBalance for brute \
forcing"
.to_string(),
})?;

let results = ElgamalVerifiableBalance::batched_brute_force(
secret_view_key,
value_range,
lookup,
&outputs_viewable_balance_decompressed,
)
.map_err(|e| ConfidentialCryptoApiError::ValueLookupTableError { details: e.to_string() })?;

Ok(results)
}
}

#[derive(Debug, thiserror::Error)]
Expand All @@ -160,8 +121,6 @@ pub enum ConfidentialCryptoApiError {
WalletCryptoError(#[from] WalletCryptoError),
#[error("Confidential proof error: {0}")]
ConfidentialProofError(#[from] ConfidentialProofError),
#[error("Value lookup table error: {details}")]
ValueLookupTableError { details: String },
#[error("Negative amount")]
NegativeAmount,
}
1 change: 1 addition & 0 deletions crates/wallet/sdk/src/apis/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ pub mod stealth_transfer;
pub mod substate;
pub mod template;
pub mod transaction;
pub mod viewable_balance;
Loading
Loading