diff --git a/applications/tari_app_grpc/proto/types.proto b/applications/tari_app_grpc/proto/types.proto index e1e95beec2..420b3a4a42 100644 --- a/applications/tari_app_grpc/proto/types.proto +++ b/applications/tari_app_grpc/proto/types.proto @@ -179,6 +179,8 @@ message TransactionInput { uint32 version = 11; // The encrypted value bytes encrypted_value = 12; + // The minimum value of the commitment that is proven by the range proof (in MicroTari) + uint64 minimum_value_promise = 13; } // Output for a transaction, defining the new ownership of coins that are being transferred. The commitment is a @@ -206,6 +208,8 @@ message TransactionOutput { uint32 version = 9; // The encrypted value bytes encrypted_value = 10; + // The minimum value of the commitment that is proven by the range proof (in MicroTari) + uint64 minimum_value_promise = 11; } // Options for UTXOs @@ -469,6 +473,8 @@ message UnblindedOutput { bytes covenant = 11; // Encrypted Value bytes encrypted_value = 12; + // The minimum value of the commitment that is proven by the range proof (in MicroTari) + uint64 minimum_value_promise = 13; } // ----------------------------- Network Types ----------------------------- // diff --git a/applications/tari_app_grpc/src/conversions/transaction_input.rs b/applications/tari_app_grpc/src/conversions/transaction_input.rs index b4922ec25a..af300a2c78 100644 --- a/applications/tari_app_grpc/src/conversions/transaction_input.rs +++ b/applications/tari_app_grpc/src/conversions/transaction_input.rs @@ -63,6 +63,8 @@ impl TryFrom for TransactionInput { PublicKey::from_bytes(input.sender_offset_public_key.as_bytes()).map_err(|err| format!("{:?}", err))?; let covenant = Covenant::from_bytes(&input.covenant).map_err(|err| err.to_string())?; let encrypted_value = EncryptedValue::from_bytes(&input.encrypted_value).map_err(|err| err.to_string())?; + let minimum_value_promise = input.minimum_value_promise.into(); + Ok(TransactionInput::new_with_output_data( TransactionInputVersion::try_from( u8::try_from(input.version).map_err(|_| "Invalid version: overflowed u8")?, @@ -75,6 +77,7 @@ impl TryFrom for TransactionInput { sender_offset_public_key, covenant, encrypted_value, + minimum_value_promise, )) } } @@ -131,6 +134,10 @@ impl TryFrom for grpc::TransactionInput { .encrypted_value() .map_err(|_| "Non-compact Transaction input should contain encrypted value".to_string())? .to_vec(), + minimum_value_promise: input + .minimum_value_promise() + .map_err(|_| "Non-compact Transaction input should contain the minimum value promise".to_string())? + .as_u64(), }) } } diff --git a/applications/tari_app_grpc/src/conversions/transaction_output.rs b/applications/tari_app_grpc/src/conversions/transaction_output.rs index b854f77294..e57f22c127 100644 --- a/applications/tari_app_grpc/src/conversions/transaction_output.rs +++ b/applications/tari_app_grpc/src/conversions/transaction_output.rs @@ -25,7 +25,10 @@ use std::convert::{TryFrom, TryInto}; use tari_common_types::types::{BulletRangeProof, Commitment, PublicKey}; use tari_core::{ covenants::Covenant, - transactions::transaction_components::{EncryptedValue, TransactionOutput, TransactionOutputVersion}, + transactions::{ + tari_amount::MicroTari, + transaction_components::{EncryptedValue, TransactionOutput, TransactionOutputVersion}, + }, }; use tari_script::TariScript; use tari_utilities::{ByteArray, Hashable}; @@ -56,6 +59,7 @@ impl TryFrom for TransactionOutput { .map_err(|_| "Metadata signature could not be converted".to_string())?; let covenant = Covenant::from_bytes(&output.covenant).map_err(|err| err.to_string())?; let encrypted_value = EncryptedValue::from_bytes(&output.encrypted_value).map_err(|err| err.to_string())?; + let minimum_value_promise = MicroTari::from(output.minimum_value_promise); Ok(Self::new( TransactionOutputVersion::try_from( u8::try_from(output.version).map_err(|_| "Invalid version: overflowed u8")?, @@ -68,6 +72,7 @@ impl TryFrom for TransactionOutput { metadata_signature, covenant, encrypted_value, + minimum_value_promise, )) } } @@ -90,6 +95,7 @@ impl From for grpc::TransactionOutput { covenant: output.covenant.to_bytes(), version: output.version as u32, encrypted_value: output.encrypted_value.to_vec(), + minimum_value_promise: output.minimum_value_promise.into(), } } } diff --git a/applications/tari_app_grpc/src/conversions/unblinded_output.rs b/applications/tari_app_grpc/src/conversions/unblinded_output.rs index 3e3f3b9e92..d49153c35b 100644 --- a/applications/tari_app_grpc/src/conversions/unblinded_output.rs +++ b/applications/tari_app_grpc/src/conversions/unblinded_output.rs @@ -53,6 +53,7 @@ impl From for grpc::UnblindedOutput { script_lock_height: output.script_lock_height, covenant: output.covenant.to_bytes(), encrypted_value: output.encrypted_value.to_vec(), + minimum_value_promise: output.minimum_value_promise.into(), } } } @@ -90,6 +91,8 @@ impl TryFrom for UnblindedOutput { let encrypted_value = EncryptedValue::from_bytes(&output.encrypted_value).map_err(|err| err.to_string())?; + let minimum_value_promise = MicroTari::from(output.minimum_value_promise); + Ok(Self::new( TransactionOutputVersion::try_from(0u8)?, MicroTari::from(output.value), @@ -103,6 +106,7 @@ impl TryFrom for UnblindedOutput { output.script_lock_height, covenant, encrypted_value, + minimum_value_promise, )) } } diff --git a/applications/test_faucet/src/main.rs b/applications/test_faucet/src/main.rs index 6726171d5e..3ed646c0de 100644 --- a/applications/test_faucet/src/main.rs +++ b/applications/test_faucet/src/main.rs @@ -201,6 +201,7 @@ fn create_utxo( panic!("Range proof does not verify"); }; let encrypted_value = EncryptedValue::default(); + let minimum_value_promise = MicroTari::zero(); let metadata_sig = TransactionOutput::create_final_metadata_signature( TransactionOutputVersion::get_current_version(), value, @@ -210,6 +211,7 @@ fn create_utxo( &offset_keys.k, &covenant, &encrypted_value, + minimum_value_promise, ) .unwrap(); @@ -222,6 +224,7 @@ fn create_utxo( metadata_sig, covenant, encrypted_value, + minimum_value_promise, ); utxo.verify_range_proof(&CryptoFactories::default().range_proof) .unwrap(); diff --git a/base_layer/core/src/blocks/genesis_block.rs b/base_layer/core/src/blocks/genesis_block.rs index 34fda4a67c..98066194c7 100644 --- a/base_layer/core/src/blocks/genesis_block.rs +++ b/base_layer/core/src/blocks/genesis_block.rs @@ -117,6 +117,8 @@ fn get_igor_genesis_block_raw() -> Block { Default::default(), Covenant::default(), EncryptedValue::default(), + // Genesis blocks don't need to prove a minimum value + MicroTari::zero(), )], vec![TransactionKernel::new_current_version( KernelFeatures::COINBASE_KERNEL, @@ -262,6 +264,8 @@ fn get_dibbler_genesis_block_raw() -> Block { // Covenant Covenant::default(), EncryptedValue::default(), + // Genesis blocks don't need to prove a minimum value + MicroTari::zero(), ); let kernel = TransactionKernel::new( TransactionKernelVersion::V0, diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index 12251def0d..cc8cba0643 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -1522,6 +1522,7 @@ fn fetch_block(db: &T, height: u64) -> Result for TransactionInput { sender_offset_public_key, Covenant::from_bytes(&input.covenant).map_err(|err| err.to_string())?, EncryptedValue::from_bytes(&input.encrypted_value).map_err(|err| err.to_string())?, + input.minimum_value_promise.into(), )) } else { if input.output_hash.is_empty() { @@ -227,6 +228,10 @@ impl TryFrom for proto::types::TransactionInput { .encrypted_value() .map_err(|_| "Non-compact Transaction input should contain encrypted value".to_string())? .to_vec(), + minimum_value_promise: input + .minimum_value_promise() + .map_err(|_| "Non-compact Transaction input should contain the minimum value promise".to_string())? + .as_u64(), }) } } @@ -264,6 +269,8 @@ impl TryFrom for TransactionOutput { let encrypted_value = EncryptedValue::from_bytes(&output.encrypted_value).map_err(|err| err.to_string())?; + let minimum_value_promise = MicroTari::zero(); + Ok(Self::new( TransactionOutputVersion::try_from( u8::try_from(output.version).map_err(|_| "Invalid version: overflowed u8")?, @@ -276,6 +283,7 @@ impl TryFrom for TransactionOutput { metadata_signature, covenant, encrypted_value, + minimum_value_promise, )) } } @@ -292,6 +300,7 @@ impl From for proto::types::TransactionOutput { covenant: output.covenant.to_bytes(), version: output.version as u32, encrypted_value: output.encrypted_value.to_vec(), + minimum_value_promise: output.minimum_value_promise.into(), } } } diff --git a/base_layer/core/src/transactions/coinbase_builder.rs b/base_layer/core/src/transactions/coinbase_builder.rs index abfa8a334d..6363831374 100644 --- a/base_layer/core/src/transactions/coinbase_builder.rs +++ b/base_layer/core/src/transactions/coinbase_builder.rs @@ -215,6 +215,8 @@ impl CoinbaseBuilder { .map_err(|_| CoinbaseBuildError::ValueEncryptionFailed)? .unwrap_or_default(); + let minimum_value_promise = MicroTari::zero(); + let metadata_sig = TransactionOutput::create_final_metadata_signature( TransactionOutputVersion::get_current_version(), total_reward, @@ -224,6 +226,7 @@ impl CoinbaseBuilder { &sender_offset_private_key, &covenant, &encrypted_value, + minimum_value_promise, ) .map_err(|e| CoinbaseBuildError::BuildError(e.to_string()))?; @@ -239,6 +242,7 @@ impl CoinbaseBuilder { 0, covenant, encrypted_value, + minimum_value_promise, ); let output = if let Some(rewind_data) = self.rewind_data.as_ref() { unblinded_output diff --git a/base_layer/core/src/transactions/test_helpers.rs b/base_layer/core/src/transactions/test_helpers.rs index 32ca003f49..5a5c3fe699 100644 --- a/base_layer/core/src/transactions/test_helpers.rs +++ b/base_layer/core/src/transactions/test_helpers.rs @@ -103,6 +103,7 @@ pub struct UtxoTestParams { pub input_data: Option, pub covenant: Covenant, pub output_version: Option, + pub minimum_value_promise: MicroTari, } impl UtxoTestParams { @@ -123,6 +124,7 @@ impl Default for UtxoTestParams { input_data: None, covenant: Covenant::default(), output_version: None, + minimum_value_promise: MicroTari::zero(), } } } @@ -177,6 +179,7 @@ impl TestParams { } else { EncryptedValue::default() }; + let metadata_signature = TransactionOutput::create_final_metadata_signature( TransactionOutputVersion::get_current_version(), params.value, @@ -186,6 +189,7 @@ impl TestParams { &self.sender_offset_private_key, ¶ms.covenant, &encrypted_value, + params.minimum_value_promise, ) .unwrap(); @@ -206,6 +210,7 @@ impl TestParams { 0, params.covenant, encrypted_value, + params.minimum_value_promise, ) } @@ -665,6 +670,7 @@ pub fn create_stx_protocol(schema: TransactionSchema) -> (SenderTransactionProto input_data: schema.input_data.clone(), covenant: schema.covenant.clone(), output_version: schema.output_version, + minimum_value_promise: MicroTari::zero(), }); outputs.push(utxo.clone()); stx_builder @@ -682,6 +688,7 @@ pub fn create_stx_protocol(schema: TransactionSchema) -> (SenderTransactionProto &test_params.sender_offset_private_key, &utxo.covenant, &utxo.encrypted_value, + utxo.minimum_value_promise, ) .unwrap(); utxo.sender_offset_public_key = test_params.sender_offset_public_key; @@ -702,6 +709,8 @@ pub fn create_stx_protocol(schema: TransactionSchema) -> (SenderTransactionProto let encrypted_value = EncryptedValue::default(); + let minimum_value_promise = MicroTari::zero(); + let change_metadata_sig = TransactionOutput::create_final_metadata_signature( output_version, change, @@ -711,6 +720,7 @@ pub fn create_stx_protocol(schema: TransactionSchema) -> (SenderTransactionProto &test_params_change_and_txn.sender_offset_private_key, &covenant, &encrypted_value, + minimum_value_promise, ) .unwrap(); @@ -728,6 +738,7 @@ pub fn create_stx_protocol(schema: TransactionSchema) -> (SenderTransactionProto 0, covenant, encrypted_value, + minimum_value_promise, ); outputs.push(change_output); (stx_protocol, outputs) @@ -763,6 +774,7 @@ pub fn create_utxo( features: &OutputFeatures, script: &TariScript, covenant: &Covenant, + minimum_value_promise: MicroTari, ) -> (TransactionOutput, PrivateKey, PrivateKey) { let keys = generate_keys(); let offset_keys = generate_keys(); @@ -778,6 +790,7 @@ pub fn create_utxo( &offset_keys.k, covenant, &EncryptedValue::default(), + minimum_value_promise, ) .unwrap(); @@ -790,6 +803,7 @@ pub fn create_utxo( metadata_sig, covenant.clone(), EncryptedValue::default(), + minimum_value_promise, ); utxo.verify_range_proof(&CryptoFactories::default().range_proof) .unwrap(); diff --git a/base_layer/core/src/transactions/transaction_components/mod.rs b/base_layer/core/src/transactions/transaction_components/mod.rs index 2dc0e3a4fd..0c35b48612 100644 --- a/base_layer/core/src/transactions/transaction_components/mod.rs +++ b/base_layer/core/src/transactions/transaction_components/mod.rs @@ -85,6 +85,7 @@ pub const MAX_TRANSACTION_RECIPIENTS: usize = 15; //---------------------------------------- Crate functions ----------------------------------------------------// +use super::tari_amount::MicroTari; use crate::{consensus::ConsensusHashWriter, covenants::Covenant}; /// Implement the canonical hashing function for TransactionOutput and UnblindedOutput for use in @@ -100,15 +101,18 @@ pub(super) fn hash_output( script: &TariScript, covenant: &Covenant, encrypted_value: &EncryptedValue, + minimum_value_promise: MicroTari, ) -> [u8; 32] { + let common_hash = ConsensusHashWriter::default() + .chain(&version) + .chain(features) + .chain(commitment) + .chain(script) + .chain(covenant) + .chain(encrypted_value); + match version { - TransactionOutputVersion::V0 | TransactionOutputVersion::V1 => ConsensusHashWriter::default() - .chain(&version) - .chain(features) - .chain(commitment) - .chain(script) - .chain(covenant) - .chain(encrypted_value) - .finalize(), + TransactionOutputVersion::V0 | TransactionOutputVersion::V1 => common_hash.finalize(), + TransactionOutputVersion::V2 => common_hash.chain(&minimum_value_promise).finalize(), } } diff --git a/base_layer/core/src/transactions/transaction_components/test.rs b/base_layer/core/src/transactions/transaction_components/test.rs index 5d5b3a7831..b2a88d09bb 100644 --- a/base_layer/core/src/transactions/transaction_components/test.rs +++ b/base_layer/core/src/transactions/transaction_components/test.rs @@ -278,6 +278,7 @@ fn check_timelocks() { offset_pub_key, Covenant::default(), EncryptedValue::default(), + MicroTari::zero(), ); let mut kernel = test_helpers::create_test_kernel(0.into(), 0); diff --git a/base_layer/core/src/transactions/transaction_components/transaction_input.rs b/base_layer/core/src/transactions/transaction_components/transaction_input.rs index f90e8fde1e..b2a93cfa3e 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_input.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_input.rs @@ -43,6 +43,7 @@ use crate::{ consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusHashWriter, MaxSizeBytes}, covenants::Covenant, transactions::{ + tari_amount::MicroTari, transaction_components, transaction_components::{ transaction_output::TransactionOutput, @@ -117,6 +118,7 @@ impl TransactionInput { sender_offset_public_key: PublicKey, covenant: Covenant, encrypted_value: EncryptedValue, + minimum_value_promise: MicroTari, ) -> TransactionInput { TransactionInput::new( version, @@ -128,6 +130,7 @@ impl TransactionInput { covenant, version: TransactionOutputVersion::get_current_version(), encrypted_value, + minimum_value_promise, }, input_data, script_signature, @@ -144,6 +147,7 @@ impl TransactionInput { sender_offset_public_key: PublicKey, covenant: Covenant, encrypted_value: EncryptedValue, + minimum_value_promise: MicroTari, ) { self.spent_output = SpentOutput::OutputData { version, @@ -153,6 +157,7 @@ impl TransactionInput { sender_offset_public_key, covenant, encrypted_value, + minimum_value_promise, }; } @@ -229,6 +234,16 @@ impl TransactionInput { } } + pub fn minimum_value_promise(&self) -> Result<&MicroTari, TransactionError> { + match self.spent_output { + SpentOutput::OutputHash(_) => Err(TransactionError::MissingTransactionInputData), + SpentOutput::OutputData { + ref minimum_value_promise, + .. + } => Ok(minimum_value_promise), + } + } + /// Checks if the given un-blinded input instance corresponds to this blinded Transaction Input pub fn opened_by(&self, input: &UnblindedOutput, factory: &CommitmentFactory) -> Result { match self.spent_output { @@ -329,9 +344,18 @@ impl TransactionInput { features, covenant, encrypted_value, + minimum_value_promise, .. - } => transaction_components::hash_output(*version, features, commitment, script, covenant, encrypted_value) - .to_vec(), + } => transaction_components::hash_output( + *version, + features, + commitment, + script, + covenant, + encrypted_value, + *minimum_value_promise, + ) + .to_vec(), } } @@ -351,6 +375,7 @@ impl TransactionInput { ref sender_offset_public_key, ref covenant, ref encrypted_value, + ref minimum_value_promise, } => { // TODO: Change this hash to what is in RFC-0121/Consensus Encoding #testnet-reset let writer = ConsensusHashWriter::default() @@ -362,7 +387,8 @@ impl TransactionInput { .chain(&self.script_signature) .chain(&self.input_data) .chain(covenant) - .chain(encrypted_value); + .chain(encrypted_value) + .chain(minimum_value_promise); Ok(writer.finalize().to_vec()) }, @@ -483,6 +509,7 @@ pub enum SpentOutput { /// The transaction covenant covenant: Covenant, encrypted_value: EncryptedValue, + minimum_value_promise: MicroTari, }, } @@ -508,6 +535,7 @@ impl ConsensusEncoding for SpentOutput { sender_offset_public_key, covenant, encrypted_value, + minimum_value_promise, } => { version.consensus_encode(writer)?; features.consensus_encode(writer)?; @@ -516,6 +544,7 @@ impl ConsensusEncoding for SpentOutput { sender_offset_public_key.consensus_encode(writer)?; covenant.consensus_encode(writer)?; encrypted_value.consensus_encode(writer)?; + minimum_value_promise.consensus_encode(writer)?; }, }; Ok(()) @@ -540,6 +569,7 @@ impl ConsensusDecoding for SpentOutput { let sender_offset_public_key = PublicKey::consensus_decode(reader)?; let covenant = Covenant::consensus_decode(reader)?; let encrypted_value = EncryptedValue::consensus_decode(reader)?; + let minimum_value_promise = MicroTari::consensus_decode(reader)?; Ok(SpentOutput::OutputData { version, features, @@ -548,6 +578,7 @@ impl ConsensusDecoding for SpentOutput { sender_offset_public_key, covenant, encrypted_value, + minimum_value_promise, }) }, _ => Err(io::Error::new(ErrorKind::InvalidInput, "Invalid SpentOutput type")), diff --git a/base_layer/core/src/transactions/transaction_components/transaction_output.rs b/base_layer/core/src/transactions/transaction_components/transaction_output.rs index 325f9e9ea3..3ec427e130 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_output.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_output.rs @@ -50,7 +50,6 @@ use tari_crypto::{ errors::RangeProofError, extended_range_proof::{ExtendedRangeProofService, Statement}, keys::{PublicKey as PublicKeyTrait, SecretKey}, - range_proof::RangeProofService as RangeProofServiceTrait, ristretto::bulletproofs_plus::RistrettoAggregatedPublicStatement, tari_utilities::{hex::Hex, ByteArray, Hashable}, }; @@ -92,6 +91,9 @@ pub struct TransactionOutput { pub covenant: Covenant, /// Encrypted value. pub encrypted_value: EncryptedValue, + /// The minimum value of the commitment that is proven by the range proof + #[serde(default)] + pub minimum_value_promise: MicroTari, } /// An output for a transaction, includes a range proof and Tari script metadata @@ -108,6 +110,7 @@ impl TransactionOutput { metadata_signature: ComSignature, covenant: Covenant, encrypted_value: EncryptedValue, + minimum_value_promise: MicroTari, ) -> TransactionOutput { TransactionOutput { version, @@ -119,6 +122,7 @@ impl TransactionOutput { metadata_signature, covenant, encrypted_value, + minimum_value_promise, } } @@ -131,6 +135,7 @@ impl TransactionOutput { metadata_signature: ComSignature, covenant: Covenant, encrypted_value: EncryptedValue, + minimum_value_promise: MicroTari, ) -> TransactionOutput { TransactionOutput::new( TransactionOutputVersion::get_current_version(), @@ -142,6 +147,7 @@ impl TransactionOutput { metadata_signature, covenant, encrypted_value, + minimum_value_promise, ) } @@ -157,12 +163,18 @@ impl TransactionOutput { /// Verify that range proof is valid pub fn verify_range_proof(&self, prover: &RangeProofService) -> Result<(), TransactionError> { - if prover.verify(&self.proof.0, &self.commitment) { - Ok(()) - } else { - Err(TransactionError::ValidationError( - "Recipient output range proof failed to verify".to_string(), - )) + let statement = RistrettoAggregatedPublicStatement { + statements: vec![Statement { + commitment: self.commitment.clone(), + minimum_value_promise: self.minimum_value_promise.as_u64(), + }], + }; + match prover.verify_batch(vec![&self.proof.0], vec![&statement]) { + Ok(_) => Ok(()), + Err(e) => Err(TransactionError::ValidationError(format!( + "Recipient output range proof failed to verify ({})", + e + ))), } } @@ -177,6 +189,7 @@ impl TransactionOutput { &self.commitment, &self.covenant, &self.encrypted_value, + self.minimum_value_promise, ); if !self.metadata_signature.verify_challenge( &(&self.commitment + &self.sender_offset_public_key), @@ -236,6 +249,7 @@ impl TransactionOutput { &self.commitment, &self.covenant, &self.encrypted_value, + self.minimum_value_promise, ) } @@ -249,17 +263,20 @@ impl TransactionOutput { commitment: &Commitment, covenant: &Covenant, encrypted_value: &EncryptedValue, + minimum_value_promise: MicroTari, ) -> Challenge { + let common = ConsensusHashWriter::default() + .chain(public_commitment_nonce) + .chain(script) + .chain(features) + .chain(sender_offset_public_key) + .chain(commitment) + .chain(covenant) + .chain(encrypted_value); + match version { - TransactionOutputVersion::V0 | TransactionOutputVersion::V1 => ConsensusHashWriter::default() - .chain(public_commitment_nonce) - .chain(script) - .chain(features) - .chain(sender_offset_public_key) - .chain(commitment) - .chain(covenant) - .chain(encrypted_value) - .into_digest(), + TransactionOutputVersion::V0 | TransactionOutputVersion::V1 => common.into_digest(), + TransactionOutputVersion::V2 => common.chain(&minimum_value_promise).into_digest(), } } @@ -276,6 +293,7 @@ impl TransactionOutput { sender_offset_private_key: Option<&PrivateKey>, covenant: &Covenant, encrypted_value: &EncryptedValue, + minimum_value_promise: MicroTari, ) -> Result { let nonce_a = PrivateKey::random(&mut OsRng); let nonce_b = PrivateKey::random(&mut OsRng); @@ -295,6 +313,7 @@ impl TransactionOutput { &commitment, covenant, encrypted_value, + minimum_value_promise, ); let secret_x = match sender_offset_private_key { None => spending_key.clone(), @@ -321,6 +340,7 @@ impl TransactionOutput { partial_commitment_nonce: &PublicKey, covenant: &Covenant, encrypted_value: &EncryptedValue, + minimum_value_promise: MicroTari, ) -> Result { TransactionOutput::create_metadata_signature( version, @@ -333,6 +353,7 @@ impl TransactionOutput { None, covenant, encrypted_value, + minimum_value_promise, ) } @@ -346,6 +367,7 @@ impl TransactionOutput { sender_offset_private_key: &PrivateKey, covenant: &Covenant, encrypted_value: &EncryptedValue, + minimum_value_promise: MicroTari, ) -> Result { let sender_offset_public_key = PublicKey::from_secret_key(sender_offset_private_key); TransactionOutput::create_metadata_signature( @@ -359,6 +381,7 @@ impl TransactionOutput { Some(sender_offset_private_key), covenant, encrypted_value, + minimum_value_promise, ) } @@ -387,6 +410,7 @@ impl Hashable for TransactionOutput { &self.script, &self.covenant, &self.encrypted_value, + self.minimum_value_promise, ) .to_vec() } @@ -403,6 +427,7 @@ impl Default for TransactionOutput { ComSignature::default(), Covenant::default(), EncryptedValue::default(), + MicroTari::zero(), ) } } @@ -452,6 +477,7 @@ impl ConsensusEncoding for TransactionOutput { self.sender_offset_public_key.consensus_encode(writer)?; self.metadata_signature.consensus_encode(writer)?; self.covenant.consensus_encode(writer)?; + self.minimum_value_promise.consensus_encode(writer)?; Ok(()) } } @@ -467,6 +493,7 @@ impl ConsensusDecoding for TransactionOutput { let metadata_signature = ComSignature::consensus_decode(reader)?; let covenant = Covenant::consensus_decode(reader)?; let encrypted_value = EncryptedValue::consensus_decode(reader)?; + let minimum_value_promise = MicroTari::consensus_decode(reader)?; let output = TransactionOutput::new( version, features, @@ -477,6 +504,7 @@ impl ConsensusDecoding for TransactionOutput { metadata_signature, covenant, encrypted_value, + minimum_value_promise, ); Ok(output) } @@ -503,7 +531,7 @@ pub fn batch_verify_range_proofs( statements.push(RistrettoAggregatedPublicStatement { statements: vec![Statement { commitment: output.commitment.clone(), - minimum_value_promise: 0, + minimum_value_promise: output.minimum_value_promise.into(), }], }); proofs.push(output.proof.to_vec().clone()); @@ -550,7 +578,13 @@ fn power_of_two_chunk_sizes(len: usize, max_power: u8) -> Vec { #[cfg(test)] mod test { - use crate::transactions::transaction_components::transaction_output::power_of_two_chunk_sizes; + use super::{batch_verify_range_proofs, TransactionOutput}; + use crate::transactions::{ + tari_amount::MicroTari, + test_helpers::{TestParams, UtxoTestParams}, + transaction_components::transaction_output::power_of_two_chunk_sizes, + CryptoFactories, + }; #[test] fn it_creates_power_of_two_chunks() { @@ -575,4 +609,89 @@ mod test { 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 64, 2, 1 ]); } + + #[test] + fn it_builds_correctly_from_unblinded_output() { + let factories = CryptoFactories::default(); + let test_params = TestParams::new(); + + let value = MicroTari(10); + let minimum_value_promise = MicroTari(10); + let tx_output = create_valid_output(&test_params, &factories, value, minimum_value_promise); + + assert!(tx_output.verify_range_proof(&factories.range_proof).is_ok()); + assert!(tx_output.verify_metadata_signature().is_ok()); + assert!(tx_output + .verify_mask(&factories.range_proof, &test_params.spend_key, value.into()) + .is_ok()); + } + + #[test] + fn it_does_not_verify_incorrect_minimum_value() { + let factories = CryptoFactories::default(); + let test_params = TestParams::new(); + + let value = MicroTari(10); + let minimum_value_promise = MicroTari(11); + let tx_output = create_invalid_output(&test_params, &factories, value, minimum_value_promise); + + assert!(tx_output.verify_range_proof(&factories.range_proof).is_err()); + } + + #[test] + fn it_does_batch_verify_correct_minimum_values() { + let factories = CryptoFactories::default(); + let test_params = TestParams::new(); + + let outputs = [ + &create_valid_output(&test_params, &factories, MicroTari(10), MicroTari::zero()), + &create_valid_output(&test_params, &factories, MicroTari(10), MicroTari(5)), + &create_valid_output(&test_params, &factories, MicroTari(10), MicroTari(10)), + ]; + + assert!(batch_verify_range_proofs(&factories.range_proof, &outputs,).is_ok()); + } + + #[test] + fn it_does_not_batch_verify_incorrect_minimum_values() { + let factories = CryptoFactories::default(); + let test_params = TestParams::new(); + + let outputs = [ + &create_valid_output(&test_params, &factories, MicroTari(10), MicroTari(10)), + &create_invalid_output(&test_params, &factories, MicroTari(10), MicroTari(11)), + ]; + + assert!(batch_verify_range_proofs(&factories.range_proof, &outputs,).is_err()); + } + + fn create_valid_output( + test_params: &TestParams, + factories: &CryptoFactories, + value: MicroTari, + minimum_value_promise: MicroTari, + ) -> TransactionOutput { + let utxo = test_params.create_unblinded_output(UtxoTestParams { + value, + minimum_value_promise, + ..Default::default() + }); + utxo.as_transaction_output(factories).unwrap() + } + + fn create_invalid_output( + test_params: &TestParams, + factories: &CryptoFactories, + value: MicroTari, + minimum_value_promise: MicroTari, + ) -> TransactionOutput { + // we need first to create a valid minimum value, regardless of the minimum_value_promise + // because this test function shoud allow creating an invalid proof for later testing + let mut output = create_valid_output(test_params, factories, value, MicroTari::zero()); + + // Now we can updated the minimum value, even to an invalid value + output.minimum_value_promise = minimum_value_promise; + + output + } } diff --git a/base_layer/core/src/transactions/transaction_components/transaction_output_version.rs b/base_layer/core/src/transactions/transaction_components/transaction_output_version.rs index f62c77e9c0..e83c188ea0 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_output_version.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_output_version.rs @@ -39,6 +39,7 @@ pub enum TransactionOutputVersion { V0 = 0, /// Currently only used in tests, this can be used as the next version V1 = 1, + V2 = 2, } impl TransactionOutputVersion { @@ -58,6 +59,7 @@ impl TryFrom for TransactionOutputVersion { match value { 0 => Ok(TransactionOutputVersion::V0), 1 => Ok(TransactionOutputVersion::V1), + 2 => Ok(TransactionOutputVersion::V2), _ => Err("Unknown version!".to_string()), } } @@ -97,7 +99,8 @@ mod test { fn test_try_from() { assert_eq!(TransactionOutputVersion::try_from(0), Ok(TransactionOutputVersion::V0)); assert_eq!(TransactionOutputVersion::try_from(1), Ok(TransactionOutputVersion::V1)); - assert!(TransactionOutputVersion::try_from(2).is_err()); + assert_eq!(TransactionOutputVersion::try_from(2), Ok(TransactionOutputVersion::V2)); + assert!(TransactionOutputVersion::try_from(3).is_err()); } #[test] diff --git a/base_layer/core/src/transactions/transaction_components/unblinded_output.rs b/base_layer/core/src/transactions/transaction_components/unblinded_output.rs index 0287de198b..5dd7f38907 100644 --- a/base_layer/core/src/transactions/transaction_components/unblinded_output.rs +++ b/base_layer/core/src/transactions/transaction_components/unblinded_output.rs @@ -41,11 +41,12 @@ use tari_common_types::types::{ RangeProof, }; use tari_crypto::{ - commitment::HomomorphicCommitmentFactory, + commitment::{ExtensionDegree, HomomorphicCommitmentFactory}, errors::RangeProofError, extended_range_proof::ExtendedRangeProofService, keys::{PublicKey as PublicKeyTrait, SecretKey}, range_proof::RangeProofService, + ristretto::bulletproofs_plus::{RistrettoExtendedMask, RistrettoExtendedWitness}, tari_utilities::ByteArray, }; use tari_script::{ExecutionStack, TariScript}; @@ -88,6 +89,7 @@ pub struct UnblindedOutput { pub metadata_signature: ComSignature, pub script_lock_height: u64, pub encrypted_value: EncryptedValue, + pub minimum_value_promise: MicroTari, } impl UnblindedOutput { @@ -106,6 +108,7 @@ impl UnblindedOutput { script_lock_height: u64, covenant: Covenant, encrypted_value: EncryptedValue, + minimum_value_promise: MicroTari, ) -> Self { Self { version, @@ -120,6 +123,7 @@ impl UnblindedOutput { script_lock_height, covenant, encrypted_value, + minimum_value_promise, } } @@ -135,6 +139,7 @@ impl UnblindedOutput { script_lock_height: u64, covenant: Covenant, encrypted_value: EncryptedValue, + minimum_value_promise: MicroTari, ) -> Self { Self::new( TransactionOutputVersion::get_current_version(), @@ -149,6 +154,7 @@ impl UnblindedOutput { script_lock_height, covenant, encrypted_value, + minimum_value_promise, ) } @@ -186,6 +192,7 @@ impl UnblindedOutput { covenant: self.covenant.clone(), version: self.version, encrypted_value: self.encrypted_value.clone(), + minimum_value_promise: self.minimum_value_promise, }, self.input_data.clone(), script_signature, @@ -216,30 +223,69 @@ impl UnblindedOutput { } let commitment = factories.commitment.commit(&self.spending_key, &self.value.into()); + let range_proof = self.construct_range_proof(factories, None)?; + let output = TransactionOutput::new( self.version, self.features.clone(), commitment, - RangeProof::from_bytes( - &factories - .range_proof - .construct_proof(&self.spending_key, self.value.into())?, - ) - .map_err(|_| { - TransactionError::RangeProofError(RangeProofError::ProofConstructionError( - "Creating transaction output".to_string(), - )) - })?, + range_proof, self.script.clone(), self.sender_offset_public_key.clone(), self.metadata_signature.clone(), self.covenant.clone(), self.encrypted_value.clone(), + self.minimum_value_promise, ); Ok(output) } + fn construct_range_proof( + &self, + factories: &CryptoFactories, + seed_nonce: Option, + ) -> Result { + let proof_bytes_result = if self.minimum_value_promise.as_u64() == 0 { + match seed_nonce { + Some(nonce) => factories.range_proof.construct_proof_with_recovery_seed_nonce( + &self.spending_key, + self.value.into(), + &nonce, + ), + None => factories + .range_proof + .construct_proof(&self.spending_key, self.value.into()), + } + } else { + let extended_mask = + RistrettoExtendedMask::assign(ExtensionDegree::DefaultPedersen, vec![self.spending_key.clone()])?; + + let extended_witness = RistrettoExtendedWitness { + mask: extended_mask, + value: self.value.into(), + minimum_value_promise: self.minimum_value_promise.as_u64(), + }; + + factories + .range_proof + .construct_extended_proof(vec![extended_witness], seed_nonce) + }; + + let proof_bytes = proof_bytes_result.map_err(|err| { + TransactionError::RangeProofError(RangeProofError::ProofConstructionError(format!( + "Failed to construct range proof: {}", + err + ))) + })?; + + RangeProof::from_bytes(&proof_bytes).map_err(|_| { + TransactionError::RangeProofError(RangeProofError::ProofConstructionError( + "Rangeproof factory returned invalid range proof bytes".to_string(), + )) + }) + } + pub fn as_rewindable_transaction_output( &self, factories: &CryptoFactories, @@ -257,16 +303,12 @@ impl UnblindedOutput { let proof = if let Some(proof) = range_proof { proof.clone() } else { - let proof_bytes = factories.range_proof.construct_proof_with_recovery_seed_nonce( - &self.spending_key, - self.value.into(), - &rewind_data.rewind_blinding_key, - )?; - RangeProof::from_bytes(&proof_bytes).map_err(|_| { - TransactionError::RangeProofError(RangeProofError::ProofConstructionError( - "Creating rewindable transaction output".to_string(), - )) - })? + self.construct_range_proof(factories, Some(rewind_data.rewind_blinding_key.clone())) + .map_err(|_| { + TransactionError::RangeProofError(RangeProofError::ProofConstructionError( + "Creating rewindable transaction output".to_string(), + )) + })? }; let output = TransactionOutput::new( @@ -279,6 +321,7 @@ impl UnblindedOutput { self.metadata_signature.clone(), self.covenant.clone(), self.encrypted_value.clone(), + self.minimum_value_promise, ); Ok(output) @@ -301,6 +344,7 @@ impl UnblindedOutput { &self.script, &self.covenant, &self.encrypted_value, + self.minimum_value_promise, ) .to_vec() } @@ -341,6 +385,7 @@ impl Debug for UnblindedOutput { .field("sender_offset_public_key", &self.sender_offset_public_key) .field("metadata_signature", &self.metadata_signature) .field("script_lock_height", &self.script_lock_height) + .field("minimum_value_promise", &self.minimum_value_promise) .finish() } } diff --git a/base_layer/core/src/transactions/transaction_components/unblinded_output_builder.rs b/base_layer/core/src/transactions/transaction_components/unblinded_output_builder.rs index 1ad53b7444..810b456e81 100644 --- a/base_layer/core/src/transactions/transaction_components/unblinded_output_builder.rs +++ b/base_layer/core/src/transactions/transaction_components/unblinded_output_builder.rs @@ -60,6 +60,7 @@ pub struct UnblindedOutputBuilder { metadata_signed_by_sender: bool, encrypted_value: EncryptedValue, rewind_data: Option, + minimum_value_promise: MicroTari, } impl UnblindedOutputBuilder { @@ -78,6 +79,7 @@ impl UnblindedOutputBuilder { metadata_signed_by_sender: false, encrypted_value: EncryptedValue::default(), rewind_data: None, + minimum_value_promise: MicroTari::zero(), } } @@ -100,6 +102,7 @@ impl UnblindedOutputBuilder { &public_nonce_commitment, &self.covenant, &self.encrypted_value, + self.minimum_value_promise, )?; self.metadata_signature = Some(metadata_partial); self.metadata_signed_by_receiver = true; @@ -118,6 +121,7 @@ impl UnblindedOutputBuilder { sender_offset_private_key, &self.covenant, &self.encrypted_value, + self.minimum_value_promise, )?; self.metadata_signature = Some(metadata_sig); self.metadata_signed_by_sender = true; @@ -152,6 +156,7 @@ impl UnblindedOutputBuilder { 0, self.covenant, self.encrypted_value, + self.minimum_value_promise, ); Ok(ub) } diff --git a/base_layer/core/src/transactions/transaction_protocol/mod.rs b/base_layer/core/src/transactions/transaction_protocol/mod.rs index f29f210673..e97318a46d 100644 --- a/base_layer/core/src/transactions/transaction_protocol/mod.rs +++ b/base_layer/core/src/transactions/transaction_protocol/mod.rs @@ -129,6 +129,8 @@ pub enum TransactionProtocolError { ConversionError(String), #[error("The script offset private key could not be found")] ScriptOffsetPrivateKeyNotFound, + #[error("The minimum value promise could not be found")] + MinimumValuePromiseNotFound, #[error("Value encryption failed")] EncryptionError, } diff --git a/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.proto b/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.proto index 782f70372f..2d0b18685e 100644 --- a/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.proto +++ b/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.proto @@ -32,6 +32,8 @@ message SingleRoundSenderData { tari.types.OutputFeatures features = 10; // Covenant bytes covenant = 11; + // The minimum value of the commitment that is proven by the range proof (in MicroTari) + uint64 minimum_value_promise = 12; // Unique id for NFTs // bytes unique_id = 12; } diff --git a/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.rs b/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.rs index 08fcb8d81f..c10b9e56ce 100644 --- a/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.rs +++ b/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.rs @@ -121,6 +121,7 @@ impl TryFrom for SingleRoundSenderData { sender_offset_public_key, public_commitment_nonce, covenant, + minimum_value_promise: data.minimum_value_promise.into(), }) } } @@ -141,6 +142,7 @@ impl From for proto::SingleRoundSenderData { sender_offset_public_key: sender_data.sender_offset_public_key.to_vec(), public_commitment_nonce: sender_data.public_commitment_nonce.to_vec(), covenant: sender_data.covenant.to_consensus_bytes(), + minimum_value_promise: sender_data.minimum_value_promise.into(), } } } diff --git a/base_layer/core/src/transactions/transaction_protocol/recipient.rs b/base_layer/core/src/transactions/transaction_protocol/recipient.rs index b84a5acb6b..b476763bc6 100644 --- a/base_layer/core/src/transactions/transaction_protocol/recipient.rs +++ b/base_layer/core/src/transactions/transaction_protocol/recipient.rs @@ -250,6 +250,7 @@ mod test { sender_offset_public_key: p.sender_offset_public_key, public_commitment_nonce: p.sender_public_commitment_nonce, covenant: Covenant::default(), + minimum_value_promise: MicroTari::zero(), }; let sender_info = TransactionSenderMessage::Single(Box::new(msg.clone())); let pubkey = PublicKey::from_secret_key(&p.spend_key); @@ -298,6 +299,7 @@ mod test { sender_offset_public_key: p.sender_offset_public_key, public_commitment_nonce: p.sender_public_commitment_nonce, covenant: Covenant::default(), + minimum_value_promise: MicroTari::zero(), }; let sender_info = TransactionSenderMessage::Single(Box::new(msg)); let receiver = ReceiverTransactionProtocol::new_with_rewindable_output( diff --git a/base_layer/core/src/transactions/transaction_protocol/sender.rs b/base_layer/core/src/transactions/transaction_protocol/sender.rs index df4ecb5fd8..79c078e987 100644 --- a/base_layer/core/src/transactions/transaction_protocol/sender.rs +++ b/base_layer/core/src/transactions/transaction_protocol/sender.rs @@ -93,6 +93,7 @@ pub(super) struct RawTransactionInfo { #[derivative(Debug = "ignore")] pub recipient_sender_offset_private_keys: Vec, pub recipient_covenants: Vec, + pub recipient_minimum_value_promise: Vec, // The sender's portion of the public commitment nonce #[derivative(Debug = "ignore")] pub private_commitment_nonces: Vec, @@ -148,6 +149,8 @@ pub struct SingleRoundSenderData { pub public_commitment_nonce: PublicKey, /// Covenant pub covenant: Covenant, + /// The minimum value of the commitment that is proven by the range proof + pub minimum_value_promise: MicroTari, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -354,6 +357,20 @@ impl SenderTransactionProtocol { } } + pub fn get_minimum_value_promise(&self, recipient_index: usize) -> Result { + match &self.state { + SenderState::Initializing(info) | + SenderState::Finalizing(info) | + SenderState::SingleRoundMessageReady(info) | + SenderState::CollectingSingleSignature(info) => Ok(*info + .recipient_minimum_value_promise + .get(recipient_index) + .ok_or(TPE::MinimumValuePromiseNotFound)?), + SenderState::FinalizedTransaction(_) => Err(TPE::InvalidStateError), + SenderState::Failed(_) => Err(TPE::InvalidStateError), + } + } + /// Build the sender's message for the single-round protocol (one recipient) and move to next State pub fn build_single_round_message(&mut self) -> Result { match &self.state { @@ -398,6 +415,10 @@ impl SenderTransactionProtocol { let recipient_covenant = info.recipient_covenants.first().cloned().ok_or_else(|| { TPE::IncompleteStateError("The recipient covenant should be available".to_string()) })?; + let recipient_minimum_value_promise = + info.recipient_minimum_value_promise.first().copied().ok_or_else(|| { + TPE::IncompleteStateError("The recipient minimum value promise should be available".to_string()) + })?; Ok(SingleRoundSenderData { tx_id: info.tx_id, @@ -411,6 +432,7 @@ impl SenderTransactionProtocol { sender_offset_public_key: PublicKey::from_secret_key(recipient_script_offset_secret_key), public_commitment_nonce: PublicKey::from_secret_key(private_commitment_nonce), covenant: recipient_covenant, + minimum_value_promise: recipient_minimum_value_promise, }) }, _ => Err(TPE::InvalidStateError), @@ -900,6 +922,8 @@ mod test { let encryption_key = PrivateKey::random(&mut OsRng); let encrypted_value = EncryptedValue::encrypt_value(&encryption_key, &commitment, value.into()).unwrap(); + let minimum_value_promise = MicroTari::zero(); + let partial_metadata_signature = TransactionOutput::create_partial_metadata_signature( TransactionOutputVersion::get_current_version(), value.into(), @@ -910,6 +934,7 @@ mod test { &sender_public_commitment_nonce, &covenant, &encrypted_value, + minimum_value_promise, ) .unwrap(); @@ -922,6 +947,7 @@ mod test { partial_metadata_signature.clone(), covenant, encrypted_value, + minimum_value_promise, ); assert!(output.verify_metadata_signature().is_err()); assert!(partial_metadata_signature.verify_challenge( @@ -999,7 +1025,7 @@ mod test { .with_offset(a.offset.clone()) .with_private_nonce(a.nonce.clone()) .with_input(utxo.clone(), input) - .with_recipient_data(0, script.clone(), PrivateKey::random(&mut OsRng), OutputFeatures::default(), PrivateKey::random(&mut OsRng), Covenant::default()) + .with_recipient_data(0, script.clone(), PrivateKey::random(&mut OsRng), OutputFeatures::default(), PrivateKey::random(&mut OsRng), Covenant::default(), MicroTari::zero()) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()) // A little twist: Check the case where the change is less than the cost of another output .with_amount(0, MicroTari(1200) - fee - MicroTari(10)); @@ -1067,6 +1093,7 @@ mod test { OutputFeatures::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()) .with_amount(0, MicroTari(5000)); @@ -1146,6 +1173,7 @@ mod test { OutputFeatures::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()) .with_amount(0, (2u64.pow(32) + 1).into()); @@ -1192,6 +1220,7 @@ mod test { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); // Verify that the initial 'fee greater than amount' check rejects the transaction when it is constructed @@ -1226,6 +1255,7 @@ mod test { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); // Test if the transaction passes the initial 'fee greater than amount' check when it is constructed @@ -1264,6 +1294,7 @@ mod test { OutputFeatures::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); let mut alice = builder.build::(&factories, None, u64::MAX).unwrap(); diff --git a/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs b/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs index ff37c9a905..128e574828 100644 --- a/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs +++ b/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs @@ -113,6 +113,8 @@ impl SingleReceiverTransactionProtocol { .map_err(|_| TPE::EncryptionError)? .unwrap_or_default(); + let minimum_value_promise = sender_info.minimum_value_promise; + let partial_metadata_signature = TransactionOutput::create_partial_metadata_signature( TransactionOutputVersion::get_current_version(), sender_info.amount, @@ -123,6 +125,7 @@ impl SingleReceiverTransactionProtocol { &sender_info.public_commitment_nonce, &sender_info.covenant, &encrypted_value, + minimum_value_promise, )?; let output = TransactionOutput::new_current_version( @@ -138,6 +141,7 @@ impl SingleReceiverTransactionProtocol { partial_metadata_signature, sender_info.covenant.clone(), encrypted_value, + minimum_value_promise, ); Ok(output) } @@ -215,6 +219,7 @@ mod test { sender_offset_public_key, public_commitment_nonce, covenant: Default::default(), + minimum_value_promise: MicroTari::zero(), }; let prot = SingleReceiverTransactionProtocol::create(&info, r, k.clone(), &factories, None).unwrap(); assert_eq!(prot.tx_id.as_u64(), 500, "tx_id is incorrect"); diff --git a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs index dd5b2c5a7e..c4d7a4b278 100644 --- a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs +++ b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs @@ -101,6 +101,7 @@ pub struct SenderTransactionInitializer { recipient_scripts: FixedSet, recipient_sender_offset_private_keys: FixedSet, recipient_covenants: FixedSet, + recipient_minimum_value_promise: FixedSet, private_commitment_nonces: FixedSet, tx_id: Option, fee: Fee, @@ -145,6 +146,7 @@ impl SenderTransactionInitializer { recipient_scripts: FixedSet::new(num_recipients), recipient_sender_offset_private_keys: FixedSet::new(num_recipients), recipient_covenants: FixedSet::new(num_recipients), + recipient_minimum_value_promise: FixedSet::new(num_recipients), private_commitment_nonces: FixedSet::new(num_recipients), tx_id: None, } @@ -173,6 +175,7 @@ impl SenderTransactionInitializer { recipient_output_features: OutputFeatures, private_commitment_nonce: PrivateKey, covenant: Covenant, + minimum_value_promise: MicroTari, ) -> &mut Self { self.recipient_output_features .set_item(receiver_index, recipient_output_features); @@ -182,6 +185,8 @@ impl SenderTransactionInitializer { self.private_commitment_nonces .set_item(receiver_index, private_commitment_nonce); self.recipient_covenants.set_item(receiver_index, covenant); + self.recipient_minimum_value_promise + .set_item(receiver_index, minimum_value_promise); self } @@ -224,6 +229,7 @@ impl SenderTransactionInitializer { &commitment, &output.covenant, &output.encrypted_value, + output.minimum_value_promise, ); if !output.metadata_signature.verify_challenge( &(&commitment + &output.sender_offset_public_key), @@ -389,6 +395,8 @@ impl SenderTransactionInitializer { .map_err(|e| e.to_string())? .unwrap_or_default(); + let minimum_value_promise = MicroTari::zero(); + let metadata_signature = TransactionOutput::create_final_metadata_signature( TransactionOutputVersion::get_current_version(), v, @@ -398,8 +406,10 @@ impl SenderTransactionInitializer { &change_sender_offset_private_key, &self.change_covenant, &encrypted_value, + minimum_value_promise, ) .map_err(|e| e.to_string())?; + let change_unblinded_output = UnblindedOutput::new_current_version( v, change_key.clone(), @@ -418,6 +428,7 @@ impl SenderTransactionInitializer { 0, self.change_covenant.clone(), encrypted_value, + minimum_value_promise, ); Ok((fee_without_change + change_fee, v, Some(change_unblinded_output))) }, @@ -639,6 +650,7 @@ impl SenderTransactionInitializer { recipient_scripts: self.recipient_scripts.into_vec(), recipient_sender_offset_private_keys: self.recipient_sender_offset_private_keys.into_vec(), recipient_covenants: self.recipient_covenants.into_vec(), + recipient_minimum_value_promise: self.recipient_minimum_value_promise.into_vec(), private_commitment_nonces: self.private_commitment_nonces.into_vec(), change, unblinded_change_output: change_output, @@ -742,6 +754,7 @@ mod test { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); let expected_fee = builder @@ -923,6 +936,7 @@ mod test { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ); // .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); let err = builder.build::(&factories, None, u64::MAX).unwrap_err(); @@ -956,6 +970,7 @@ mod test { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); let err = builder.build::(&factories, None, u64::MAX).unwrap_err(); @@ -994,6 +1009,7 @@ mod test { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_recipient_data( 1, @@ -1002,6 +1018,7 @@ mod test { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); let result = builder.build::(&factories, None, u64::MAX).unwrap(); @@ -1057,6 +1074,7 @@ mod test { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); let result = builder.build::(&factories, None, u64::MAX).unwrap(); @@ -1109,6 +1127,7 @@ mod test { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); let result = builder.build::(&factories, None, u64::MAX); diff --git a/base_layer/core/src/validation/block_validators/async_validator.rs b/base_layer/core/src/validation/block_validators/async_validator.rs index e7aaceb914..2d5e0d89b5 100644 --- a/base_layer/core/src/validation/block_validators/async_validator.rs +++ b/base_layer/core/src/validation/block_validators/async_validator.rs @@ -299,6 +299,7 @@ impl BlockValidator { output.sender_offset_public_key, output.covenant, output.encrypted_value, + output.minimum_value_promise, ); }, } diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index 55a6d25626..db7d5e8c16 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -416,6 +416,7 @@ pub fn check_input_is_utxo(db: &B, input: &TransactionInpu output.sender_offset_public_key, output.covenant, output.encrypted_value, + output.minimum_value_promise, ); let input_hash = input.canonical_hash()?; if compact.canonical_hash()? != input_hash { @@ -922,6 +923,7 @@ mod test { Default::default(), Default::default(), Default::default(), + MicroTari::zero(), ); assert!(matches!( diff --git a/base_layer/core/src/validation/test.rs b/base_layer/core/src/validation/test.rs index 5cb577a004..1d97b3b798 100644 --- a/base_layer/core/src/validation/test.rs +++ b/base_layer/core/src/validation/test.rs @@ -142,6 +142,7 @@ fn chain_balance_validation() { &OutputFeatures::default(), &script!(Nop), &Covenant::default(), + MicroTari::zero(), ); let (pk, sig) = create_random_signature_from_s_key(faucet_key, 0.into(), 0); let excess = Commitment::from_public_key(&pk); @@ -188,6 +189,7 @@ fn chain_balance_validation() { &OutputFeatures::create_coinbase(1), &script!(Nop), &Covenant::default(), + MicroTari::zero(), ); // let _coinbase_hash = coinbase.hash(); let (pk, sig) = create_random_signature_from_s_key(coinbase_key, 0.into(), 0); @@ -240,6 +242,7 @@ fn chain_balance_validation() { &OutputFeatures::create_coinbase(1), &script!(Nop), &Covenant::default(), + MicroTari::zero(), ); let (pk, sig) = create_random_signature_from_s_key(key, 0.into(), 0); let excess = Commitment::from_public_key(&pk); diff --git a/base_layer/core/tests/chain_storage_tests/chain_storage.rs b/base_layer/core/tests/chain_storage_tests/chain_storage.rs index d607e28321..2e6a800e17 100644 --- a/base_layer/core/tests/chain_storage_tests/chain_storage.rs +++ b/base_layer/core/tests/chain_storage_tests/chain_storage.rs @@ -1867,6 +1867,7 @@ mod malleability { output.sender_offset_public_key.clone(), output.covenant.clone(), output.encrypted_value.clone(), + output.minimum_value_promise, ); }); } diff --git a/base_layer/core/tests/helpers/block_builders.rs b/base_layer/core/tests/helpers/block_builders.rs index 0e8d79af03..fa21dfd85a 100644 --- a/base_layer/core/tests/helpers/block_builders.rs +++ b/base_layer/core/tests/helpers/block_builders.rs @@ -138,6 +138,7 @@ fn print_new_genesis_block(network: Network) { &OutputFeatures::create_coinbase(1), &script![Nop], &Covenant::default(), + MicroTari::zero(), ); let (pk, sig) = create_random_signature_from_s_key(key, 0.into(), 0); let excess = Commitment::from_public_key(&pk); diff --git a/base_layer/core/tests/node_comms_interface.rs b/base_layer/core/tests/node_comms_interface.rs index b38fe224b5..9e7c9b7b19 100644 --- a/base_layer/core/tests/node_comms_interface.rs +++ b/base_layer/core/tests/node_comms_interface.rs @@ -192,6 +192,7 @@ async fn inbound_fetch_utxos() { &Default::default(), &TariScript::default(), &Covenant::default(), + MicroTari::zero(), ); let hash_2 = utxo_2.hash(); @@ -234,6 +235,7 @@ async fn inbound_fetch_txos() { &Default::default(), &TariScript::default(), &Covenant::default(), + MicroTari::zero(), ); let (pruned_utxo, _, _) = create_utxo( MicroTari(10_000), @@ -241,6 +243,7 @@ async fn inbound_fetch_txos() { &Default::default(), &TariScript::default(), &Covenant::default(), + MicroTari::zero(), ); let (stxo, _, _) = create_utxo( MicroTari(10_000), @@ -248,6 +251,7 @@ async fn inbound_fetch_txos() { &Default::default(), &TariScript::default(), &Covenant::default(), + MicroTari::zero(), ); let utxo_hash = utxo.hash(); let stxo_hash = stxo.hash(); @@ -315,6 +319,7 @@ async fn inbound_fetch_blocks() { } #[tokio::test] +#[allow(clippy::too_many_lines)] async fn inbound_fetch_blocks_before_horizon_height() { let factories = CryptoFactories::default(); let network = Network::LocalNet; @@ -352,7 +357,14 @@ async fn inbound_fetch_blocks_before_horizon_height() { ); let script = script!(Nop); let amount = MicroTari(10_000); - let (utxo, key, offset) = create_utxo(amount, &factories, &Default::default(), &script, &Covenant::default()); + let (utxo, key, offset) = create_utxo( + amount, + &factories, + &Default::default(), + &script, + &Covenant::default(), + MicroTari::zero(), + ); let metadata_signature = TransactionOutput::create_final_metadata_signature( TransactionOutputVersion::get_current_version(), amount, @@ -362,6 +374,7 @@ async fn inbound_fetch_blocks_before_horizon_height() { &offset, &Covenant::default(), &utxo.encrypted_value, + utxo.minimum_value_promise, ) .unwrap(); let unblinded_output = UnblindedOutput::new_current_version( @@ -376,6 +389,7 @@ async fn inbound_fetch_blocks_before_horizon_height() { 0, Covenant::default(), utxo.encrypted_value.clone(), + utxo.minimum_value_promise, ); let mut txn = DbTransaction::new(); txn.insert_utxo(utxo.clone(), block0.hash().clone(), 0, 4002); diff --git a/base_layer/wallet/migrations/2022-07-15-214144_add_minimum_value_promise/down.sql b/base_layer/wallet/migrations/2022-07-15-214144_add_minimum_value_promise/down.sql new file mode 100644 index 0000000000..4bdb163c23 --- /dev/null +++ b/base_layer/wallet/migrations/2022-07-15-214144_add_minimum_value_promise/down.sql @@ -0,0 +1 @@ +ALTER TABLE outputs DROP COLUMN minimum_value_promise; \ No newline at end of file diff --git a/base_layer/wallet/migrations/2022-07-15-214144_add_minimum_value_promise/up.sql b/base_layer/wallet/migrations/2022-07-15-214144_add_minimum_value_promise/up.sql new file mode 100644 index 0000000000..2048ada7c2 --- /dev/null +++ b/base_layer/wallet/migrations/2022-07-15-214144_add_minimum_value_promise/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE outputs + ADD minimum_value_promise BIGINT NOT NULL; \ No newline at end of file diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index 56bbc0520b..c811333772 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -85,6 +85,7 @@ pub enum OutputManagerRequest { message: String, script: TariScript, covenant: Covenant, + minimum_value_promise: MicroTari, }, CreatePayToSelfTransaction { tx_id: TxId, @@ -510,6 +511,7 @@ impl OutputManagerHandle { message: String, script: TariScript, covenant: Covenant, + minimum_value_promise: MicroTari, ) -> Result { match self .handle @@ -523,6 +525,7 @@ impl OutputManagerHandle { message, script, covenant, + minimum_value_promise, }) .await?? { diff --git a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs index 0aaceaf6b5..13e578fddf 100644 --- a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs +++ b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs @@ -118,6 +118,7 @@ where 0, output.covenant, output.encrypted_value, + output.minimum_value_promise, ); rewound_outputs.push((uo, output.proof)); } diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 0c52246b07..0b46166db1 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -282,6 +282,7 @@ where message, script, covenant, + minimum_value_promise, } => self .prepare_transaction_to_send( tx_id, @@ -293,6 +294,7 @@ where *output_features, script, covenant, + minimum_value_promise, ) .await .map(OutputManagerResponse::TransactionToSend), @@ -692,6 +694,7 @@ where &commitment, single_round_sender_data.amount, )?; + let minimum_value_promise = single_round_sender_data.minimum_value_promise; let output = DbUnblindedOutput::rewindable_from_unblinded_output( UnblindedOutput::new_current_version( single_round_sender_data.amount, @@ -713,10 +716,12 @@ where &single_round_sender_data.public_commitment_nonce, &single_round_sender_data.covenant, &encrypted_value, + minimum_value_promise, )?, 0, single_round_sender_data.covenant.clone(), encrypted_value, + minimum_value_promise, ), &self.resources.factories, &self.resources.rewind_data, @@ -802,6 +807,7 @@ where recipient_output_features: OutputFeatures, recipient_script: TariScript, recipient_covenant: Covenant, + recipient_minimum_value_promise: MicroTari, ) -> Result { debug!( target: LOG_TARGET, @@ -841,6 +847,7 @@ where recipient_output_features, PrivateKey::random(&mut OsRng), recipient_covenant, + recipient_minimum_value_promise, ) .with_message(message) .with_prevent_fee_gt_amount(self.resources.config.prevent_fee_gt_amount) @@ -1202,6 +1209,7 @@ where .commit_value(&spending_key, amount.into()); let encrypted_value = EncryptedValue::encrypt_value(&self.resources.rewind_data.encryption_key, &commitment, amount)?; + let minimum_amount_promise = MicroTari::zero(); let metadata_signature = TransactionOutput::create_final_metadata_signature( TransactionOutputVersion::get_current_version(), amount, @@ -1211,6 +1219,7 @@ where &sender_offset_private_key, &covenant, &encrypted_value, + minimum_amount_promise, )?; let utxo = DbUnblindedOutput::rewindable_from_unblinded_output( UnblindedOutput::new_current_version( @@ -1225,6 +1234,7 @@ where 0, covenant, encrypted_value, + minimum_amount_promise, ), &self.resources.factories, &self.resources.rewind_data, @@ -1548,6 +1558,7 @@ where .commit_value(&spending_key, output_amount.into()); let encrypted_value = EncryptedValue::encrypt_value(&self.resources.rewind_data.encryption_key, &commitment, output_amount)?; + let minimum_value_promise = MicroTari::zero(); let metadata_signature = TransactionOutput::create_final_metadata_signature( TransactionOutputVersion::get_current_version(), output_amount, @@ -1557,6 +1568,7 @@ where &sender_offset_private_key, &covenant, &encrypted_value, + minimum_value_promise, )?; let utxo = DbUnblindedOutput::rewindable_from_unblinded_output( UnblindedOutput::new_current_version( @@ -1571,6 +1583,7 @@ where 0, covenant.clone(), encrypted_value, + minimum_value_promise, ), &self.resources.factories, &self.resources.rewind_data.clone(), @@ -1702,6 +1715,7 @@ where 0, output.covenant, output.encrypted_value, + output.minimum_value_promise, ); let offset = PrivateKey::random(&mut OsRng); @@ -1935,6 +1949,7 @@ where known_one_sided_payment_scripts[i].script_lock_height, output.covenant, output.encrypted_value, + output.minimum_value_promise, ); let db_output = DbUnblindedOutput::rewindable_from_unblinded_output( diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs index aeb50ce63b..a48219f5af 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs @@ -65,6 +65,7 @@ pub struct NewOutputSql { pub covenant: Vec, pub encrypted_value: Vec, pub contract_id: Option>, + pub minimum_value_promise: i64, } impl NewOutputSql { @@ -108,6 +109,7 @@ impl NewOutputSql { covenant: output.unblinded_output.covenant.to_bytes(), encrypted_value: output.unblinded_output.encrypted_value.to_vec(), contract_id: output.unblinded_output.features.contract_id().map(|h| h.to_vec()), + minimum_value_promise: output.unblinded_output.minimum_value_promise.as_u64() as i64, }) } @@ -158,6 +160,7 @@ impl From for NewOutputSql { covenant: o.covenant, encrypted_value: o.encrypted_value, contract_id: o.contract_id, + minimum_value_promise: o.minimum_value_promise, } } } diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs index 2beaba9604..4640ad39ae 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs @@ -103,6 +103,7 @@ pub struct OutputSql { pub covenant: Vec, pub encrypted_value: Vec, pub contract_id: Option>, + pub minimum_value_promise: i64, } impl OutputSql { @@ -689,6 +690,7 @@ impl TryFrom for DbUnblindedOutput { } })?, encrypted_value, + MicroTari::from(o.minimum_value_promise as u64), ); let hash = match o.hash { diff --git a/base_layer/wallet/src/schema.rs b/base_layer/wallet/src/schema.rs index 1a6a412a06..72c8b97dda 100644 --- a/base_layer/wallet/src/schema.rs +++ b/base_layer/wallet/src/schema.rs @@ -155,6 +155,7 @@ table! { covenant -> Binary, encrypted_value -> Binary, contract_id -> Nullable, + minimum_value_promise -> BigInt, } } diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs index 556df671ad..ebb3597de1 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs @@ -225,6 +225,7 @@ where self.message.clone(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await { diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 3d5bff7b06..872aba1bbd 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -1003,6 +1003,9 @@ where // Empty covenant let covenant = Covenant::default(); + // Default range proof + let minimum_value_promise = MicroTari::zero(); + // Prepare sender part of the transaction let mut stp = self .output_manager_service @@ -1016,6 +1019,7 @@ where message.clone(), script.clone(), covenant.clone(), + minimum_value_promise, ) .await?; @@ -1067,6 +1071,7 @@ where .commitment .commit_value(&spend_key, amount.into()); let encrypted_value = EncryptedValue::encrypt_value(&rewind_data.encryption_key, &commitment, amount)?; + let minimum_value_promise = MicroTari::zero(); let unblinded_output = UnblindedOutput::new_current_version( amount, spend_key, @@ -1079,6 +1084,7 @@ where height, covenant, encrypted_value, + minimum_value_promise, ); // Start finalizing @@ -1174,6 +1180,7 @@ where message.clone(), script, Covenant::default(), + MicroTari::zero(), ) .await?; diff --git a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs index ed299b1f9a..754ab3bd62 100644 --- a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs @@ -2220,6 +2220,7 @@ mod test { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script!(Nop), ExecutionStack::default(), PrivateKey::random(&mut OsRng)); diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index e850d1bc93..f5fdc613f8 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -432,6 +432,7 @@ where script_lock_height: u64, covenant: Covenant, encrypted_value: EncryptedValue, + minimum_value_promise: MicroTari, ) -> Result { let unblinded_output = UnblindedOutput::new_current_version( amount, @@ -445,6 +446,7 @@ where script_lock_height, covenant, encrypted_value, + minimum_value_promise, ); let tx_id = self diff --git a/base_layer/wallet/tests/output_manager_service_tests/service.rs b/base_layer/wallet/tests/output_manager_service_tests/service.rs index e8c1097a16..541ec47e39 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/service.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/service.rs @@ -342,6 +342,7 @@ async fn generate_sender_transaction_message(amount: MicroTari) -> (TxId, Transa OutputFeatures::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script( script!(Nop), @@ -439,6 +440,7 @@ async fn test_utxo_selection_no_chain_metadata() { "".to_string(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await .unwrap_err(); @@ -471,6 +473,7 @@ async fn test_utxo_selection_no_chain_metadata() { String::new(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await .unwrap(); @@ -552,6 +555,7 @@ async fn test_utxo_selection_with_chain_metadata() { "".to_string(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await .unwrap_err(); @@ -613,6 +617,7 @@ async fn test_utxo_selection_with_chain_metadata() { "".to_string(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await .unwrap(); @@ -640,6 +645,7 @@ async fn test_utxo_selection_with_chain_metadata() { "".to_string(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await .unwrap(); @@ -711,6 +717,7 @@ async fn test_utxo_selection_with_tx_priority() { "".to_string(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await .unwrap(); @@ -789,6 +796,7 @@ async fn utxo_selection_for_contract_checkpoint() { String::new(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await .unwrap(); @@ -831,6 +839,7 @@ async fn send_not_enough_funds() { "".to_string(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await { @@ -890,6 +899,7 @@ async fn send_no_change() { "".to_string(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await .unwrap(); @@ -955,6 +965,7 @@ async fn send_not_enough_for_change() { "".to_string(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await { @@ -995,6 +1006,7 @@ async fn cancel_transaction() { "".to_string(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await .unwrap(); @@ -1087,6 +1099,7 @@ async fn test_get_balance() { "".to_string(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await .unwrap(); @@ -1143,6 +1156,7 @@ async fn sending_transaction_persisted_while_offline() { "".to_string(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await .unwrap(); @@ -1174,6 +1188,7 @@ async fn sending_transaction_persisted_while_offline() { "".to_string(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await .unwrap(); @@ -1469,6 +1484,7 @@ async fn test_txo_validation() { "".to_string(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await .unwrap(); @@ -2134,6 +2150,7 @@ async fn scan_for_recovery_test() { 0, Covenant::new(), encrypted_value, + MicroTari::zero(), ); rewindable_unblinded_outputs.push(uo); } diff --git a/base_layer/wallet/tests/transaction_service_tests/service.rs b/base_layer/wallet/tests/transaction_service_tests/service.rs index e00f08558d..c946344cba 100644 --- a/base_layer/wallet/tests/transaction_service_tests/service.rs +++ b/base_layer/wallet/tests/transaction_service_tests/service.rs @@ -1459,6 +1459,7 @@ async fn finalize_tx_with_incorrect_pubkey() { "".to_string(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await .unwrap(); @@ -1574,6 +1575,7 @@ async fn finalize_tx_with_missing_output() { "".to_string(), script!(Nop), Covenant::default(), + MicroTari::zero(), ) .await .unwrap(); @@ -2156,6 +2158,7 @@ async fn test_transaction_cancellation() { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script!(Nop), ExecutionStack::default(), PrivateKey::random(&mut OsRng)); @@ -2237,6 +2240,7 @@ async fn test_transaction_cancellation() { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script!(Nop), ExecutionStack::default(), PrivateKey::random(&mut OsRng)); @@ -2896,6 +2900,7 @@ async fn test_restarting_transaction_protocols() { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script( script!(Nop), @@ -4196,6 +4201,7 @@ async fn test_resend_on_startup() { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script!(Nop), ExecutionStack::default(), PrivateKey::random(&mut OsRng)); @@ -4672,6 +4678,7 @@ async fn test_transaction_timeout_cancellation() { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script!(Nop), ExecutionStack::default(), PrivateKey::random(&mut OsRng)); diff --git a/base_layer/wallet/tests/transaction_service_tests/storage.rs b/base_layer/wallet/tests/transaction_service_tests/storage.rs index 92650a70e0..6f16b5e33a 100644 --- a/base_layer/wallet/tests/transaction_service_tests/storage.rs +++ b/base_layer/wallet/tests/transaction_service_tests/storage.rs @@ -97,6 +97,7 @@ pub fn test_db_backend(backend: T) { Default::default(), PrivateKey::random(&mut OsRng), Covenant::default(), + MicroTari::zero(), ) .with_change_script(script!(Nop), ExecutionStack::default(), PrivateKey::random(&mut OsRng)); diff --git a/base_layer/wallet/tests/wallet.rs b/base_layer/wallet/tests/wallet.rs index 4371e509cd..05af980cd9 100644 --- a/base_layer/wallet/tests/wallet.rs +++ b/base_layer/wallet/tests/wallet.rs @@ -730,6 +730,7 @@ async fn test_import_utxo() { 0, Covenant::default(), output.encrypted_value, + utxo.minimum_value_promise, ) .await .unwrap(); diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 5165a77c79..72d182715a 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -5684,6 +5684,7 @@ pub unsafe extern "C" fn wallet_import_external_utxo_as_non_rewindable( script_private_key: *mut TariPrivateKey, covenant: *mut TariCovenant, encrypted_value: *mut TariEncryptedValue, + minimum_value_promise: c_ulonglong, message: *const c_char, error_out: *mut c_int, ) -> c_ulonglong { @@ -5788,6 +5789,7 @@ pub unsafe extern "C" fn wallet_import_external_utxo_as_non_rewindable( 0, covenant, encrypted_value, + MicroTari::from(minimum_value_promise), )) { Ok(tx_id) => { if let Err(e) = (*wallet) @@ -8620,6 +8622,7 @@ mod test { let script_private_key_ptr = Box::into_raw(Box::new(utxo_1.script_private_key)); let covenant_ptr = Box::into_raw(Box::new(utxo_1.covenant)); let encrypted_value_ptr = Box::into_raw(Box::new(utxo_1.encrypted_value)); + let minimum_value_promise = utxo_1.minimum_value_promise.as_u64(); let message_ptr = CString::into_raw(CString::new("For my friend").unwrap()) as *const c_char; let tx_id = wallet_import_external_utxo_as_non_rewindable( @@ -8633,6 +8636,7 @@ mod test { script_private_key_ptr, covenant_ptr, encrypted_value_ptr, + minimum_value_promise, message_ptr, error_ptr, ); diff --git a/base_layer/wallet_ffi/tari_wallet_ffi.h b/base_layer/wallet_ffi/tari_wallet_ffi.h index 92a8487488..ecc08623b4 100644 --- a/base_layer/wallet_ffi/tari_wallet_ffi.h +++ b/base_layer/wallet_ffi/tari_wallet_ffi.h @@ -2746,6 +2746,7 @@ unsigned long long wallet_import_external_utxo_as_non_rewindable(struct TariWall TariPrivateKey *script_private_key, TariCovenant *covenant, TariEncryptedValue *encrypted_value, + unsigned long long minimum_value_promise, const char *message, int *error_out); diff --git a/integration_tests/helpers/ffi/ffiInterface.js b/integration_tests/helpers/ffi/ffiInterface.js index f373c28812..ea9791a88a 100644 --- a/integration_tests/helpers/ffi/ffiInterface.js +++ b/integration_tests/helpers/ffi/ffiInterface.js @@ -405,6 +405,7 @@ class InterfaceFFI { this.ptr, this.ptr, this.ptr, + this.ulonglong, this.string, this.intPtr, ],