diff --git a/applications/tari_app_utilities/src/fee_tables.rs b/applications/tari_app_utilities/src/fee_tables.rs new file mode 100644 index 0000000000..3f94b4dd60 --- /dev/null +++ b/applications/tari_app_utilities/src/fee_tables.rs @@ -0,0 +1,35 @@ +// Copyright 2025 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use tari_engine::fees::FeeTable; +use tari_ootle_common_types::Network; + +const TESTNET_FEE_TABLE: FeeTable = FeeTable { + per_transaction_weight_cost: 1, + per_module_call_cost: 1, + per_byte_storage_cost: 1, + per_event_cost: 1, + per_log_cost: 1, + per_signature_verification_cost: 10, +}; + +// TODO: finalize these values +const MAINNET_FEE_TABLE: FeeTable = FeeTable { + per_transaction_weight_cost: 1, + per_module_call_cost: 1, + per_byte_storage_cost: 1, + per_event_cost: 1, + per_log_cost: 1, + per_signature_verification_cost: 10, +}; + +pub const fn get_fee_table_by_network(network: Network) -> &'static FeeTable { + match network { + Network::LocalNet => &TESTNET_FEE_TABLE, + Network::Igor => &TESTNET_FEE_TABLE, + Network::Esmeralda => &TESTNET_FEE_TABLE, + Network::StageNet => &TESTNET_FEE_TABLE, + Network::NextNet => &TESTNET_FEE_TABLE, + Network::MainNet => &MAINNET_FEE_TABLE, + } +} diff --git a/applications/tari_app_utilities/src/lib.rs b/applications/tari_app_utilities/src/lib.rs index 638ec1fe27..33f2f5f0a1 100644 --- a/applications/tari_app_utilities/src/lib.rs +++ b/applications/tari_app_utilities/src/lib.rs @@ -23,6 +23,7 @@ pub mod common; pub mod configuration; pub mod epoch_oracle_config; +pub mod fee_tables; pub mod keypair; pub mod p2p_config; pub mod seed_peer; diff --git a/applications/tari_indexer/src/dry_run/processor.rs b/applications/tari_indexer/src/dry_run/processor.rs index 3af35f56fa..f984c13ea5 100644 --- a/applications/tari_indexer/src/dry_run/processor.rs +++ b/applications/tari_indexer/src/dry_run/processor.rs @@ -23,14 +23,17 @@ use std::collections::HashMap; use log::{debug, info}; -use tari_engine::{fees::FeeTable, state_store::new_memory_store, transaction::TransactionProcessorConfig}; +use tari_engine::{state_store::new_memory_store, transaction::TransactionProcessorConfig}; use tari_engine_types::{ commit_result::ExecuteResult, substate::{Substate, SubstateId}, virtual_substate::{VirtualSubstate, VirtualSubstateId, VirtualSubstates}, }; use tari_epoch_manager::{service::EpochManagerHandle, EpochManagerReader}; -use tari_ootle_app_utilities::transaction_executor::{TariTransactionProcessor, TransactionExecutor as _}; +use tari_ootle_app_utilities::{ + fee_tables::get_fee_table_by_network, + transaction_executor::{TariTransactionProcessor, TransactionExecutor as _}, +}; use tari_ootle_common_types::{Epoch, PeerAddress, SubstateRequirement}; use tari_template_manager::implementation::TemplateManager; use tari_transaction::Transaction; @@ -81,7 +84,9 @@ impl DryRunTransactionProcessor { let epoch = self.epoch_manager.current_epoch().await?; let found_substates = self.fetch_input_substates(&transaction, epoch).await?; - let payload_processor = self.build_payload_processor(&transaction); + let fee_table = get_fee_table_by_network(self.config.network); + let payload_processor = + TariTransactionProcessor::new(self.config.clone(), self.template_manager.clone(), fee_table.clone()); let virtual_substates = self.get_virtual_substates(&transaction, epoch).await?; @@ -96,31 +101,6 @@ impl DryRunTransactionProcessor { Ok(exec_output.result) } - fn build_payload_processor( - &self, - transaction: &Transaction, - ) -> TariTransactionProcessor> { - // simulate fees if the transaction requires it - let fee_table = if Self::transaction_includes_fees(transaction) { - // TODO: should match the VN fee table, should the fee table values be a consensus constant? - FeeTable { - per_transaction_weight_cost: 1, - per_module_call_cost: 1, - per_byte_storage_cost: 1, - per_event_cost: 1, - per_log_cost: 1, - } - } else { - FeeTable::zero_rated() - }; - - TariTransactionProcessor::new(self.config.clone(), self.template_manager.clone(), fee_table) - } - - fn transaction_includes_fees(transaction: &Transaction) -> bool { - !transaction.fee_instructions().is_empty() - } - async fn fetch_input_substates( &self, transaction: &Transaction, diff --git a/applications/tari_indexer/src/storage_sqlite/store_factory.rs b/applications/tari_indexer/src/storage_sqlite/store_factory.rs index 23a63558ed..26b0b27c55 100644 --- a/applications/tari_indexer/src/storage_sqlite/store_factory.rs +++ b/applications/tari_indexer/src/storage_sqlite/store_factory.rs @@ -24,8 +24,10 @@ use tari_ootle_storage_sqlite::{error::SqliteStorageError, SqliteTransaction}; use tari_ootle_wallet_sdk::models::WalletUtxoUpdate; use tari_template_lib::{ models::ResourceAddress, - prelude::{crypto::UtxoTag, RistrettoPublicKeyBytes}, - types::TemplateAddress, + types::{ + crypto::{RistrettoPublicKeyBytes, UtxoTag}, + TemplateAddress, + }, }; use tari_transaction::{Transaction, TransactionId}; diff --git a/applications/tari_indexer/src/substate_manager.rs b/applications/tari_indexer/src/substate_manager.rs index 091d30a614..80379ed16e 100644 --- a/applications/tari_indexer/src/substate_manager.rs +++ b/applications/tari_indexer/src/substate_manager.rs @@ -42,8 +42,10 @@ use tari_ootle_common_types::{ use tari_ootle_wallet_sdk::models::WalletUtxoUpdate; use tari_template_lib::{ models::ResourceAddress, - prelude::{crypto::UtxoTag, RistrettoPublicKeyBytes}, - types::TemplateAddress, + types::{ + crypto::{RistrettoPublicKeyBytes, UtxoTag}, + TemplateAddress, + }, }; use tari_validator_node_rpc::client::{SubstateResult, TariValidatorNodeRpcClientFactory}; diff --git a/applications/tari_validator_node/src/bootstrap.rs b/applications/tari_validator_node/src/bootstrap.rs index 9443c2302b..ca4d2dde84 100644 --- a/applications/tari_validator_node/src/bootstrap.rs +++ b/applications/tari_validator_node/src/bootstrap.rs @@ -36,7 +36,7 @@ use tari_consensus::consensus_constants::ConsensusConstants; #[cfg(not(feature = "metrics"))] use tari_consensus::traits::hooks::NoopHooks; use tari_crypto::tari_utilities::ByteArray; -use tari_engine::{fees::FeeTable, transaction::TransactionProcessorConfig}; +use tari_engine::transaction::TransactionProcessorConfig; use tari_engine_types::ToByteType; use tari_epoch_manager::{ service::{EpochManagerConfig, EpochManagerHandle}, @@ -54,6 +54,7 @@ use tari_ootle_app_utilities::{ common::verify_correct_network, configuration::convert_network_to_l1_network, epoch_oracle_config::{BaseLayerOracleConfig, EpochOracleType}, + fee_tables::get_fee_table_by_network, keypair::RistrettoKeypair, seed_peer::SeedPeer, template_download_queue::TemplateDownloadQueue, @@ -269,13 +270,6 @@ pub async fn spawn_services( info!(target: LOG_TARGET, "Payload processor initializing"); // Payload processor - let fee_table = FeeTable { - per_transaction_weight_cost: 1, - per_module_call_cost: 1, - per_byte_storage_cost: 1, - per_event_cost: 1, - per_log_cost: 1, - }; let (tx_hotstuff_events, _) = broadcast::channel(100); // Consensus gossip @@ -305,12 +299,13 @@ pub async fn spawn_services( message_logger.clone(), ); - // Consensus + // Transaction executor + let fee_table = get_fee_table_by_network(config.network); let payload_processor = TariTransactionProcessor::new( TransactionProcessorConfig::new(config.network) .with_template_binary_max_size_bytes(consensus_constants.template_binary_max_size_bytes), template_manager.clone(), - fee_table, + fee_table.clone(), ); let transaction_executor = TarBlockTransactionExecutor::new( payload_processor.clone(), @@ -330,6 +325,7 @@ pub async fn spawn_services( .as_ref() .map(|pk| pk.to_byte_type()); + // Consensus let signing_service = consensus::TariSignatureService::new(keypair.clone()); let (consensus_join_handle, consensus_handle) = consensus::spawn( config.network, diff --git a/crates/common_types/src/engine_signature.rs b/crates/common_types/src/engine_signature.rs new file mode 100644 index 0000000000..92eb78c408 --- /dev/null +++ b/crates/common_types/src/engine_signature.rs @@ -0,0 +1,112 @@ +// Copyright 2025 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use blake2::{ + digest::{consts, generic_array::GenericArray}, + Blake2b, + Digest, +}; +use tari_crypto::ristretto::RistrettoSchnorr; +use tari_engine_types::{ConvertFromByteType, FromByteType, ToByteType}; +use tari_template_lib_types::crypto::{PublicKey, RistrettoPublicKeyBytes, SignaturePayload}; + +pub trait GetVerifier { + fn get_verifier(&self) -> &dyn Verifier; +} + +impl GetVerifier for SignaturePayload { + fn get_verifier(&self) -> &dyn Verifier { + match self { + SignaturePayload::RistrettoSchnorrBlake2b(_) => &RistrettoSchnorrBlake2bVerifier, + } + } +} + +pub trait Verifier { + fn verify(&self, domain: &[u8], message: &[u8], public_key: &PublicKey, signature: &SignaturePayload) -> bool; +} + +/// A verifier for Ristretto Schnorr signatures with Blake2b hashing. +pub struct RistrettoSchnorrBlake2bVerifier; + +impl RistrettoSchnorrBlake2bVerifier { + pub fn compute_challenge( + domain: &[u8], + message: &[u8], + public_key: &RistrettoPublicKeyBytes, + nonce: &RistrettoPublicKeyBytes, + ) -> GenericArray { + Blake2b::::new() + // Domain + .chain_update(domain) + // Fiat-Shamir - note that we force this on the user to avoid security pitfalls. + .chain_update(nonce.as_bytes()) + .chain_update(public_key.as_bytes()) + // Message + .chain_update(message) + .finalize() + } +} + +impl Verifier for RistrettoSchnorrBlake2bVerifier { + fn verify(&self, domain: &[u8], message: &[u8], public_key: &PublicKey, signature: &SignaturePayload) -> bool { + let sig = signature + .ristretto_schnorr_blake2b() + .expect("Expected Ristretto Schnorr signature"); + let ristretto_public_key = public_key.ristretto25519().expect("Expected Ristretto PublicKey"); + + let Ok(pk) = ristretto_public_key.try_from_byte_type() else { + return false; + }; + + let Ok(sig) = RistrettoSchnorr::convert_from_byte_type(sig) else { + return false; + }; + + // NOTE: this is general purpose signature verification, and we want to user to specify a domain, without + // forcing tari-style domains to be mixed in. + let message = Self::compute_challenge( + domain, + message, + ristretto_public_key, + &sig.get_public_nonce().to_byte_type(), + ); + sig.verify_raw_uniform(&pk, &message) + } +} + +#[cfg(test)] +mod tests { + use rand::rngs::OsRng; + use tari_crypto::{ + keys::PublicKey as _, + ristretto::{RistrettoPublicKey, RistrettoSchnorr}, + }; + use tari_engine_types::ToByteType; + + use super::*; + + #[test] + fn it_verifies_a_valid_signature() { + let (secret_key, public_key) = RistrettoPublicKey::random_keypair(&mut OsRng); + + let message = b"Hello, world!"; + let domain = b"Test domain"; + + let (nonce, public_nonce) = RistrettoPublicKey::random_keypair(&mut OsRng); + + let hashed_message = RistrettoSchnorrBlake2bVerifier::compute_challenge( + domain, + message, + &public_key.to_byte_type(), + &public_nonce.to_byte_type(), + ); + let signature = RistrettoSchnorr::sign_raw_uniform(&secret_key, nonce, &hashed_message).unwrap(); + + let signature_payload = SignaturePayload::RistrettoSchnorrBlake2b(signature.to_byte_type()); + let public_key = PublicKey::Ristretto25519(public_key.to_byte_type()); + + let verifier = RistrettoSchnorrBlake2bVerifier; + assert!(verifier.verify(domain, message, &public_key, &signature_payload)); + } +} diff --git a/crates/common_types/src/lib.rs b/crates/common_types/src/lib.rs index 4896e7379d..a205dcfc42 100644 --- a/crates/common_types/src/lib.rs +++ b/crates/common_types/src/lib.rs @@ -8,6 +8,7 @@ pub mod committee; mod consensus_constants; pub mod crypto; pub mod displayable; +mod engine_signature; mod epoch; mod era; mod extra_data; @@ -35,6 +36,7 @@ mod vote_power; pub use bytes::*; pub use consensus_constants::*; +pub use engine_signature::*; pub use epoch::Epoch; pub use era::*; pub use extra_data::*; diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index 0bee5534ad..1c22ba9893 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -10,7 +10,7 @@ license.workspace = true [dependencies] tari_bor = { workspace = true, default-features = true, features = ["std"] } tari_common_types = { workspace = true } -tari_crypto = { workspace = true, features = ["borsh"] } +tari_crypto = { workspace = true, features = ["serde", "borsh"] } tari_ootle_common_types = { workspace = true } tari_engine_types = { workspace = true } tari_template_abi = { workspace = true, features = ["std"] } diff --git a/crates/engine/src/fees/fee_module.rs b/crates/engine/src/fees/fee_module.rs index 61739483ef..40a745c86f 100644 --- a/crates/engine/src/fees/fee_module.rs +++ b/crates/engine/src/fees/fee_module.rs @@ -5,7 +5,7 @@ use tari_bor::{encode_into_writer, ByteCounter}; use tari_engine_types::fees::FeeSource; use super::FeeTable; -use crate::runtime::{RuntimeModule, RuntimeModuleError, StateTracker}; +use crate::runtime::{RuntimeEvent, RuntimeModule, RuntimeModuleError, StateTracker}; pub struct FeeModule { initial_cost: u64, @@ -67,4 +67,17 @@ impl RuntimeModule for FeeModule { Ok(()) } + + fn on_runtime_event(&self, track: &StateTracker, call: &RuntimeEvent) -> Result<(), RuntimeModuleError> { + match call { + RuntimeEvent::SignatureVerified => { + track.add_fee_charge( + FeeSource::SignatureVerification, + self.fee_table.per_signature_verification_cost(), + ); + }, + } + + Ok(()) + } } diff --git a/crates/engine/src/fees/fee_table.rs b/crates/engine/src/fees/fee_table.rs index 00d346fb7c..1c1236bf15 100644 --- a/crates/engine/src/fees/fee_table.rs +++ b/crates/engine/src/fees/fee_table.rs @@ -8,6 +8,7 @@ pub struct FeeTable { pub per_byte_storage_cost: u64, pub per_event_cost: u64, pub per_log_cost: u64, + pub per_signature_verification_cost: u64, } impl FeeTable { @@ -18,6 +19,7 @@ impl FeeTable { per_byte_storage_cost: 0, per_event_cost: 0, per_log_cost: 0, + per_signature_verification_cost: 0, } } @@ -40,4 +42,8 @@ impl FeeTable { pub fn per_log_cost(&self) -> u64 { self.per_log_cost } + + pub fn per_signature_verification_cost(&self) -> u64 { + self.per_signature_verification_cost + } } diff --git a/crates/engine/src/runtime/impl.rs b/crates/engine/src/runtime/impl.rs index 7a05568788..3690833c6c 100644 --- a/crates/engine/src/runtime/impl.rs +++ b/crates/engine/src/runtime/impl.rs @@ -58,6 +58,7 @@ use tari_engine_types::{ use tari_ootle_common_types::{ base_layer_hashing::ownership_proof_hasher64, services::template_provider::TemplateProvider, + GetVerifier, Network, }; use tari_template_abi::{TemplateDef, Type}; @@ -123,10 +124,16 @@ use tari_template_lib::{ prelude::{ResourceType, RistrettoPublicKeyBytes}, resource::{IMAGE_URL, TOKEN_SYMBOL}, template::BuiltinTemplate, - types::{crypto::UtxoTag, Amount, EntityId, TemplateAddress}, + types::{ + crypto::UtxoTag, + engine_args::{SignatureAction, SignatureVerifyArg}, + Amount, + EntityId, + TemplateAddress, + }, }; -use super::{working_state::WorkingState, Runtime}; +use super::{working_state::WorkingState, Runtime, RuntimeEvent}; use crate::{ runtime::{ engine_args::EngineArgs, @@ -199,6 +206,13 @@ impl> RuntimeInte Ok(()) } + fn invoke_modules_on_runtime_event(&self, event: RuntimeEvent) -> Result<(), RuntimeError> { + for module in &self.modules { + module.on_runtime_event(&self.tracker, &event)?; + } + Ok(()) + } + pub fn get_template_def(&self, template_address: &TemplateAddress) -> Result { let loaded = self .template_provider @@ -2612,6 +2626,26 @@ impl> RuntimeInte }) } + fn signature_invoke(&self, action: SignatureAction, args: EngineArgs) -> Result { + self.invoke_modules_on_runtime_call("signature_invoke")?; + + match action { + SignatureAction::Verify => { + self.invoke_modules_on_runtime_event(RuntimeEvent::SignatureVerified)?; + + let SignatureVerifyArg { + public_key, + domain, + message, + payload, + } = args.assert_one_arg()?; + + let is_valid = payload.get_verifier().verify(&domain, &message, &public_key, &payload); + Ok(InvokeResult::encode(&is_valid)?) + }, + } + } + /// Create a new address allocation for the provided substate type and entity id fn allocate_address( &self, diff --git a/crates/engine/src/runtime/mod.rs b/crates/engine/src/runtime/mod.rs index dcca3f1c89..fc28bab774 100644 --- a/crates/engine/src/runtime/mod.rs +++ b/crates/engine/src/runtime/mod.rs @@ -36,7 +36,7 @@ mod actions; pub use actions::*; mod module; -pub use module::{RuntimeModule, RuntimeModuleError}; +pub use module::{RuntimeEvent, RuntimeModule, RuntimeModuleError}; mod fee_state; mod tracker; @@ -95,7 +95,7 @@ use tari_template_lib::{ }, invoke_args, models::{BucketId, ComponentAddress, Metadata, NonFungibleAddress, StealthTransferStatement, VaultRef}, - types::EntityId, + types::{engine_args::SignatureAction, EntityId}, }; pub use tracker::StateTracker; @@ -192,6 +192,8 @@ pub trait RuntimeInterface: Send + Sync { fn pop_call_frame(&self) -> Result<(), RuntimeError>; fn publish_template(&self, template: Vec) -> Result<(), RuntimeError>; + fn signature_invoke(&self, action: SignatureAction, args: EngineArgs) -> Result; + fn allocate_address( &self, substate_type: AllocatableAddressType, diff --git a/crates/engine/src/runtime/module.rs b/crates/engine/src/runtime/module.rs index 203b5fba25..f40378bbf3 100644 --- a/crates/engine/src/runtime/module.rs +++ b/crates/engine/src/runtime/module.rs @@ -15,6 +15,15 @@ pub trait RuntimeModule: Send + Sync { fn on_before_finalize(&self, _track: &StateTracker) -> Result<(), RuntimeModuleError> { Ok(()) } + + fn on_runtime_event(&self, _track: &StateTracker, _call: &RuntimeEvent) -> Result<(), RuntimeModuleError> { + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub enum RuntimeEvent { + SignatureVerified, } #[derive(Debug, thiserror::Error)] diff --git a/crates/engine/src/wasm/process.rs b/crates/engine/src/wasm/process.rs index 26f566f603..7dbe510ea6 100644 --- a/crates/engine/src/wasm/process.rs +++ b/crates/engine/src/wasm/process.rs @@ -51,6 +51,7 @@ use tari_template_lib::{ VaultInvokeArg, WorkspaceInvokeArg, }, + types::engine_args::SignatureInvokeArg, AbiContext, }; use wasmer::{imports, AsStoreMut, Function, FunctionEnv, FunctionEnvMut, Instance, Store, StoreMut, WasmPtr}; @@ -209,6 +210,9 @@ impl WasmProcess { env.interface().builtin_template_invoke(arg.action) }) }, + EngineOp::SignatureInvoke => Self::handle(store, env_mut, arg, |env, arg: SignatureInvokeArg| { + env.interface().signature_invoke(arg.action, arg.args.into()) + }), }; result.unwrap_or_else(|err| { diff --git a/crates/engine/tests/signature.rs b/crates/engine/tests/signature.rs new file mode 100644 index 0000000000..3892ae830b --- /dev/null +++ b/crates/engine/tests/signature.rs @@ -0,0 +1,171 @@ +// Copyright 2025 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use rand::rngs::OsRng; +use tari_crypto::{ + keys::PublicKey as _, + ristretto::{RistrettoPublicKey, RistrettoSchnorr, RistrettoSecretKey}, +}; +use tari_engine_types::ToByteType; +use tari_ootle_common_types::{ + crypto::create_key_pair_from_seed, + substate_type::SubstateType, + RistrettoSchnorrBlake2bVerifier, +}; +use tari_template_lib::{ + models::ComponentAddress, + prelude::PublicKey, + types::{ + amount, + crypto::{NoSignatureDomain, Signature}, + Amount, + }, +}; +use tari_template_test_tooling::{ + support::{assert_error::assert_reject_reason, stealth}, + TemplateTest, +}; +use tari_transaction::{args, Transaction}; + +const TEMPLATE_PATHS: &[&str] = &["tests/templates/signature"]; +const TEMPLATE_NAME: &str = "SignatureTest"; +const MESSAGE: &[u8] = b"Some message that binds to something important"; +const INITIAL_SUPPLY: Amount = amount!("1000000000000000000000"); + +const TEST_DOMAIN: &[u8] = b"tari.test.signature domain for tests"; +fn sign_it(secret: &RistrettoSecretKey) -> Signature { + sign_it_with(secret, MESSAGE) +} + +fn sign_it_with(secret: &RistrettoSecretKey, message: &[u8]) -> Signature { + let (nonce, nonce_pub) = RistrettoPublicKey::random_keypair(&mut OsRng); + let public_key = RistrettoPublicKey::from_secret_key(secret); + let challenge = RistrettoSchnorrBlake2bVerifier::compute_challenge( + TEST_DOMAIN, + message, + &public_key.to_byte_type(), + &nonce_pub.to_byte_type(), + ); + let sig = RistrettoSchnorr::sign_raw_uniform(secret, nonce, &challenge).unwrap(); + sig.to_byte_type().into() +} + +fn setup(allow_list: Vec) -> (TemplateTest, ComponentAddress) { + let mut test = TemplateTest::new(TEMPLATE_PATHS); + let template_addr = test.get_template_address(TEMPLATE_NAME); + + let transaction = Transaction::builder() + .call_function(template_addr, "new", args![allow_list]) + .build_and_seal(test.secret_key()); + + test.execute_expect_success(transaction, vec![]); + + let faucet = test.get_previous_output_address(SubstateType::Component); + + (test, faucet.as_component_address().unwrap()) +} + +#[test] +fn claim_with_valid_signature() { + let (s1, p1) = create_key_pair_from_seed(1); + let p1 = PublicKey::from(p1.to_byte_type()); + let (mut test, faucet) = setup(vec![p1]); + + let vault_id = test + .get_previous_output_address(SubstateType::Vault) + .as_vault_id() + .unwrap(); + + let transfer = stealth::generate_transfer_data(&[], 1_000_000_000_000u64, Some(1_000_000_000_000), 0); + let signature = sign_it(&s1); + let result = test.execute_expect_success( + Transaction::builder() + .call_method(faucet, "claim_funds", args![p1, signature, transfer.statement]) + .build_and_seal(test.secret_key()), + vec![], + ); + + let diff = result.finalize.any_accept().unwrap(); + let utxos = diff + .up_iter() + .filter_map(|(_, substate)| substate.substate_value().as_utxo()) + .collect::>(); + assert_eq!(utxos.len(), 1); + assert!(utxos[0].output().is_some()); + let vault = test.read_only_state_store().get_vault(&vault_id).unwrap(); + assert_eq!(vault.balance(), INITIAL_SUPPLY - amount!("1000000000000")); +} + +#[test] +fn multi_claim() { + let (s1, p1) = create_key_pair_from_seed(1); + let (s2, p2) = create_key_pair_from_seed(2); + let p1 = PublicKey::from(p1.to_byte_type()); + let p2 = PublicKey::from(p2.to_byte_type()); + let (mut test, faucet) = setup(vec![p1, p2]); + + let transfer1 = stealth::generate_transfer_data(&[], 1000, Some(1000), 0); + let transfer2 = stealth::generate_transfer_data(&[], 1000, Some(1000), 0); + let sig1 = sign_it(&s1); + let sig2 = sign_it(&s2); + test.execute_expect_success( + Transaction::builder() + .call_method(faucet, "claim_funds", args![p1, sig1, transfer1.statement]) + .call_method(faucet, "claim_funds", args![p2, sig2, transfer2.statement]) + .build_and_seal(test.secret_key()), + vec![], + ); +} + +#[test] +fn bad_signature() { + let (s1, p1) = create_key_pair_from_seed(1); + let p1 = PublicKey::from(p1.to_byte_type()); + let (mut test, faucet) = setup(vec![p1]); + + let transfer = stealth::generate_transfer_data(&[], 1000, Some(1000), 0); + let sig1 = sign_it_with(&s1, b"A different message"); + let reason = test.execute_expect_failure( + Transaction::builder() + .call_method(faucet, "claim_funds", args![p1, sig1, transfer.statement]) + .build_and_seal(test.secret_key()), + vec![], + ); + + assert_reject_reason(reason.clone(), "Your signature is invalid, so no funds for you"); +} + +#[test] +fn check_signature_api() { + let (s1, p1) = create_key_pair_from_seed(1); + let (_, p2) = create_key_pair_from_seed(2); + let p1 = PublicKey::from(p1.to_byte_type()); + let p2 = PublicKey::from(p2.to_byte_type()); + let (mut test, _) = setup(vec![p1, p2]); + let template_addr = test.get_template_address(TEMPLATE_NAME); + + let good_sig = sign_it(&s1); + let bad_sig = sign_it_with(&s1, b"A different message"); + let result = test.execute_expect_success( + Transaction::builder() + .call_function(template_addr, "check_sig", args![p1, bad_sig]) + .call_function(template_addr, "check_sig", args![p1, good_sig]) + // Bad public key + .call_function(template_addr, "check_sig", args![p2, good_sig]) + .build_and_seal(test.secret_key()), + vec![], + ); + + assert!( + !result.finalize.execution_results[0].decode::().unwrap(), + "Expected bad_sig to be false" + ); + assert!( + result.finalize.execution_results[1].decode::().unwrap(), + "Expected good_sig to be true" + ); + assert!( + !result.finalize.execution_results[2].decode::().unwrap(), + "Expected bad public key to be false" + ); +} diff --git a/crates/engine/tests/templates/signature/Cargo.toml b/crates/engine/tests/templates/signature/Cargo.toml new file mode 100644 index 0000000000..91f3b3ecd1 --- /dev/null +++ b/crates/engine/tests/templates/signature/Cargo.toml @@ -0,0 +1,14 @@ +[workspace] +[package] +name = "signature" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tari_template_lib = { path = "../../../../template_lib" } + + +[lib] +crate-type = ["cdylib", "lib"] diff --git a/crates/engine/tests/templates/signature/src/lib.rs b/crates/engine/tests/templates/signature/src/lib.rs new file mode 100644 index 0000000000..eec0ce5966 --- /dev/null +++ b/crates/engine/tests/templates/signature/src/lib.rs @@ -0,0 +1,85 @@ +// Copyright 2025 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use std::collections::HashSet; + +use tari_template_lib::prelude::*; + +custom_signature_domain!(MyCustomDomain, b"tari.test.signature domain for tests"); + +const SOME_MESSAGE: &[u8] = b"Some message that binds to something important"; + +#[template] +mod template { + use super::*; + + pub struct SignatureTest { + allow_list: HashSet, + manager: ResourceManager, + supply_vault: Vault, + } + + impl SignatureTest { + pub fn new(allow_list: HashSet) -> Component { + let bucket = ResourceBuilder::stealth() + .with_token_symbol("SIGCOIN") + .with_divisibility(9) + .mintable(rule!(allow_all)) + .initial_supply(amount!("1000000000000000000000")); + + let resource_address = bucket.resource_address(); + let supply_vault = Vault::from_bucket(bucket); + + Component::new(Self { + allow_list, + manager: resource_address.into(), + supply_vault, + }) + .with_access_rules(AccessRules::allow_all()) + .create() + } + + pub fn check_sig(public_key: PublicKey, spend_signature: Signature) -> bool { + // Mainly checking the conditional API works i.e. does not panic and returns the expected result. + spend_signature.verify(&public_key, SOME_MESSAGE) + } + + pub fn claim_funds( + &mut self, + public_key: PublicKey, + spend_signature: Signature, + transfer: StealthTransferStatement, + ) { + if !transfer.revealed_input_amount().is_positive() { + panic!("Input amount must be positive"); + } + if transfer.revealed_input_amount() > 1000_000_000_000u64 { + panic!("Cannot claim more than 1000 SIGCOIN at a time"); + } + // 1. Remove the public key from the allow list to prevent double claims + assert!( + self.allow_list.remove(&public_key), + "Public key {public_key} is not in the allow list" + ); + + // 2. Verify that the signature is valid for the public key and the message + // Note: that to prevent replay attacks, some single-use data would need to be included in the message. In + // this case, the public key is removed from the allow list, so it can only be used once. + // A nonce field on the component could also be used. + // This template is about testing signature verification, so the double claim mechanism isn't tested. + if !spend_signature.verify(&public_key.into(), SOME_MESSAGE) { + panic!("Your signature is invalid, so no funds for you"); + } + + let input_bucket = self.supply_vault.withdraw(transfer.inputs_statement.revealed_amount); + + let bucket = self + .manager + .stealth_transfer_with_opt_input_bucket(transfer, Some(input_bucket)); + if let Some(bucket) = bucket { + // Any revealed funds are transferred back into the component's vault. + self.supply_vault.deposit(bucket); + } + } + } +} diff --git a/crates/engine_types/src/fees.rs b/crates/engine_types/src/fees.rs index 246cd8fa79..3c2f0cd961 100644 --- a/crates/engine_types/src/fees.rs +++ b/crates/engine_types/src/fees.rs @@ -69,6 +69,7 @@ pub enum FeeSource { Events, Logs, TransactionWeight, + SignatureVerification, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] diff --git a/crates/engine_types/src/utxo.rs b/crates/engine_types/src/utxo.rs index e1a3c63da3..118968533d 100644 --- a/crates/engine_types/src/utxo.rs +++ b/crates/engine_types/src/utxo.rs @@ -11,8 +11,13 @@ use borsh::{BorshDeserialize, BorshSerialize}; use tari_bor::{BorTag, Deserialize, Serialize}; use tari_template_lib::{ models::{BinaryTag, ResourceAddress}, - prelude::{from_hex, serde_helpers, KeyParseError, PedersenCommitmentBytes, RistrettoPublicKeyBytes}, - types::{crypto::UtxoTag, hex::write_hex_fmt}, + types::{ + crypto::{PedersenCommitmentBytes, RistrettoPublicKeyBytes, UtxoTag}, + from_hex, + hex::write_hex_fmt, + serde_helpers, + KeyParseError, + }, }; use crate::crypto::PrivateOutput; diff --git a/crates/template_abi/src/ops.rs b/crates/template_abi/src/ops.rs index 87d746a49c..3a3aebeb5b 100644 --- a/crates/template_abi/src/ops.rs +++ b/crates/template_abi/src/ops.rs @@ -41,6 +41,7 @@ pub enum EngineOp { ProofInvoke = 0x0D, BuiltinTemplateInvoke = 0x0E, AddressAllocationInvoke = 0x0F, + SignatureInvoke = 0x10, } impl EngineOp { @@ -62,6 +63,7 @@ impl EngineOp { 0x0D => Some(EngineOp::ProofInvoke), 0x0E => Some(EngineOp::BuiltinTemplateInvoke), 0x0F => Some(EngineOp::AddressAllocationInvoke), + 0x10 => Some(EngineOp::SignatureInvoke), _ => None, } } diff --git a/crates/template_lib/src/lib.rs b/crates/template_lib/src/lib.rs index 6034811847..8a9a3b0c4e 100644 --- a/crates/template_lib/src/lib.rs +++ b/crates/template_lib/src/lib.rs @@ -46,6 +46,7 @@ pub mod auth; #[macro_use] pub mod args; +#[macro_use] pub mod models; pub mod component; diff --git a/crates/template_lib/src/models/metadata.rs b/crates/template_lib/src/models/metadata.rs index b5f52b3643..3daed07e4a 100644 --- a/crates/template_lib/src/models/metadata.rs +++ b/crates/template_lib/src/models/metadata.rs @@ -81,6 +81,12 @@ impl FromStr for Metadata { } } +impl From<()> for Metadata { + fn from(_: ()) -> Self { + Self::new() + } +} + impl From> for Metadata { fn from(value: BTreeMap) -> Self { Self(BorTag::new(value)) diff --git a/crates/template_lib/src/models/mod.rs b/crates/template_lib/src/models/mod.rs index b26c4ab216..b9af170750 100644 --- a/crates/template_lib/src/models/mod.rs +++ b/crates/template_lib/src/models/mod.rs @@ -35,6 +35,7 @@ mod metadata; mod non_fungible; mod proof; mod resource; +mod signature_verifier; mod stealth; mod system; mod unspent_output; @@ -53,6 +54,7 @@ pub use metadata::*; pub use non_fungible::*; pub use proof::*; pub use resource::ResourceAddress; +pub use signature_verifier::*; pub use stealth::*; pub use system::*; pub use unspent_output::*; diff --git a/crates/template_lib/src/models/signature_verifier.rs b/crates/template_lib/src/models/signature_verifier.rs new file mode 100644 index 0000000000..8288bc7e76 --- /dev/null +++ b/crates/template_lib/src/models/signature_verifier.rs @@ -0,0 +1,51 @@ +// Copyright 2025 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use tari_template_abi::{call_engine, EngineOp}; +use tari_template_lib_types::{ + crypto::{PublicKey, Signature, SignatureDomain, SignaturePayload}, + engine_args::{SignatureAction, SignatureInvokeArg, SignatureVerifyArgRef}, +}; + +use crate::args::InvokeResult; + +pub trait Verifiable { + fn verify(&self, public_key: &PublicKey, message: &[u8]) -> bool; + + fn assert_valid(&self, public_key: &PublicKey, message: &[u8]) { + if !self.verify(public_key, message) { + panic!("Signature verification failed"); + } + } +} + +impl Verifiable for Signature { + fn verify(&self, public_key: &PublicKey, message: &[u8]) -> bool { + SignatureVerifier::with_domain(D::domain()).verify(public_key, message, self.payload()) + } +} +pub struct SignatureVerifier { + domain: &'static [u8], +} + +impl SignatureVerifier { + pub const fn with_domain(domain: &'static [u8]) -> Self { + Self { domain } + } +} + +impl SignatureVerifier { + pub fn verify(&self, public_key: &PublicKey, message: &[u8], payload: &SignaturePayload) -> bool { + let resp: InvokeResult = call_engine(EngineOp::SignatureInvoke, &SignatureInvokeArg { + action: SignatureAction::Verify, + args: invoke_args![SignatureVerifyArgRef { + public_key, + domain: self.domain, + message, + payload, + }], + }); + + resp.decode().expect("Failed to decode signature verification result") + } +} diff --git a/crates/template_lib/src/models/stealth.rs b/crates/template_lib/src/models/stealth.rs index e6ec418386..0d674543b5 100644 --- a/crates/template_lib/src/models/stealth.rs +++ b/crates/template_lib/src/models/stealth.rs @@ -73,3 +73,13 @@ pub struct StealthTransferStatement { #[cfg_attr(feature = "ts", ts(type = "{public_nonce: string, signature: string}"))] pub balance_proof: BalanceProofSignature, } + +impl StealthTransferStatement { + pub fn revealed_input_amount(&self) -> Amount { + self.inputs_statement.revealed_amount + } + + pub fn revealed_output_amount(&self) -> Amount { + self.outputs_statement.revealed_output_amount + } +} diff --git a/crates/template_lib/src/prelude.rs b/crates/template_lib/src/prelude.rs index 24fdc0d737..8f751f6d0e 100644 --- a/crates/template_lib/src/prelude.rs +++ b/crates/template_lib/src/prelude.rs @@ -28,10 +28,15 @@ pub use tari_template_lib_types::{ crypto::{ BalanceProofSignature, PedersenCommitmentBytes, + PublicKey, RistrettoPublicKeyBytes, Scalar32Bytes, SchnorrSignatureBytes, + Signature, + SignatureDomain, + SignaturePayload, }, + custom_signature_domain, TemplateAddress, }; #[cfg(all(feature = "macro", target_arch = "wasm32"))] @@ -69,16 +74,19 @@ pub use crate::{ ProofId, ResourceAddress, ResourceAddressAllocation, + SignatureVerifier, StealthInputsStatement, StealthOutputsStatement, StealthTransferStatement, Vault, VaultId, + Verifiable, }, rand, resource::{ResourceBuilder, ResourceManager, ResourceType}, rule, template::{BuiltinTemplate, TemplateManager}, - types::*, + types, + types::{amount, crypto, Amount}, warn, }; diff --git a/crates/template_lib_types/src/crypto/mod.rs b/crates/template_lib_types/src/crypto/mod.rs index 0144de58d0..b9c5e70150 100644 --- a/crates/template_lib_types/src/crypto/mod.rs +++ b/crates/template_lib_types/src/crypto/mod.rs @@ -12,6 +12,9 @@ mod scalar; mod schnorr; mod utxo_tag; +#[macro_use] +mod signature; + pub use balance_proof::*; pub use commitment::*; pub use commitment_signature::*; @@ -19,6 +22,7 @@ pub use range_proof::*; pub use ristretto::*; pub use scalar::*; pub use schnorr::*; +pub use signature::*; pub use utxo_tag::*; pub use crate::error::*; diff --git a/crates/template_lib_types/src/crypto/signature.rs b/crates/template_lib_types/src/crypto/signature.rs new file mode 100644 index 0000000000..0ae225ecfa --- /dev/null +++ b/crates/template_lib_types/src/crypto/signature.rs @@ -0,0 +1,138 @@ +// Copyright 2025 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use tari_template_abi::rust::{fmt, marker::PhantomData}; + +use crate::crypto::{RistrettoPublicKeyBytes, SchnorrSignatureBytes}; + +pub trait SignatureDomain { + fn domain() -> &'static [u8]; +} + +/// A signature domain that is the empty byte string. +/// +/// # Warning +/// This is not recommended use as it could lead to signature replay attacks across different contexts. +/// Instead, define a custom domain for your application using the `custom_signature_domain!` macro. +/// +/// # Example +/// ```rust,ignore +/// custom_signature_domain!(MyAppDomain, b"MyAppSignatureDomain"); +/// ``` +pub struct NoSignatureDomain; + +impl SignatureDomain for NoSignatureDomain { + fn domain() -> &'static [u8] { + b"" + } +} + +#[macro_export] +macro_rules! custom_signature_domain { + ($name:ident, $domain:expr) => { + pub struct $name; + + impl $crate::crypto::SignatureDomain for $name { + fn domain() -> &'static [u8] { + $domain + } + } + }; +} + +/// A signature with an associated signature domain. +/// +/// # Example +/// ```rust,ignore +/// // Define a custom signature domain +/// custom_signature_domain!(MyAppDomain, b"MyAppSignatureDomain"); +/// +/// // Create a signature type with the custom domain +/// let paylaod = SignaturePayload::RistrettoSchnorrBlake2b(schnorr_signature_bytes); +/// let signature = Signature::::new(paylaod); +/// // Verify the signature +/// let is_valid = signature.verify(&public_key, &message); +/// ``` +#[derive(Clone, serde::Serialize, serde::Deserialize)] +pub struct Signature { + payload: SignaturePayload, + #[serde(skip)] + _domain: PhantomData, +} + +impl> From for Signature { + fn from(value: T) -> Self { + Self::new(value) + } +} +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub enum SignaturePayload { + RistrettoSchnorrBlake2b(SchnorrSignatureBytes), +} + +impl SignaturePayload { + pub fn ristretto_schnorr_blake2b(&self) -> Option<&SchnorrSignatureBytes> { + match self { + SignaturePayload::RistrettoSchnorrBlake2b(sig) => Some(sig), + } + } +} + +impl From for SignaturePayload { + fn from(value: SchnorrSignatureBytes) -> Self { + SignaturePayload::RistrettoSchnorrBlake2b(value) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default, serde::Serialize, serde::Deserialize)] +pub enum PublicKey { + #[default] + Zero, + Ristretto25519(RistrettoPublicKeyBytes), +} + +impl PublicKey { + pub fn ristretto25519(&self) -> Option<&RistrettoPublicKeyBytes> { + match self { + Self::Zero => None, + Self::Ristretto25519(pk) => Some(pk), + } + } + + pub fn as_bytes(&self) -> &[u8] { + match self { + Self::Zero => &[], + Self::Ristretto25519(pk) => pk.as_bytes(), + } + } +} + +impl fmt::Display for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Zero => write!(f, "Zero"), + Self::Ristretto25519(pk) => write!(f, "Ristretto25519({})", pk), + } + } +} + +impl From for PublicKey { + fn from(value: RistrettoPublicKeyBytes) -> Self { + Self::Ristretto25519(value) + } +} + +impl Signature { + pub fn new>(payload: T) -> Self { + Self { + payload: payload.into(), + _domain: PhantomData, + } + } +} + +impl Signature { + pub fn payload(&self) -> &SignaturePayload { + &self.payload + } +} diff --git a/crates/template_lib_types/src/engine_args.rs b/crates/template_lib_types/src/engine_args.rs new file mode 100644 index 0000000000..a3122356e9 --- /dev/null +++ b/crates/template_lib_types/src/engine_args.rs @@ -0,0 +1,34 @@ +// Copyright 2025 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use serde::{Deserialize, Serialize}; + +use crate::crypto::{PublicKey, SignaturePayload}; + +// -------------------------------- Signature -------------------------------- // +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum SignatureAction { + Verify, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SignatureInvokeArg { + pub action: SignatureAction, + pub args: Vec>, +} + +#[derive(Debug, Clone, Serialize)] +pub struct SignatureVerifyArgRef<'a> { + pub public_key: &'a PublicKey, + pub domain: &'a [u8], + pub message: &'a [u8], + pub payload: &'a SignaturePayload, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct SignatureVerifyArg { + pub public_key: PublicKey, + pub domain: Vec, + pub message: Vec, + pub payload: SignaturePayload, +} diff --git a/crates/template_lib_types/src/lib.rs b/crates/template_lib_types/src/lib.rs index bcce4a356a..aa157ec25e 100644 --- a/crates/template_lib_types/src/lib.rs +++ b/crates/template_lib_types/src/lib.rs @@ -3,7 +3,9 @@ #[macro_use] mod amount; +#[macro_use] pub mod crypto; +pub mod engine_args; mod entity_id; mod error; mod hash; diff --git a/crates/template_test_tooling/src/template_test.rs b/crates/template_test_tooling/src/template_test.rs index 82b959c21b..32944a8a78 100644 --- a/crates/template_test_tooling/src/template_test.rs +++ b/crates/template_test_tooling/src/template_test.rs @@ -164,6 +164,7 @@ impl TemplateTest { per_byte_storage_cost: 1, per_event_cost: 1, per_log_cost: 1, + per_signature_verification_cost: 1, }, key_seed: 1, } diff --git a/crates/wallet/sdk/src/apis/stealth_crypto.rs b/crates/wallet/sdk/src/apis/stealth_crypto.rs index afad5f88ea..d0c8cbfe99 100644 --- a/crates/wallet/sdk/src/apis/stealth_crypto.rs +++ b/crates/wallet/sdk/src/apis/stealth_crypto.rs @@ -27,8 +27,11 @@ use tari_ootle_wallet_crypto::{ }; use tari_template_lib::{ models::{ConfidentialOutputStatement, EncryptedData, ResourceAddress, StealthTransferStatement}, - prelude::{crypto::CommitmentSignatureBytes, PedersenCommitmentBytes, RistrettoPublicKeyBytes}, - types::{crypto::UtxoTag, Amount}, + prelude::{PedersenCommitmentBytes, RistrettoPublicKeyBytes}, + types::{ + crypto::{CommitmentSignatureBytes, UtxoTag}, + Amount, + }, }; const LOG_TARGET: &str = "tari::ootle::wallet::sdk::stealth_crypto"; diff --git a/crates/wallet/sdk/src/models/utxo_update.rs b/crates/wallet/sdk/src/models/utxo_update.rs index 5e53aa4cb7..2030e1e84b 100644 --- a/crates/wallet/sdk/src/models/utxo_update.rs +++ b/crates/wallet/sdk/src/models/utxo_update.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use tari_bor::{Deserialize, Serialize}; use tari_engine_types::UtxoId; use tari_ootle_common_types::{shard::Shard, StateVersion}; -use tari_template_lib::prelude::{crypto::UtxoTag, RistrettoPublicKeyBytes}; +use tari_template_lib::types::crypto::{RistrettoPublicKeyBytes, UtxoTag}; #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export))] diff --git a/crates/wallet/storage_sqlite/src/models/stealth_output.rs b/crates/wallet/storage_sqlite/src/models/stealth_output.rs index 15288bac5b..585470463f 100644 --- a/crates/wallet/storage_sqlite/src/models/stealth_output.rs +++ b/crates/wallet/storage_sqlite/src/models/stealth_output.rs @@ -5,8 +5,10 @@ use diesel::dsl; use tari_ootle_wallet_sdk::{models::StealthOutputModel, storage::WalletStorageError}; use tari_template_lib::{ models::{ComponentAddress, EncryptedData}, - prelude::crypto::UtxoTag, - types::{amount, crypto::RistrettoPublicKeyBytes}, + types::{ + amount, + crypto::{RistrettoPublicKeyBytes, UtxoTag}, + }, }; use time::PrimitiveDateTime; diff --git a/crates/wallet/storage_sqlite/src/writer.rs b/crates/wallet/storage_sqlite/src/writer.rs index ab088414cb..3d25db548d 100644 --- a/crates/wallet/storage_sqlite/src/writer.rs +++ b/crates/wallet/storage_sqlite/src/writer.rs @@ -42,9 +42,12 @@ use tari_ootle_wallet_sdk::{ storage::{WalletStorageError, WalletStoreReader, WalletStoreWriter}, }; use tari_template_lib::{ - models::{EncryptedData, NonFungibleId, ResourceAddress, VaultId}, - prelude::{crypto::UtxoTag, ComponentAddress, PedersenCommitmentBytes, RistrettoPublicKeyBytes}, - types::{Amount, TemplateAddress}, + models::{ComponentAddress, EncryptedData, NonFungibleId, ResourceAddress, VaultId}, + types::{ + crypto::{PedersenCommitmentBytes, RistrettoPublicKeyBytes, UtxoTag}, + Amount, + TemplateAddress, + }, }; use tari_transaction::{Transaction, TransactionId}; use tari_utilities::hex::Hex; diff --git a/integration_tests/tests/steps/wallet_daemon.rs b/integration_tests/tests/steps/wallet_daemon.rs index 6d68615fb5..2115a07dfa 100644 --- a/integration_tests/tests/steps/wallet_daemon.rs +++ b/integration_tests/tests/steps/wallet_daemon.rs @@ -8,7 +8,10 @@ use cucumber::{then, when}; use integration_tests::{util::cucumber_log, wallet_daemon_cli, TariWorld}; use tari_engine_types::commit_result::FinalizeResult; use tari_ootle_wallet_sdk::apis::key_manager::KeyBranch; -use tari_template_lib::prelude::{crypto::CommitmentSignatureBytes, Amount, PedersenCommitmentBytes, Scalar32Bytes}; +use tari_template_lib::types::{ + crypto::{CommitmentSignatureBytes, PedersenCommitmentBytes, Scalar32Bytes}, + Amount, +}; use tari_transaction_components::transaction_components::{memo_field::TxType, MemoField}; use tari_wallet_daemon_client::{ types::{ClaimBurnProof, ExtClaimBurnProof},