From d495bd402229f65d6c96d8cd126b3b9d693b0599 Mon Sep 17 00:00:00 2001 From: ryan kurte Date: Tue, 14 Feb 2023 13:48:19 +1300 Subject: [PATCH 01/11] working on summary report improvements splits output and totals, adds balance checks, support for SCIs, tests --- account-keys/Cargo.toml | 4 +- account-keys/src/account_keys.rs | 8 +- transaction/extra/tests/verifier.rs | 192 +++++++------ transaction/summary/Cargo.toml | 6 + transaction/summary/src/data.rs | 13 +- transaction/summary/src/lib.rs | 2 +- transaction/summary/src/report.rs | 345 ++++++++++++++++++++--- transaction/summary/src/verifier.rs | 406 ++++++++++++++++++++++++++-- 8 files changed, 797 insertions(+), 179 deletions(-) diff --git a/account-keys/Cargo.toml b/account-keys/Cargo.toml index 2693101494..01f3471012 100644 --- a/account-keys/Cargo.toml +++ b/account-keys/Cargo.toml @@ -10,7 +10,7 @@ rust-version = { workspace = true } [features] std = ["mc-util-repr-bytes/alloc"] prost = ["dep:prost", "mc-util-repr-bytes/prost", "mc-crypto-keys/prost"] -serde = ["mc-crypto-keys/serde"] +serde = ["dep:serde", "mc-crypto-keys/serde", "curve25519-dalek/serde"] default = ["std", "prost", "serde", "mc-util-serial", "mc-crypto-digestible/default", "mc-crypto-hashes/default", "mc-crypto-keys/default"] [dependencies] @@ -22,7 +22,7 @@ hex_fmt = "0.3" hkdf = "0.12.3" prost = { version = "0.11", optional = true, default-features = false, features = ["prost-derive"] } rand_core = { version = "0.6", default-features = false } -serde = { version = "1.0", default-features = false } +serde = { version = "1.0", optional = true, default-features = false, features = [ "alloc", "derive" ] } subtle = { version = "2", default-features = false } zeroize = { version = "1", default-features = false } diff --git a/account-keys/src/account_keys.rs b/account-keys/src/account_keys.rs index c86cf7550b..521bc982c0 100644 --- a/account-keys/src/account_keys.rs +++ b/account-keys/src/account_keys.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022 The MobileCoin Foundation +// Copyright (c) 2018-2023 The MobileCoin Foundation //! MobileCoin account keys. //! @@ -37,6 +37,7 @@ use mc_util_from_random::FromRandom; #[cfg(feature = "prost")] use prost::Message; use rand_core::{CryptoRng, RngCore}; +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use zeroize::Zeroize; @@ -49,10 +50,9 @@ pub use mc_core::{ }; /// A MobileCoin user's public subaddress. -#[derive( - Clone, Deserialize, Digestible, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Zeroize, -)] +#[derive(Clone, Digestible, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize)] #[cfg_attr(feature = "prost", derive(Message))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PublicAddress { /// The user's public subaddress view key 'C'. #[cfg_attr(feature = "prost", prost(message, required, tag = "1"))] diff --git a/transaction/extra/tests/verifier.rs b/transaction/extra/tests/verifier.rs index 327ba6d5e7..439f6e382a 100644 --- a/transaction/extra/tests/verifier.rs +++ b/transaction/extra/tests/verifier.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022 The MobileCoin Foundation +// Copyright (c) 2018-2023 The MobileCoin Foundation //! Tests of the streaming verifier @@ -204,6 +204,7 @@ fn test_max_size_tx_summary_verification() { &tx_summary, &tx_summary_unblinding_data, *sender.view_private_key(), + sender.change_subaddress(), ) .unwrap(); assert_eq!( @@ -212,17 +213,16 @@ fn test_max_size_tx_summary_verification() { ); let recipient_hash = ShortAddressHash::from(&recipient.default_subaddress()); - let balance_changes: Vec<_> = report.balance_changes.iter().collect(); assert_eq!( - balance_changes, - vec![ - (&(TransactionEntity::Ourself, TokenId::from(0)), &-16000), - ( - &(TransactionEntity::Address(recipient_hash), TokenId::from(0)), - &160 - ) - ] + &report.outputs, + &[( + TransactionEntity::OtherAddress(recipient_hash), + TokenId::from(0), + 160 + )] ); + assert_eq!(&report.totals, &[(TokenId::from(0), 16000),]); + assert_eq!(report.network_fee, Amount::new(15840, TokenId::from(0))); } @@ -239,6 +239,7 @@ fn test_min_size_tx_summary_verification() { &tx_summary, &tx_summary_unblinding_data, *sender.view_private_key(), + sender.change_subaddress(), ) .unwrap(); assert_eq!( @@ -247,17 +248,15 @@ fn test_min_size_tx_summary_verification() { ); let recipient_hash = ShortAddressHash::from(&recipient.default_subaddress()); - let balance_changes: Vec<_> = report.balance_changes.iter().collect(); assert_eq!( - balance_changes, - vec![ - (&(TransactionEntity::Ourself, TokenId::from(0)), &-1000), - ( - &(TransactionEntity::Address(recipient_hash), TokenId::from(0)), - &10 - ) - ] + &report.outputs, + &[( + TransactionEntity::OtherAddress(recipient_hash), + TokenId::from(0), + 10 + )] ); + assert_eq!(&report.totals, &[(TokenId::from(0), 1000),]); assert_eq!(report.network_fee, Amount::new(990, TokenId::from(0))); } @@ -333,6 +332,7 @@ fn test_two_input_tx_with_change_tx_summary_verification() { &tx_summary, &tx_summary_unblinding_data, *sender.view_private_key(), + sender.change_subaddress(), ) .unwrap(); assert_eq!( @@ -341,23 +341,18 @@ fn test_two_input_tx_with_change_tx_summary_verification() { ); let recipient_hash = ShortAddressHash::from(&recipient.default_subaddress()); - let balance_changes: Vec<_> = report - .balance_changes - .iter() - .map(|(x, y)| (x.clone(), *y)) - .collect(); - let expected = vec![ - ( - (TransactionEntity::Ourself, token_id), - -((value + value2 - change_value) as i64), - ), - ( - (TransactionEntity::Address(recipient_hash), token_id), - (value + value2 - change_value - Mob::MINIMUM_FEE) as i64, - ), - ]; - - assert_eq!(balance_changes, expected); + assert_eq!( + &report.totals, + &[(token_id, (value + value2 - change_value) as i64),] + ); + assert_eq!( + &report.outputs, + &[( + TransactionEntity::OtherAddress(recipient_hash), + token_id, + (value + value2 - change_value - Mob::MINIMUM_FEE) + ),] + ); assert_eq!(report.network_fee, Amount::new(Mob::MINIMUM_FEE, token_id)); } } @@ -425,6 +420,7 @@ fn test_simple_tx_with_change_tx_summary_verification() { &tx_summary, &tx_summary_unblinding_data, *sender.view_private_key(), + sender.change_subaddress(), ) .unwrap(); assert_eq!( @@ -433,23 +429,18 @@ fn test_simple_tx_with_change_tx_summary_verification() { ); let recipient_hash = ShortAddressHash::from(&recipient.default_subaddress()); - let balance_changes: Vec<_> = report - .balance_changes - .iter() - .map(|(x, y)| (x.clone(), *y)) - .collect(); - let expected = vec![ - ( - (TransactionEntity::Ourself, token_id), - -((value - change_value) as i64), - ), - ( - (TransactionEntity::Address(recipient_hash), token_id), - (value - change_value - Mob::MINIMUM_FEE) as i64, - ), - ]; - - assert_eq!(balance_changes, expected); + assert_eq!( + &report.totals, + &[(token_id, ((value - change_value) as i64)),] + ); + assert_eq!( + &report.outputs, + &[( + TransactionEntity::OtherAddress(recipient_hash), + token_id, + (value - change_value - Mob::MINIMUM_FEE) + ),] + ); assert_eq!(report.network_fee, Amount::new(Mob::MINIMUM_FEE, token_id)); } } @@ -520,6 +511,7 @@ fn test_two_output_tx_with_change_tx_summary_verification() { &tx_summary, &tx_summary_unblinding_data, *sender.view_private_key(), + sender.change_subaddress(), ) .unwrap(); assert_eq!( @@ -529,28 +521,24 @@ fn test_two_output_tx_with_change_tx_summary_verification() { let recipient_hash = ShortAddressHash::from(&recipient.default_subaddress()); let recipient2_hash = ShortAddressHash::from(&recipient2.default_subaddress()); - let balance_changes: Vec<_> = report - .balance_changes - .iter() - .map(|(x, y)| (x.clone(), *y)) - .collect(); - let mut expected = vec![ - ( - (TransactionEntity::Ourself, token_id), - -((value + value2 + Mob::MINIMUM_FEE) as i64), - ), + assert_eq!( + &report.totals, + &[(token_id, (value + value2 + Mob::MINIMUM_FEE) as i64),] + ); + let mut outputs = vec![ ( - (TransactionEntity::Address(recipient_hash), token_id), - (value as i64), + TransactionEntity::OtherAddress(recipient_hash), + token_id, + value, ), ( - (TransactionEntity::Address(recipient2_hash), token_id), - (value2 as i64), + TransactionEntity::OtherAddress(recipient2_hash), + token_id, + value2, ), ]; - expected.sort(); - - assert_eq!(balance_changes, expected); + outputs.sort(); + assert_eq!(&report.outputs[..], &outputs[..]); assert_eq!(report.network_fee, Amount::new(Mob::MINIMUM_FEE, token_id)); } } @@ -637,6 +625,7 @@ fn test_sci_tx_summary_verification() { &mut rng, ) .unwrap(); + let bob_hash = ShortAddressHash::from(&bob.default_subaddress()); let unsigned_tx = builder .build_unsigned::() @@ -650,6 +639,7 @@ fn test_sci_tx_summary_verification() { &tx_summary, &tx_summary_unblinding_data, *bob.view_private_key(), + bob.change_subaddress(), ) .unwrap(); assert_eq!( @@ -657,23 +647,27 @@ fn test_sci_tx_summary_verification() { &signing_data.mlsag_signing_digest[..] ); - let balance_changes: Vec<_> = report - .balance_changes - .iter() - .map(|(x, y)| (x.clone(), *y)) - .collect(); - let mut expected = vec![ + // TODO: fix this test + assert_eq!( + &report.totals, + &[ + // Bob spends 3x worth of token id 2 in the transaction + (token2, value2 as i64), + ] + ); + let mut outputs = vec![ + // Output to swap counterparty + (TransactionEntity::Swap, token2, value2), + // Converted output to ourself ( - (TransactionEntity::Ourself, Mob::ID), - ((value - Mob::MINIMUM_FEE) as i64), + TransactionEntity::OurAddress(bob_hash), + Mob::ID, + value - Mob::MINIMUM_FEE, ), - ((TransactionEntity::Ourself, token2), -(value2 as i64)), - ((TransactionEntity::Swap, Mob::ID), -(value as i64)), - ((TransactionEntity::Swap, token2), (value2 as i64)), ]; - expected.sort(); + outputs.sort(); + assert_eq!(&report.outputs[..], &outputs[..]); - assert_eq!(balance_changes, expected); assert_eq!(report.network_fee, Amount::new(Mob::MINIMUM_FEE, Mob::ID)); } @@ -773,6 +767,7 @@ fn test_sci_three_way_tx_summary_verification() { &tx_summary, &tx_summary_unblinding_data, *bob.view_private_key(), + bob.change_subaddress(), ) .unwrap(); assert_eq!( @@ -780,24 +775,27 @@ fn test_sci_three_way_tx_summary_verification() { &signing_data.mlsag_signing_digest[..] ); - let balance_changes: Vec<_> = report - .balance_changes - .iter() - .map(|(x, y)| (x.clone(), *y)) - .collect(); - let charlie_hash = ShortAddressHash::from(&charlie.default_subaddress()); - let mut expected = vec![ - ((TransactionEntity::Ourself, token2), -(value2 as i64)), + + assert_eq!( + &report.totals, + &[ + // Bob's spend to create the transaction + (token2, value2 as i64), + ] + ); + let mut outputs = vec![ + // Converted output to charlie, - fee paid from Mob input ( - (TransactionEntity::Address(charlie_hash), Mob::ID), - ((value - Mob::MINIMUM_FEE) as i64), + TransactionEntity::OtherAddress(charlie_hash), + Mob::ID, + (value - Mob::MINIMUM_FEE), ), - ((TransactionEntity::Swap, Mob::ID), -(value as i64)), - ((TransactionEntity::Swap, token2), (value2 as i64)), + // Output to swap counterparty + (TransactionEntity::Swap, token2, value2), ]; - expected.sort(); + outputs.sort(); + assert_eq!(&report.outputs[..], &outputs[..]); - assert_eq!(balance_changes, expected); assert_eq!(report.network_fee, Amount::new(Mob::MINIMUM_FEE, Mob::ID)); } diff --git a/transaction/summary/Cargo.toml b/transaction/summary/Cargo.toml index b8da40410c..72e022e561 100644 --- a/transaction/summary/Cargo.toml +++ b/transaction/summary/Cargo.toml @@ -27,6 +27,7 @@ default = ["std", "serde", "prost", "mc-account-keys"] [dependencies] # External dependencies displaydoc = { version = "0.2", default-features = false } +heapless = { version = "0.7.16", default-features = false } # MobileCoin dependencies mc-account-keys = { path = "../../account-keys", optional = true, default-features = false } @@ -42,3 +43,8 @@ prost = { version = "0.11", optional = true, default-features = false, features serde = { version = "1.0", optional = true, default-features = false, features = ["derive"] } subtle = { version = "2.4.1", default-features = false, features = ["i128"] } zeroize = { version = "1", default-features = false } + +[dev-dependencies] +mc-transaction-core = { path = "../core", default-features = false, features = [] } +mc-util-from-random = { path = "../../util/from-random", default-features = false } +rand = "0.8.5" diff --git a/transaction/summary/src/data.rs b/transaction/summary/src/data.rs index 04c569af64..ac3a55a27a 100644 --- a/transaction/summary/src/data.rs +++ b/transaction/summary/src/data.rs @@ -3,9 +3,9 @@ use alloc::vec::Vec; use super::{Error, TxSummaryUnblindingReport}; -use crate::TxSummaryStreamingVerifierCtx; +use crate::{report::TransactionReport, TxSummaryStreamingVerifierCtx}; use mc_account_keys::PublicAddress; -use mc_core::account::ShortAddressHash; +use mc_core::account::{PublicSubaddress, RingCtAddress, ShortAddressHash}; use mc_crypto_digestible::Digestible; use mc_crypto_keys::RistrettoPrivate; use mc_transaction_types::{Amount, TxSummary, UnmaskedAmount}; @@ -76,6 +76,7 @@ pub fn verify_tx_summary( tx_summary: &TxSummary, unblinding_data: &TxSummaryUnblindingData, view_private_key: RistrettoPrivate, + change_address: impl RingCtAddress, ) -> Result<([u8; 32], TxSummaryUnblindingReport), Error> { let mut verifier = TxSummaryStreamingVerifierCtx::new( extended_message_digest, @@ -83,6 +84,10 @@ pub fn verify_tx_summary( tx_summary.outputs.len(), tx_summary.inputs.len(), view_private_key, + PublicSubaddress { + view_public: change_address.view_public_key(), + spend_public: change_address.spend_public_key(), + }, ); let mut report = TxSummaryUnblindingReport::default(); @@ -116,7 +121,9 @@ pub fn verify_tx_summary( tx_summary.tombstone_block, &mut digest, &mut report, - ); + )?; + + report.finalize()?; // In a debug build, confirm the digest by computing it in a non-streaming way // diff --git a/transaction/summary/src/lib.rs b/transaction/summary/src/lib.rs index cf6de2c0ba..f035f94f16 100644 --- a/transaction/summary/src/lib.rs +++ b/transaction/summary/src/lib.rs @@ -2,7 +2,7 @@ // Copyright (c) 2018-2022 The MobileCoin Foundation -#![no_std] +#![cfg_attr(not(feature = "std"), no_std)] #![doc = include_str!("../README.md")] #![deny(missing_docs)] diff --git a/transaction/summary/src/report.rs b/transaction/summary/src/report.rs index c81d915864..402e9361cd 100644 --- a/transaction/summary/src/report.rs +++ b/transaction/summary/src/report.rs @@ -3,90 +3,269 @@ //! A TxSummaryUnblindingReport, containing the set of verified information //! about a transaction. -use super::Error; use core::fmt::Display; + use displaydoc::Display; +use heapless::Vec; use mc_core::account::ShortAddressHash; use mc_transaction_types::{ constants::{MAX_INPUTS, MAX_OUTPUTS}, Amount, TokenId, }; -use mc_util_vec_map::VecMap; + +use super::Error; /// An entity with whom a transaction can interact, and who can be identified /// by the TxSummary verification process #[derive(Clone, Debug, Display, Eq, Ord, PartialEq, PartialOrd)] pub enum TransactionEntity { - /// Self - Ourself, - /// Address hash {0} - Address(ShortAddressHash), + /// Outputs to a non-change address that we control (hash {0}) + OurAddress(ShortAddressHash), + + /// Outputs to other accounts (hash {0}) + OtherAddress(ShortAddressHash), + /// Swap counterparty Swap, } +/// Generic transaction report interface +// (There is at this time only one report implementation, however, this trait +// is particularly useful for eliding generics when using this and is expected +// to be helpful when building support for account info caching.) +pub trait TransactionReport { + /// Add value to the transaction running transaction totals + fn total_add(&mut self, amount: Amount) -> Result<(), Error>; + + /// Add matched change outputs to the report, these are subtracted from the + /// transaction totals + fn change_add(&mut self, amount: Amount) -> Result<(), Error>; + + /// Add output value for a particular entity / address to the report + fn output_add(&mut self, entity: TransactionEntity, amount: Amount) -> Result<(), Error>; + + /// Set the network fee + fn network_fee_set(&mut self, amount: Amount) -> Result<(), Error>; + + /// Set the tombstone block + fn tombstone_block_set(&mut self, value: u64) -> Result<(), Error>; + + /// Finalise the report, checking balances and sorting report entries + fn finalize(&mut self) -> Result<(), Error>; +} + +/// [TransactionReport] impl for `&mut T` where `T: TransactionReport` +impl TransactionReport for &mut T { + fn total_add(&mut self, amount: Amount) -> Result<(), Error> { + ::total_add(self, amount) + } + + fn change_add(&mut self, amount: Amount) -> Result<(), Error> { + ::change_add(self, amount) + } + + fn output_add(&mut self, entity: TransactionEntity, amount: Amount) -> Result<(), Error> { + ::output_add(self, entity, amount) + } + + fn network_fee_set(&mut self, amount: Amount) -> Result<(), Error> { + ::network_fee_set(self, amount) + } + + fn tombstone_block_set(&mut self, value: u64) -> Result<(), Error> { + ::tombstone_block_set(self, value) + } + + fn finalize(&mut self) -> Result<(), Error> { + ::finalize(self) + } +} + +/// Compute maximum number of outputs and inputs to be supported by a report pub const MAX_RECORDS: usize = MAX_OUTPUTS as usize + MAX_INPUTS as usize; -/// A report of the parties and balance changes due to a transaction. -/// This can be produced for a given TxSummary and TxSummaryUnblindingData. +/// Maximum number of currencies with totals supported in a single report. +/// +/// It is expected that _most_ transactions will contain one total, however, +/// this should be large enough to support SCIs with other token types +pub const MAX_TOTALS: usize = 4; + +/// A report of the parties and balance changes due to a transaction, +/// produced for a given TxSummary and TxSummaryUnblindingData. +/// +/// This uses a double-entry approach where outputs and totals should be +/// balanced. For each token, totals = our inputs - sum(change outputs) == +/// sum(other outputs) + fee #[derive(Clone, Debug, Default)] -pub struct TxSummaryUnblindingReport { - /// The set of balance changes that we have observed - // Note: We can save about 210 bytes on the stack if we store TokenId as - // a [u8; 8] to avoid alignment requirements. TBD if that's worth it. - pub balance_changes: VecMap<(TransactionEntity, TokenId), i64, RECORDS>, - /// The network fee that we pay +pub struct TxSummaryUnblindingReport< + const RECORDS: usize = MAX_RECORDS, + const TOTALS: usize = MAX_TOTALS, +> { + /// Transaction outputs aggregated by address and token type + pub outputs: Vec<(TransactionEntity, TokenId, u64), RECORDS>, + + /// Total balance change for our account for each type of token in the + /// transaction. + /// + /// totals = our inputs - sum(change outputs) + /// + /// Note that swap inputs are elided as these are not inputs + /// owned by us (ie. are not spent from our account) + pub totals: Vec<(TokenId, i64), TOTALS>, + + /// The network fee that we pay to execute the transaction pub network_fee: Amount, + /// The tombstone block associated to this transaction pub tombstone_block: u64, } -impl TxSummaryUnblindingReport { - /// Add value to the balance report, for some entity - pub fn balance_add( - &mut self, - entity: TransactionEntity, - token_id: TokenId, - value: u64, - ) -> Result<(), Error> { +impl TransactionReport + for TxSummaryUnblindingReport +{ + /// Add input, added to the transaction total + fn total_add(&mut self, amount: Amount) -> Result<(), Error> { + let Amount { token_id, value } = amount; + + // Ensure value will not overflow let value = i64::try_from(value).map_err(|_| Error::NumericOverflow)?; - let stored = self - .balance_changes - .get_mut_or_insert_with(&(entity, token_id), || 0) - .map_err(|_| Error::BufferOverflow)?; - *stored = stored.checked_add(value).ok_or(Error::NumericOverflow)?; + + // Check for existing total entry for this token + match self.totals.iter_mut().find(|(t, _)| t == &token_id) { + // If we have an entry, add the value to this + Some(v) => v.1 = v.1.checked_add(value).ok_or(Error::NumericOverflow)?, + // If we do not, create a new entry + None => self + .totals + .push((token_id, value)) + .map_err(|_| Error::BufferOverflow)?, + } + Ok(()) } - /// Subtract value from the balance report, for some entity - pub fn balance_subtract( - &mut self, - entity: TransactionEntity, - token_id: TokenId, - value: u64, - ) -> Result<(), Error> { + /// Add change output, subtracted from the transaction total + fn change_add(&mut self, amount: Amount) -> Result<(), Error> { + let Amount { token_id, value } = amount; + + // Ensure value will not overflow let value = i64::try_from(value).map_err(|_| Error::NumericOverflow)?; - let stored = self - .balance_changes - .get_mut_or_insert_with(&(entity, token_id), || 0) - .map_err(|_| Error::BufferOverflow)?; - *stored = stored.checked_sub(value).ok_or(Error::NumericOverflow)?; + + // Check for existing total entry for this token + match self.totals.iter_mut().find(|(t, _)| t == &token_id) { + // If we have an entry, subtract the change value from this + Some(v) => v.1 = v.1.checked_sub(value).ok_or(Error::NumericOverflow)?, + // If we do not, create a new entry + None => self + .totals + .push((token_id, -value)) + .map_err(|_| Error::BufferOverflow)?, + } + + Ok(()) + } + + /// Add output value to a particular entity / address to the report + fn output_add(&mut self, entity: TransactionEntity, amount: Amount) -> Result<(), Error> { + let Amount { token_id, value } = amount; + + // Check for existing output for this address + match self + .outputs + .iter_mut() + .find(|(e, t, _)| t == &token_id && e == &entity) + { + // If we have an entry, subtract the change value from this + Some((_, _, v)) => *v = v.checked_add(value).ok_or(Error::NumericOverflow)?, + // If we do not, create a new entry + None => self + .outputs + .push((entity, token_id, value)) + .map_err(|_| Error::BufferOverflow)?, + } + Ok(()) } - /// This should be done before displaying the report + /// Add network fee to the report + fn network_fee_set(&mut self, amount: Amount) -> Result<(), Error> { + // Set fee value + self.network_fee = amount; + + Ok(()) + } + + /// Set tombstone block in the report + fn tombstone_block_set(&mut self, value: u64) -> Result<(), Error> { + self.tombstone_block = value; + Ok(()) + } + + /// Finalise report, checking totals and sorting report entries + fn finalize(&mut self) -> Result<(), Error> { + // Sort outputs and totals + self.sort(); + + // For each token id, check that inputs match outputs + for (token_id, value) in &self.totals { + // Sum outputs for this token id + let mut balance = 0u64; + for (_e, id, v) in &self.outputs { + if id == token_id { + balance = balance.checked_add(*v).ok_or(Error::NumericOverflow)?; + } + } + + // Add network fee for matching token id + if &self.network_fee.token_id == token_id { + balance = balance + .checked_add(self.network_fee.value) + .ok_or(Error::NumericOverflow)?; + } + + // Check that the balance matches the total + if balance != *value as u64 { + return Err(Error::AmountVerificationFailed); + } + } + + Ok(()) + } +} + +impl TxSummaryUnblindingReport { + /// Create a new report instance + pub fn new() -> Self { + Self { + outputs: Vec::new(), + totals: Vec::new(), + network_fee: Default::default(), + tombstone_block: 0, + } + } + + /// Sort balance changes and totals + /// + /// This should be called prior to displaying the report. pub fn sort(&mut self) { - self.balance_changes.sort(); + // TODO: should we remove zeroed balances / totals? + + (&mut self.outputs[..]).sort_by_key(|(_, t, _)| *t); + (&mut self.outputs[..]).sort_by_key(|(e, _, _)| e.clone()); + + (&mut self.totals[..]).sort_by_key(|(t, _)| *t); } } // This is a proof-of-concept, it doesn't map token id's to their symbol when // displaying. -impl Display for TxSummaryUnblindingReport { +impl Display + for TxSummaryUnblindingReport +{ fn fmt(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { let mut current_entity = None; - for ((entity, tok), val) in self.balance_changes.iter() { + for (entity, tok, val) in self.outputs.iter() { if Some(entity) != current_entity.as_ref() { writeln!(formatter, "{entity}:")?; current_entity = Some(entity.clone()); @@ -104,9 +283,85 @@ impl Display for TxSummaryUnblindingReport { #[cfg(test)] mod tests { + use rand::random; + use super::*; + #[test] - fn test_report_size_size() { - assert_eq!(core::mem::size_of::(), 1320); + fn test_report_size() { + assert_eq!(core::mem::size_of::(), 1384); + } + + #[test] + fn test_report_totals() { + let mut report = TxSummaryUnblindingReport::<16>::new(); + + let amounts = [ + Amount::new(50, TokenId::from(1)), + Amount::new(50, TokenId::from(1)), + Amount::new(100, TokenId::from(2)), + Amount::new(200, TokenId::from(2)), + ]; + + for a in amounts { + report.total_add(a).unwrap(); + } + + // Check total inputs + report.sort(); + assert_eq!( + &report.totals[..], + &[(TokenId::from(1), 100), (TokenId::from(2), 300)] + ); + + // Subtract change amounts + report + .change_add(Amount::new(25, TokenId::from(1))) + .unwrap(); + report + .change_add(Amount::new(50, TokenId::from(2))) + .unwrap(); + + // Check total inputs - change + assert_eq!( + &report.totals[..], + &[(TokenId::from(1), 75), (TokenId::from(2), 250)] + ); + } + + #[test] + fn test_report_balances() { + let mut report = TxSummaryUnblindingReport::<16>::new(); + + // Setup random addresses, sorted so these match the report entry order + let mut addrs = [ + TransactionEntity::OtherAddress(ShortAddressHash::from(random::<[u8; 16]>())), + TransactionEntity::OtherAddress(ShortAddressHash::from(random::<[u8; 16]>())), + ]; + addrs.sort(); + + let amounts = [ + (addrs[0].clone(), Amount::new(50, TokenId::from(1))), + (addrs[0].clone(), Amount::new(50, TokenId::from(1))), + (addrs[0].clone(), Amount::new(80, TokenId::from(2))), + (addrs[1].clone(), Amount::new(120, TokenId::from(2))), + (TransactionEntity::Swap, Amount::new(200, TokenId::from(2))), + ]; + + for (e, a) in amounts { + report.output_add(e, a).unwrap(); + } + + // Check total outputs + report.sort(); + assert_eq!( + &report.outputs[..], + &[ + (addrs[0].clone(), TokenId::from(1), 100), + (addrs[0].clone(), TokenId::from(2), 80), + (addrs[1].clone(), TokenId::from(2), 120), + (TransactionEntity::Swap, TokenId::from(2), 200), + ] + ); } } diff --git a/transaction/summary/src/verifier.rs b/transaction/summary/src/verifier.rs index 2c028f03bb..17810070a4 100644 --- a/transaction/summary/src/verifier.rs +++ b/transaction/summary/src/verifier.rs @@ -10,8 +10,10 @@ //! To take the largest "step" (verifying an output) requires //! approximately 300 bytes + Fog url length -use super::{Error, TransactionEntity, TxSummaryUnblindingReport}; -use mc_core::account::{RingCtAddress, ShortAddressHash}; +use crate::report::TransactionReport; + +use super::{Error, TransactionEntity}; +use mc_core::account::{PublicSubaddress, RingCtAddress, ShortAddressHash}; use mc_crypto_digestible::{DigestTranscript, Digestible, MerlinTranscript}; use mc_crypto_keys::{RistrettoPrivate, RistrettoPublic}; use mc_crypto_ring_signature::{ @@ -51,6 +53,10 @@ pub struct TxSummaryStreamingVerifierCtx { // The account view private key of the transaction signer. // This is used to identify outputs addressed to ourselves regardless of subaddress view_private_key: RistrettoPrivate, + + // The account change address for matching outputs + change_address: PublicSubaddress, + // The block version that this transaction is targetting block_version: BlockVersion, // The merlin transcript which we maintain in order to produce the digest @@ -87,6 +93,7 @@ impl TxSummaryStreamingVerifierCtx { expected_num_outputs: usize, expected_num_inputs: usize, view_private_key: RistrettoPrivate, + change_address: PublicSubaddress, ) -> Self { let mut transcript = MerlinTranscript::new(EXTENDED_MESSAGE_AND_TX_SUMMARY_DOMAIN_TAG.as_bytes()); @@ -105,18 +112,19 @@ impl TxSummaryStreamingVerifierCtx { expected_num_inputs, output_count: 0, input_count: 0, + change_address, } } /// Stream the next TxOutSummary and matching unblinding data to the /// streaming verifier, which will verify and then digest it. - pub fn digest_output( + pub fn digest_output( &mut self, tx_out_summary: &TxOutSummary, unmasked_amount: &UnmaskedAmount, address: Option<(ShortAddressHash, impl RingCtAddress)>, tx_private_key: Option<&RistrettoPrivate>, - report: &mut TxSummaryUnblindingReport, + mut report: impl TransactionReport, ) -> Result<(), Error> { if self.output_count >= self.expected_num_outputs { return Err(Error::UnexpectedOutput); @@ -124,10 +132,33 @@ impl TxSummaryStreamingVerifierCtx { // Now try to verify the recipient. This is either ourselves, or someone else // with the listed address, or this is associated to an SCI. + + // If we view-key matched the output, then it belongs to one of our subaddresses if let Some(amount) = self.view_key_match(tx_out_summary)? { - // If we view-key matched the output, then it belongs to one of our subaddresses - report.balance_add(TransactionEntity::Ourself, amount.token_id, amount.value)?; + // If we have address information + if let Some((address_hash, address)) = address.as_ref() { + // Check whether this is to our change address + if address.view_public_key() == self.change_address.view_public_key() + && address.spend_public_key() == self.change_address.spend_public_key() + { + // If this is to our change address, subtract this from the total inputs + report.change_add(amount)?; + } else { + // Otherwise, add this as an output to ourself + report + .output_add(TransactionEntity::OurAddress(address_hash.clone()), amount)?; + } + } else { + // TODO: If we _don't_ have address information but it's to our own address... + // what then? is this even possible??! + panic!("what's the right thing to do here..?"); + } + + // If we didn't match the output, and we have address information, this + // belongs to someone else } else if let Some((address_hash, address)) = address.as_ref() { + // Otherwise, this belongs to another address + let amount = Amount::new(unmasked_amount.value, unmasked_amount.token_id.into()); // In this case, we are given the address of who is supposed to have received // this. @@ -136,14 +167,17 @@ impl TxSummaryStreamingVerifierCtx { let expected = Self::expected_tx_out_summary(self.block_version, amount, address, tx_private_key)?; if &expected == tx_out_summary { - report.balance_add( - TransactionEntity::Address(address_hash.clone()), - amount.token_id, - amount.value, + // Add as an output to the report + report.output_add( + TransactionEntity::OtherAddress(address_hash.clone()), + amount, )?; } else { return Err(Error::AddressVerificationFailed); } + + // If we didn't match the output, and we don't have address information, + // this is an SCI } else { if !tx_out_summary.associated_to_input_rules { return Err(Error::MissingDataRequiredToVerifyTxOutRecipient); @@ -165,7 +199,12 @@ impl TxSummaryStreamingVerifierCtx { { return Err(Error::AmountVerificationFailed); } - report.balance_add(TransactionEntity::Swap, token_id.into(), value)?; + + // Add swap output to report + // NOTE: this is not exercised as swap rings are created without a transaction + // ... does this mean the ocurrence of a swap output is invalid / doesn't need + // to be handled here? + report.output_add(TransactionEntity::Swap, unmasked_amount.into())?; } // We've now verified the tx_out_summary and added it to the report. @@ -186,11 +225,11 @@ impl TxSummaryStreamingVerifierCtx { /// Stream the next TxInSummary and matching unblinding data to the /// streaming verifier, which will verify and then digest it. - pub fn digest_input( + pub fn digest_input( &mut self, tx_in_summary: &TxInSummary, tx_in_summary_unblinding_data: &UnmaskedAmount, - report: &mut TxSummaryUnblindingReport, + mut report: impl TransactionReport, ) -> Result<(), Error> { if self.output_count != self.expected_num_outputs { return Err(Error::StillExpectingMoreOutputs); @@ -211,14 +250,15 @@ impl TxSummaryStreamingVerifierCtx { } // Now understand whose input this is. There are two cases - let entity = if tx_in_summary.input_rules_digest.is_empty() { - TransactionEntity::Ourself + if tx_in_summary.input_rules_digest.is_empty() { + // If we have no input rules digest, then this is a normal input + // add this to the report total + report.total_add(tx_in_summary_unblinding_data.into())?; } else { - TransactionEntity::Swap + // If we have input rules this is an SCI input and does not impact + // our balance }; - report.balance_subtract(entity, token_id.into(), value)?; - // We've now verified the tx_in_summary and added it to the report. // Now we need to add it to the digest // (See mc-crypto-digestible sources for details around b"") @@ -239,16 +279,15 @@ impl TxSummaryStreamingVerifierCtx { /// * extended-message-and-tx-summary digest /// * TxSummaryUnblindingReport, which details all balance changes for all /// parties to this Tx. - pub fn finalize( + pub fn finalize( mut self, fee: Amount, tombstone_block: u64, digest: &mut [u8; 32], - report: &mut TxSummaryUnblindingReport, - ) { - report.network_fee = fee; - report.tombstone_block = tombstone_block; - report.sort(); + mut report: impl TransactionReport, + ) -> Result<(), Error> { + report.network_fee_set(fee)?; + report.tombstone_block_set(tombstone_block)?; fee.value.append_to_transcript(b"fee", &mut self.transcript); (*fee.token_id).append_to_transcript(b"fee_token_id", &mut self.transcript); @@ -260,6 +299,8 @@ impl TxSummaryStreamingVerifierCtx { // Extract the digest self.transcript.extract_digest(digest); + + Ok(()) } // Internal: Check if TxOutSummary matches to our view private key @@ -321,17 +362,328 @@ impl TxSummaryStreamingVerifierCtx { mod tests { use super::*; + use alloc::{vec, vec::Vec}; + + use rand::rngs::OsRng; + + use crate::TxSummaryUnblindingReport; + use mc_account_keys::AccountKey; + use mc_transaction_core::{tx::TxOut, BlockVersion}; + use mc_transaction_types::TokenId; + use mc_util_from_random::FromRandom; + // Test the size of the streaming verifier on the stack. This is using heapless. #[test] fn test_streaming_verifier_size() { let s = core::mem::size_of::(); assert!( - s < 512, + s < 1024, "TxSummaryStreamingVerifierCtx exceeds size thresold {}/{}", s, - 512 + 1024 ); } - // Note: Most tests are in transaction/extra/tests to avoid build issues. + #[derive(Clone, Debug, PartialEq)] + struct TxOutReportTest { + /// Inputs spent in the transaction + inputs: Vec<(InputType, Amount)>, + /// Outputs produced by the transaction + outputs: Vec<(OutputTarget, Amount)>, + /// Totals / balances by token + totals: Vec<(TokenId, i64)>, + /// Changes produced by the transaction + changes: Vec<(TransactionEntity, TokenId, u64)>, + } + + #[derive(Clone, Debug, PartialEq)] + #[allow(dead_code)] + enum InputType { + /// An input we own, reducing our balance + Owned, + /// A SCI / SWAP input from another account + Sci, + } + + #[derive(Clone, Debug, PartialEq)] + #[allow(dead_code)] + enum OutputTarget { + /// An output to ourself (_not_ a change address) + Ourself, + /// An output to our change address + Change, + /// An output to a third party + Other, + /// A swap output (not used in existing reports) + Swap, + } + + #[test] + fn test_report_outputs() { + let mut rng = OsRng {}; + + // Setup accounts for test report + let sender = AccountKey::random(&mut rng); + let receiver = AccountKey::random(&mut rng); + let swap = AccountKey::random(&mut rng); + + let sender_subaddress = sender.default_subaddress(); + let change_subaddress = sender.change_subaddress(); + let target_subaddress = receiver.default_subaddress(); + let swap_subaddress = swap.default_subaddress(); + + // Set common token id / amounts for later use + let token_id = TokenId::from(9); + let amount = Amount::new(103_000, token_id); + let fee = 4000; + + // Setup tests + let tests = &[ + // Output to ourself, should show output to our address and total of output + fee + TxOutReportTest { + inputs: vec![(InputType::Owned, Amount::new(amount.value + fee, token_id))], + outputs: vec![(OutputTarget::Ourself, amount.clone())], + changes: vec![( + TransactionEntity::OurAddress(ShortAddressHash::from(&sender_subaddress)), + token_id, + amount.value, + )], + totals: vec![(token_id, (amount.value + fee) as i64)], + }, + // Output to our change address, should show no outputs with balance change = fee + TxOutReportTest { + inputs: vec![ + ( + InputType::Owned, + Amount::new(amount.value / 2 + fee, token_id), + ), + (InputType::Owned, Amount::new(amount.value / 2, token_id)), + ], + outputs: vec![(OutputTarget::Change, amount.clone())], + changes: vec![ + //(TransactionEntity::Total, token_id, 0), + ], + totals: vec![(token_id, fee as i64)], + }, + // Output to someone else, should show their address and total of output + fee + TxOutReportTest { + inputs: vec![(InputType::Owned, Amount::new(amount.value + fee, token_id))], + outputs: vec![(OutputTarget::Other, amount.clone())], + changes: vec![( + TransactionEntity::OtherAddress(ShortAddressHash::from(&target_subaddress)), + token_id, + amount.value, + )], + totals: vec![(token_id, (amount.value + fee) as i64)], + }, + // Basic SCI. consuming entire swap, inputs should not count towards totals + TxOutReportTest { + inputs: vec![ + // Our input, sent to SCI + (InputType::Owned, Amount::new(10_000 + fee, token_id)), + // SCI input, sent to us + (InputType::Sci, Amount::new(200, TokenId::from(2))), + ], + outputs: vec![ + // We send the converted token to ourself + (OutputTarget::Ourself, Amount::new(200, TokenId::from(2))), + // While fulfilling the requirements of the SCI + (OutputTarget::Swap, Amount::new(10_000, token_id)), + ], + changes: vec![ + ( + TransactionEntity::OurAddress(ShortAddressHash::from(&sender_subaddress)), + TokenId::from(2), + 200, + ), + (TransactionEntity::Swap, token_id, 10_000), + ], + totals: vec![ + // The total is the change to _our_ balance spent during the transaction + (token_id, (10_000 + fee) as i64), + ], + }, + // Partial SCI + TxOutReportTest { + inputs: vec![ + // Our input, owned by us + (InputType::Owned, Amount::new(7_500 + fee, token_id)), + // SCI input, owned by counterparty + (InputType::Sci, Amount::new(200, TokenId::from(2))), + ], + outputs: vec![ + // We send part of the converted token to ourself + (OutputTarget::Ourself, Amount::new(150, TokenId::from(2))), + // Returning the remaining portion to the swap counterparty + (OutputTarget::Swap, Amount::new(50, TokenId::from(2))), + // While fulfilling the requirements of the SCI + (OutputTarget::Swap, Amount::new(7_500, token_id)), + ], + changes: vec![ + ( + TransactionEntity::OurAddress(ShortAddressHash::from(&sender_subaddress)), + TokenId::from(2), + 150, + ), + (TransactionEntity::Swap, TokenId::from(2), 50), + (TransactionEntity::Swap, token_id, 7_500), + ], + totals: vec![ + // The total is the change to _our_ balance spent during the transaction + (token_id, (7_500 + fee) as i64), + ], + }, + ]; + + // Run tests + for t in tests { + println!("Running test: {:?}", t); + + // Setup verifier + let mut report = TxSummaryUnblindingReport::<16>::default(); + let mut verifier = TxSummaryStreamingVerifierCtx::new( + &[0u8; 32], + BlockVersion::THREE, + t.outputs.len(), + t.inputs.len(), + sender.view_private_key().clone(), + PublicSubaddress { + view_public: change_subaddress.view_public_key().clone().into(), + spend_public: change_subaddress.spend_public_key().clone().into(), + }, + ); + + // Build and process TxOuts + for (target, amount) in &t.outputs { + println!("Add output {:?}: {:?}", target, amount); + + // Select target address + let receive_subaddress = match target { + OutputTarget::Ourself => &sender_subaddress, + OutputTarget::Change => &change_subaddress, + OutputTarget::Other => &target_subaddress, + OutputTarget::Swap => &swap_subaddress, + }; + + // Setup keys for TxOut + let tx_private_key = RistrettoPrivate::from_random(&mut rng); + let txout_shared_secret = + create_shared_secret(receive_subaddress.view_public_key(), &tx_private_key); + + // Construct TxOut object + let tx_out = TxOut::new( + BlockVersion::THREE, + amount.clone(), + &receive_subaddress, + &tx_private_key, + Default::default(), + ) + .unwrap(); + + // Build TxOut unblinding + let masked_amount = tx_out.get_masked_amount().unwrap(); + let (amount, blinding) = masked_amount.get_value(&txout_shared_secret).unwrap(); + let unmasked_amount = UnmaskedAmount { + value: amount.value, + token_id: *amount.token_id, + blinding: blinding.into(), + }; + + // Build TxOut summary + let target_key = create_tx_out_target_key(&tx_private_key, receive_subaddress); + let tx_out_summary = TxOutSummary { + masked_amount: Some(masked_amount.clone()), + target_key: target_key.into(), + public_key: tx_out.public_key, + associated_to_input_rules: target == &OutputTarget::Swap, + }; + + // Set address for normal outputs or SCIs + // TODO: do we _never_ have the output address for an SCI? + let address = match target != &OutputTarget::Swap { + true => Some(( + ShortAddressHash::from(receive_subaddress), + receive_subaddress, + )), + false => None, + }; + + // Digest TxOout + Summary with verifier + verifier + .digest_output( + &tx_out_summary, + &unmasked_amount, + address, + Some(&tx_private_key), + &mut report, + ) + .unwrap(); + } + + // Build and process TxIns? + for (kind, amount) in &t.inputs { + println!("Add input: {:?}", amount); + + // Setup keys for TxOut (kx against sender key as this is an input) + let tx_private_key = RistrettoPrivate::from_random(&mut rng); + let txout_shared_secret = + create_shared_secret(sender_subaddress.view_public_key(), &tx_private_key); + + // Construct TxOut object + let tx_out = TxOut::new( + BlockVersion::THREE, + amount.clone(), + &sender_subaddress, + &tx_private_key, + Default::default(), + ) + .unwrap(); + + let masked_amount = tx_out.get_masked_amount().unwrap(); + + // Build TxIn summary + let input_rules_digest = match kind { + InputType::Owned => Vec::new(), + InputType::Sci => vec![0u8; 32], + }; + let tx_in_summary = TxInSummary { + pseudo_output_commitment: masked_amount.commitment().clone(), + input_rules_digest, + }; + + // Build TxIn unblinding + let (amount, blinding) = masked_amount.get_value(&txout_shared_secret).unwrap(); + let unmasked_amount = UnmaskedAmount { + value: amount.value, + token_id: *amount.token_id, + blinding: blinding.into(), + }; + + // Digest transaction input + verifier + .digest_input(&tx_in_summary, &unmasked_amount, &mut report) + .unwrap(); + } + + // Finalize verifier + let mut digest = [0u8; 32]; + verifier + .finalize(Amount::new(fee, token_id), 1234, &mut digest, &mut report) + .unwrap(); + + report.finalize().unwrap(); + + // Check report totals + let totals: Vec<_> = report.totals.iter().map(|(t, v)| (*t, *v)).collect(); + assert_eq!(&totals, &t.totals, "Total mismatch"); + + // Check report outputs + let changes: Vec<_> = report + .outputs + .iter() + .map(|(e, t, v)| (e.clone(), *t, *v)) + .collect(); + assert_eq!(&changes, &t.changes, "Output mismatch"); + } + } } From 28470de81b6a62a6d90efb24aad20e2bf222ca07 Mon Sep 17 00:00:00 2001 From: ryan kurte Date: Wed, 21 Jun 2023 13:44:11 +1200 Subject: [PATCH 02/11] add sci inputs to summary reports --- Cargo.lock | 218 +++++++++++++++++++--------- transaction/summary/src/report.rs | 145 +++++++++++++----- transaction/summary/src/verifier.rs | 35 ++--- 3 files changed, 280 insertions(+), 118 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1e7c89c47..1bdb82d072 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -680,30 +680,38 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.11" +version = "4.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098" +checksum = "906f7fe1da4185b7a282b2bc90172a496f9def1aca4545fe7526810741591e14" dependencies = [ - "bitflags 2.0.2", + "clap_builder", "clap_derive", - "clap_lex 0.3.0", - "is-terminal", "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "351f9ad9688141ed83dfd8f5fb998a06225ef444b48ff4dc43de6d409b7fd10b" +dependencies = [ + "bitflags 1.3.2", + "clap_lex 0.4.1", + "is-terminal", "strsim 0.10.0", "termcolor", ] [[package]] name = "clap_derive" -version = "4.1.9" +version = "4.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644" +checksum = "81d7dc0031c3a59a04fc2ba395c8e2dd463cba1859275f065d225f6122221b45" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.12", ] [[package]] @@ -717,12 +725,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" -dependencies = [ - "os_str_bytes", -] +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" [[package]] name = "clear_on_drop" @@ -1160,9 +1165,9 @@ dependencies = [ [[package]] name = "dirs" -version = "4.0.0" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ "dirs-sys", ] @@ -1179,13 +1184,14 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", + "option-ext", "redox_users", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -1589,7 +1595,7 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" name = "go-grpc-gateway-testing" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "displaydoc", "futures", "grpcio", @@ -2296,7 +2302,7 @@ dependencies = [ name = "mc-admin-http-gateway" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "grpcio", "mc-common", "mc-util-grpc", @@ -2843,7 +2849,7 @@ dependencies = [ name = "mc-consensus-mint-client" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "displaydoc", "grpcio", "hex", @@ -2915,7 +2921,7 @@ dependencies = [ name = "mc-consensus-scp-play" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "mc-common", "mc-consensus-scp", "mc-transaction-core", @@ -2945,7 +2951,7 @@ version = "5.0.8" dependencies = [ "base64 0.21.0", "chrono", - "clap 4.1.11", + "clap 4.1.14", "curve25519-dalek", "displaydoc", "fs_extra", @@ -3009,7 +3015,7 @@ name = "mc-consensus-service-config" version = "5.0.8" dependencies = [ "base64 0.21.0", - "clap 4.1.11", + "clap 4.1.14", "displaydoc", "hex", "mc-attest-core", @@ -3035,7 +3041,7 @@ dependencies = [ name = "mc-consensus-tool" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "grpcio", "mc-common", "mc-connection", @@ -3050,7 +3056,7 @@ name = "mc-core" version = "5.0.8" dependencies = [ "anyhow", - "clap 4.1.11", + "clap 4.1.14", "curve25519-dalek", "ed25519-dalek", "generic-array", @@ -3341,7 +3347,7 @@ name = "mc-crypto-x509-test-vectors" version = "5.0.8" dependencies = [ "cargo-emit", - "clap 4.1.11", + "clap 4.1.14", "mc-crypto-keys", "mc-util-build-script", "pem", @@ -3407,7 +3413,7 @@ dependencies = [ name = "mc-fog-distribution" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "crossbeam-channel", "curve25519-dalek", "grpcio", @@ -3465,7 +3471,7 @@ name = "mc-fog-ingest-client" version = "5.0.8" dependencies = [ "assert_cmd", - "clap 4.1.11", + "clap 4.1.14", "displaydoc", "grpcio", "hex", @@ -3620,7 +3626,7 @@ dependencies = [ name = "mc-fog-ingest-server" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "dirs", "displaydoc", "futures", @@ -3842,7 +3848,7 @@ name = "mc-fog-ledger-server" version = "5.0.8" dependencies = [ "aes-gcm", - "clap 4.1.11", + "clap 4.1.14", "displaydoc", "futures", "grpcio", @@ -3925,7 +3931,7 @@ dependencies = [ name = "mc-fog-load-testing" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "grpcio", "mc-account-keys", "mc-blockchain-test-utils", @@ -3997,7 +4003,7 @@ dependencies = [ name = "mc-fog-overseer-server" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "displaydoc", "grpcio", "lazy_static", @@ -4080,7 +4086,7 @@ name = "mc-fog-report-cli" version = "5.0.8" dependencies = [ "base64 0.21.0", - "clap 4.1.11", + "clap 4.1.14", "grpcio", "hex", "mc-account-keys", @@ -4135,7 +4141,7 @@ dependencies = [ name = "mc-fog-report-server" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "displaydoc", "futures", "grpcio", @@ -4203,7 +4209,7 @@ name = "mc-fog-sample-paykit" version = "5.0.8" dependencies = [ "cargo-emit", - "clap 4.1.11", + "clap 4.1.14", "displaydoc", "futures", "grpcio", @@ -4301,7 +4307,7 @@ name = "mc-fog-sql-recovery-db" version = "5.0.8" dependencies = [ "chrono", - "clap 4.1.11", + "clap 4.1.14", "diesel", "diesel-derive-enum", "diesel_migrations", @@ -4334,7 +4340,7 @@ name = "mc-fog-sql-recovery-db-cleanup" version = "5.0.8" dependencies = [ "chrono", - "clap 4.1.11", + "clap 4.1.14", "mc-common", "mc-fog-recovery-db-iface", "mc-fog-sql-recovery-db", @@ -4345,7 +4351,7 @@ dependencies = [ name = "mc-fog-test-client" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "displaydoc", "grpcio", "hex_fmt", @@ -4379,7 +4385,7 @@ dependencies = [ name = "mc-fog-test-infra" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "digest 0.10.6", "hex", "mc-account-keys", @@ -4572,7 +4578,7 @@ dependencies = [ name = "mc-fog-view-load-test" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "grpcio", "mc-account-keys", "mc-attest-verifier", @@ -4614,7 +4620,7 @@ dependencies = [ name = "mc-fog-view-server" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "displaydoc", "futures", "grpcio", @@ -4725,7 +4731,7 @@ dependencies = [ name = "mc-ledger-distribution" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "dirs", "displaydoc", "mc-api", @@ -4748,7 +4754,7 @@ dependencies = [ name = "mc-ledger-from-archive" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "mc-api", "mc-common", "mc-ledger-db", @@ -4759,7 +4765,7 @@ dependencies = [ name = "mc-ledger-migration" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "lmdb-rkv", "mc-common", "mc-ledger-db", @@ -4807,7 +4813,7 @@ name = "mc-mobilecoind" version = "5.0.8" dependencies = [ "aes-gcm", - "clap 4.1.11", + "clap 4.1.14", "crossbeam-channel", "displaydoc", "grpcio", @@ -4895,7 +4901,7 @@ name = "mc-mobilecoind-dev-faucet" version = "5.0.8" dependencies = [ "async-channel", - "clap 4.1.11", + "clap 4.1.14", "displaydoc", "grpcio", "hex", @@ -4926,7 +4932,7 @@ dependencies = [ name = "mc-mobilecoind-json" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "grpcio", "hex", "mc-api", @@ -5106,7 +5112,7 @@ dependencies = [ name = "mc-sgx-css-dump" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "hex_fmt", "mc-sgx-css", ] @@ -5392,7 +5398,7 @@ name = "mc-transaction-signer" version = "5.0.8" dependencies = [ "anyhow", - "clap 4.1.11", + "clap 4.1.14", "displaydoc", "hex", "log", @@ -5421,15 +5427,19 @@ name = "mc-transaction-summary" version = "5.0.8" dependencies = [ "displaydoc", + "heapless", "mc-account-keys", "mc-core", "mc-crypto-digestible", "mc-crypto-keys", "mc-crypto-ring-signature", + "mc-transaction-core", "mc-transaction-types", + "mc-util-from-random", "mc-util-vec-map", "mc-util-zip-exact", "prost", + "rand", "serde", "subtle", "zeroize", @@ -5458,7 +5468,7 @@ dependencies = [ name = "mc-util-b58-decoder" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "hex", "mc-account-keys", "mc-api", @@ -5522,7 +5532,7 @@ dependencies = [ name = "mc-util-cli" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "mc-util-build-info", ] @@ -5530,7 +5540,7 @@ dependencies = [ name = "mc-util-dump-ledger" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "displaydoc", "mc-blockchain-types", "mc-common", @@ -5565,7 +5575,7 @@ dependencies = [ name = "mc-util-generate-sample-ledger" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "mc-account-keys", "mc-blockchain-test-utils", "mc-common", @@ -5585,7 +5595,7 @@ name = "mc-util-grpc" version = "5.0.8" dependencies = [ "base64 0.21.0", - "clap 4.1.11", + "clap 4.1.14", "cookie 0.17.0", "displaydoc", "futures", @@ -5618,7 +5628,7 @@ dependencies = [ name = "mc-util-grpc-admin-tool" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "grpcio", "mc-common", "mc-util-grpc", @@ -5629,7 +5639,7 @@ dependencies = [ name = "mc-util-grpc-token-generator" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "mc-common", "mc-util-grpc", "mc-util-parse", @@ -5645,7 +5655,7 @@ name = "mc-util-keyfile" version = "5.0.8" dependencies = [ "base64 0.21.0", - "clap 4.1.11", + "clap 4.1.14", "displaydoc", "hex", "mc-account-keys", @@ -5733,7 +5743,7 @@ dependencies = [ name = "mc-util-seeded-ed25519-key-gen" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "mc-crypto-keys", "mc-util-from-random", "mc-util-parse", @@ -5769,7 +5779,7 @@ dependencies = [ name = "mc-util-test-helper" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "itertools", "lazy_static", "mc-account-keys", @@ -5851,7 +5861,7 @@ dependencies = [ name = "mc-watcher" version = "5.0.8" dependencies = [ - "clap 4.1.11", + "clap 4.1.14", "displaydoc", "futures", "grpcio", @@ -6239,6 +6249,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-float" version = "1.1.1" @@ -7451,9 +7467,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.159" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] @@ -7479,9 +7495,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", @@ -8662,21 +8678,51 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.0", "windows_aarch64_msvc 0.42.0", "windows_i686_gnu 0.42.0", "windows_i686_msvc 0.42.0", "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.0", "windows_x86_64_msvc 0.42.0", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" @@ -8689,6 +8735,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.36.1" @@ -8701,6 +8753,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.36.1" @@ -8713,6 +8771,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" @@ -8725,12 +8789,24 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" @@ -8743,6 +8819,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "winnow" version = "0.4.0" diff --git a/transaction/summary/src/report.rs b/transaction/summary/src/report.rs index 402e9361cd..926dbda554 100644 --- a/transaction/summary/src/report.rs +++ b/transaction/summary/src/report.rs @@ -35,12 +35,15 @@ pub enum TransactionEntity { // is particularly useful for eliding generics when using this and is expected // to be helpful when building support for account info caching.) pub trait TransactionReport { - /// Add value to the transaction running transaction totals - fn total_add(&mut self, amount: Amount) -> Result<(), Error>; + /// Add value to the running transaction totals + fn input_add(&mut self, amount: Amount) -> Result<(), Error>; - /// Add matched change outputs to the report, these are subtracted from the - /// transaction totals - fn change_add(&mut self, amount: Amount) -> Result<(), Error>; + /// Subtract an amount from the transaction total, used for change outputs + /// and SCIs if enabled + fn change_sub(&mut self, amount: Amount) -> Result<(), Error>; + + /// Add SCI input not owned by our account + fn sci_add(&mut self, _amount: Amount) -> Result<(), Error>; /// Add output value for a particular entity / address to the report fn output_add(&mut self, entity: TransactionEntity, amount: Amount) -> Result<(), Error>; @@ -57,12 +60,16 @@ pub trait TransactionReport { /// [TransactionReport] impl for `&mut T` where `T: TransactionReport` impl TransactionReport for &mut T { - fn total_add(&mut self, amount: Amount) -> Result<(), Error> { - ::total_add(self, amount) + fn input_add(&mut self, amount: Amount) -> Result<(), Error> { + ::input_add(self, amount) + } + + fn change_sub(&mut self, amount: Amount) -> Result<(), Error> { + ::change_sub(self, amount) } - fn change_add(&mut self, amount: Amount) -> Result<(), Error> { - ::change_add(self, amount) + fn sci_add(&mut self, amount: Amount) -> Result<(), Error> { + ::sci_add(self, amount) } fn output_add(&mut self, entity: TransactionEntity, amount: Amount) -> Result<(), Error> { @@ -97,6 +104,8 @@ pub const MAX_TOTALS: usize = 4; /// This uses a double-entry approach where outputs and totals should be /// balanced. For each token, totals = our inputs - sum(change outputs) == /// sum(other outputs) + fee +/// +/// SCI inputs are currently ignored #[derive(Clone, Debug, Default)] pub struct TxSummaryUnblindingReport< const RECORDS: usize = MAX_RECORDS, @@ -112,7 +121,7 @@ pub struct TxSummaryUnblindingReport< /// /// Note that swap inputs are elided as these are not inputs /// owned by us (ie. are not spent from our account) - pub totals: Vec<(TokenId, i64), TOTALS>, + pub totals: Vec<(TokenId, TotalKind, i64), TOTALS>, /// The network fee that we pay to execute the transaction pub network_fee: Amount, @@ -121,24 +130,36 @@ pub struct TxSummaryUnblindingReport< pub tombstone_block: u64, } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub enum TotalKind { + /// Input owned by our account + Ours, + /// Input owned by SCI counterparty + Sci, +} + impl TransactionReport for TxSummaryUnblindingReport { - /// Add input, added to the transaction total - fn total_add(&mut self, amount: Amount) -> Result<(), Error> { + /// Add owned input, added to the transaction total + fn input_add(&mut self, amount: Amount) -> Result<(), Error> { let Amount { token_id, value } = amount; // Ensure value will not overflow let value = i64::try_from(value).map_err(|_| Error::NumericOverflow)?; // Check for existing total entry for this token - match self.totals.iter_mut().find(|(t, _)| t == &token_id) { + match self + .totals + .iter_mut() + .find(|(t, k, _)| t == &token_id && *k == TotalKind::Ours) + { // If we have an entry, add the value to this - Some(v) => v.1 = v.1.checked_add(value).ok_or(Error::NumericOverflow)?, + Some(v) => v.2 = v.2.checked_add(value).ok_or(Error::NumericOverflow)?, // If we do not, create a new entry None => self .totals - .push((token_id, value)) + .push((token_id, TotalKind::Ours, value)) .map_err(|_| Error::BufferOverflow)?, } @@ -146,26 +167,54 @@ impl TransactionReport } /// Add change output, subtracted from the transaction total - fn change_add(&mut self, amount: Amount) -> Result<(), Error> { + fn change_sub(&mut self, amount: Amount) -> Result<(), Error> { let Amount { token_id, value } = amount; // Ensure value will not overflow let value = i64::try_from(value).map_err(|_| Error::NumericOverflow)?; // Check for existing total entry for this token - match self.totals.iter_mut().find(|(t, _)| t == &token_id) { + match self + .totals + .iter_mut() + .find(|(t, k, _)| t == &token_id && *k == TotalKind::Ours) + { // If we have an entry, subtract the change value from this - Some(v) => v.1 = v.1.checked_sub(value).ok_or(Error::NumericOverflow)?, + Some(v) => v.2 = v.2.checked_sub(value).ok_or(Error::NumericOverflow)?, // If we do not, create a new entry None => self .totals - .push((token_id, -value)) + .push((token_id, TotalKind::Ours, -value)) .map_err(|_| Error::BufferOverflow)?, } Ok(()) } + /// Add SCI (or other) input not owned by our account + fn sci_add(&mut self, amount: Amount) -> Result<(), Error> { + let Amount { token_id, value } = amount; + + // Ensure value will not overflow + let value = i64::try_from(value).map_err(|_| Error::NumericOverflow)?; + + // Check for existing total entry for this token + match self + .totals + .iter_mut() + .find(|(t, k, _)| t == &token_id && *k == TotalKind::Sci) + { + // If we have an entry, add the value to this + Some(v) => v.2 = v.2.checked_add(value).ok_or(Error::NumericOverflow)?, + // If we do not, create a new entry + None => self + .totals + .push((token_id, TotalKind::Sci, value)) + .map_err(|_| Error::BufferOverflow)?, + } + Ok(()) + } + /// Add output value to a particular entity / address to the report fn output_add(&mut self, entity: TransactionEntity, amount: Amount) -> Result<(), Error> { let Amount { token_id, value } = amount; @@ -202,18 +251,41 @@ impl TransactionReport Ok(()) } - /// Finalise report, checking totals and sorting report entries + /// Finalise report, checking and balancing totals and sorting report + /// entries fn finalize(&mut self) -> Result<(), Error> { // Sort outputs and totals self.sort(); // For each token id, check that inputs match outputs - for (token_id, value) in &self.totals { + // (this is only executed where _totals_ exist, so skipped + // for the current SCI implementation) + for (token_id, total_kind, value) in &mut self.totals { // Sum outputs for this token id let mut balance = 0u64; - for (_e, id, v) in &self.outputs { - if id == token_id { - balance = balance.checked_add(*v).ok_or(Error::NumericOverflow)?; + for (e, id, v) in &self.outputs { + // Skip other tokens + if id != token_id { + continue; + } + + // Handle balance / values depending on whether the total is from us or a swap + // counterparty + match total_kind { + // If it's coming from our account, track total balance + TotalKind::Ours => { + balance = balance.checked_add(*v).ok_or(Error::NumericOverflow)?; + } + // If it's coming from an SCI, and returned to the counterparty, reduce total by + // outgoing value + TotalKind::Sci if e == &TransactionEntity::Swap => { + *value = value.checked_sub(*v as i64).ok_or(Error::NumericOverflow)?; + } + // If it's coming from an SCI to us, add to total balance + TotalKind::Sci if e != &TransactionEntity::Swap => { + balance = balance.checked_add(*v).ok_or(Error::NumericOverflow)?; + } + _ => (), } } @@ -251,10 +323,11 @@ impl TxSummaryUnblindingReport(), 1384); + assert_eq!(core::mem::size_of::(), 1416); } #[test] @@ -304,28 +377,34 @@ mod tests { ]; for a in amounts { - report.total_add(a).unwrap(); + report.input_add(a).unwrap(); } // Check total inputs report.sort(); assert_eq!( &report.totals[..], - &[(TokenId::from(1), 100), (TokenId::from(2), 300)] + &[ + (TokenId::from(1), TotalKind::Ours, 100), + (TokenId::from(2), TotalKind::Ours, 300) + ] ); // Subtract change amounts report - .change_add(Amount::new(25, TokenId::from(1))) + .change_sub(Amount::new(25, TokenId::from(1))) .unwrap(); report - .change_add(Amount::new(50, TokenId::from(2))) + .change_sub(Amount::new(50, TokenId::from(2))) .unwrap(); // Check total inputs - change assert_eq!( &report.totals[..], - &[(TokenId::from(1), 75), (TokenId::from(2), 250)] + &[ + (TokenId::from(1), TotalKind::Ours, 75), + (TokenId::from(2), TotalKind::Ours, 250) + ] ); } diff --git a/transaction/summary/src/verifier.rs b/transaction/summary/src/verifier.rs index 17810070a4..9eb40b2bcd 100644 --- a/transaction/summary/src/verifier.rs +++ b/transaction/summary/src/verifier.rs @@ -142,7 +142,7 @@ impl TxSummaryStreamingVerifierCtx { && address.spend_public_key() == self.change_address.spend_public_key() { // If this is to our change address, subtract this from the total inputs - report.change_add(amount)?; + report.change_sub(amount)?; } else { // Otherwise, add this as an output to ourself report @@ -200,10 +200,7 @@ impl TxSummaryStreamingVerifierCtx { return Err(Error::AmountVerificationFailed); } - // Add swap output to report - // NOTE: this is not exercised as swap rings are created without a transaction - // ... does this mean the ocurrence of a swap output is invalid / doesn't need - // to be handled here? + // Add outputs to swap counterparty to the report report.output_add(TransactionEntity::Swap, unmasked_amount.into())?; } @@ -253,10 +250,11 @@ impl TxSummaryStreamingVerifierCtx { if tx_in_summary.input_rules_digest.is_empty() { // If we have no input rules digest, then this is a normal input // add this to the report total - report.total_add(tx_in_summary_unblinding_data.into())?; + report.input_add(tx_in_summary_unblinding_data.into())?; } else { // If we have input rules this is an SCI input and does not impact - // our balance + // our balance, but we _can_ track this if required + report.sci_add(tx_in_summary_unblinding_data.into())?; }; // We've now verified the tx_in_summary and added it to the report. @@ -366,7 +364,7 @@ mod tests { use rand::rngs::OsRng; - use crate::TxSummaryUnblindingReport; + use crate::{report::TotalKind, TxSummaryUnblindingReport}; use mc_account_keys::AccountKey; use mc_transaction_core::{tx::TxOut, BlockVersion}; use mc_transaction_types::TokenId; @@ -391,7 +389,7 @@ mod tests { /// Outputs produced by the transaction outputs: Vec<(OutputTarget, Amount)>, /// Totals / balances by token - totals: Vec<(TokenId, i64)>, + totals: Vec<(TokenId, TotalKind, i64)>, /// Changes produced by the transaction changes: Vec<(TransactionEntity, TokenId, u64)>, } @@ -448,7 +446,7 @@ mod tests { token_id, amount.value, )], - totals: vec![(token_id, (amount.value + fee) as i64)], + totals: vec![(token_id, TotalKind::Ours, (amount.value + fee) as i64)], }, // Output to our change address, should show no outputs with balance change = fee TxOutReportTest { @@ -463,7 +461,7 @@ mod tests { changes: vec![ //(TransactionEntity::Total, token_id, 0), ], - totals: vec![(token_id, fee as i64)], + totals: vec![(token_id, TotalKind::Ours, fee as i64)], }, // Output to someone else, should show their address and total of output + fee TxOutReportTest { @@ -474,7 +472,7 @@ mod tests { token_id, amount.value, )], - totals: vec![(token_id, (amount.value + fee) as i64)], + totals: vec![(token_id, TotalKind::Ours, (amount.value + fee) as i64)], }, // Basic SCI. consuming entire swap, inputs should not count towards totals TxOutReportTest { @@ -500,7 +498,9 @@ mod tests { ], totals: vec![ // The total is the change to _our_ balance spent during the transaction - (token_id, (10_000 + fee) as i64), + (token_id, TotalKind::Ours, (10_000 + fee) as i64), + // And the SCI input + (TokenId::from(2), TotalKind::Sci, (200) as i64), ], }, // Partial SCI @@ -530,7 +530,9 @@ mod tests { ], totals: vec![ // The total is the change to _our_ balance spent during the transaction - (token_id, (7_500 + fee) as i64), + (token_id, TotalKind::Ours, (7_500 + fee) as i64), + // And the SCI input - partial value returned + (TokenId::from(2), TotalKind::Sci, (150) as i64), ], }, ]; @@ -598,8 +600,7 @@ mod tests { associated_to_input_rules: target == &OutputTarget::Swap, }; - // Set address for normal outputs or SCIs - // TODO: do we _never_ have the output address for an SCI? + // Set address for normal outputs, not provided for SCIs let address = match target != &OutputTarget::Swap { true => Some(( ShortAddressHash::from(receive_subaddress), @@ -674,7 +675,7 @@ mod tests { report.finalize().unwrap(); // Check report totals - let totals: Vec<_> = report.totals.iter().map(|(t, v)| (*t, *v)).collect(); + let totals: Vec<_> = report.totals.iter().map(|(t, k, v)| (*t, *k, *v)).collect(); assert_eq!(&totals, &t.totals, "Total mismatch"); // Check report outputs From 486ae1282b46469b1928d88fd1e94178fb838137 Mon Sep 17 00:00:00 2001 From: ryan kurte Date: Sat, 15 Jul 2023 11:08:09 +1200 Subject: [PATCH 03/11] rebase and update lock --- Cargo.lock | 87 +++---------------------------- transaction/summary/src/report.rs | 2 +- 2 files changed, 8 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1bdb82d072..c0de86af8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1165,9 +1165,9 @@ dependencies = [ [[package]] name = "dirs" -version = "5.0.1" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ "dirs-sys", ] @@ -1184,14 +1184,13 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", - "option-ext", "redox_users", - "windows-sys 0.48.0", + "winapi", ] [[package]] @@ -6249,12 +6248,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "ordered-float" version = "1.1.1" @@ -8678,51 +8671,21 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm 0.42.0", + "windows_aarch64_gnullvm", "windows_aarch64_msvc 0.42.0", "windows_i686_gnu 0.42.0", "windows_i686_msvc 0.42.0", "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm 0.42.0", + "windows_x86_64_gnullvm", "windows_x86_64_msvc 0.42.0", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - [[package]] name = "windows_aarch64_msvc" version = "0.36.1" @@ -8735,12 +8698,6 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - [[package]] name = "windows_i686_gnu" version = "0.36.1" @@ -8753,12 +8710,6 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - [[package]] name = "windows_i686_msvc" version = "0.36.1" @@ -8771,12 +8722,6 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - [[package]] name = "windows_x86_64_gnu" version = "0.36.1" @@ -8789,24 +8734,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - [[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - [[package]] name = "windows_x86_64_msvc" version = "0.36.1" @@ -8819,12 +8752,6 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - [[package]] name = "winnow" version = "0.4.0" diff --git a/transaction/summary/src/report.rs b/transaction/summary/src/report.rs index 926dbda554..2f9e5a106f 100644 --- a/transaction/summary/src/report.rs +++ b/transaction/summary/src/report.rs @@ -43,7 +43,7 @@ pub trait TransactionReport { fn change_sub(&mut self, amount: Amount) -> Result<(), Error>; /// Add SCI input not owned by our account - fn sci_add(&mut self, _amount: Amount) -> Result<(), Error>; + fn sci_add(&mut self, amount: Amount) -> Result<(), Error>; /// Add output value for a particular entity / address to the report fn output_add(&mut self, entity: TransactionEntity, amount: Amount) -> Result<(), Error>; From 3ab66d3d296d617902fba06e4b408090401e629d Mon Sep 17 00:00:00 2001 From: ryan kurte Date: Wed, 26 Jul 2023 11:23:10 +1200 Subject: [PATCH 04/11] add swap elision to report, update docs --- Cargo.lock | 135 +++++++++++++--------------- transaction/summary/src/error.rs | 2 + transaction/summary/src/report.rs | 105 +++++++++++++++++++--- transaction/summary/src/verifier.rs | 5 +- 4 files changed, 162 insertions(+), 85 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c0de86af8c..d1e7c89c47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -680,38 +680,30 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.14" +version = "4.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "906f7fe1da4185b7a282b2bc90172a496f9def1aca4545fe7526810741591e14" +checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098" dependencies = [ - "clap_builder", + "bitflags 2.0.2", "clap_derive", - "once_cell", -] - -[[package]] -name = "clap_builder" -version = "4.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "351f9ad9688141ed83dfd8f5fb998a06225ef444b48ff4dc43de6d409b7fd10b" -dependencies = [ - "bitflags 1.3.2", - "clap_lex 0.4.1", + "clap_lex 0.3.0", "is-terminal", + "once_cell", "strsim 0.10.0", "termcolor", ] [[package]] name = "clap_derive" -version = "4.1.14" +version = "4.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d7dc0031c3a59a04fc2ba395c8e2dd463cba1859275f065d225f6122221b45" +checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644" dependencies = [ "heck", + "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.12", + "syn 1.0.109", ] [[package]] @@ -725,9 +717,12 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.4.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] [[package]] name = "clear_on_drop" @@ -1184,9 +1179,9 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.7" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" dependencies = [ "libc", "redox_users", @@ -1594,7 +1589,7 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" name = "go-grpc-gateway-testing" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "displaydoc", "futures", "grpcio", @@ -2301,7 +2296,7 @@ dependencies = [ name = "mc-admin-http-gateway" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "grpcio", "mc-common", "mc-util-grpc", @@ -2848,7 +2843,7 @@ dependencies = [ name = "mc-consensus-mint-client" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "displaydoc", "grpcio", "hex", @@ -2920,7 +2915,7 @@ dependencies = [ name = "mc-consensus-scp-play" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "mc-common", "mc-consensus-scp", "mc-transaction-core", @@ -2950,7 +2945,7 @@ version = "5.0.8" dependencies = [ "base64 0.21.0", "chrono", - "clap 4.1.14", + "clap 4.1.11", "curve25519-dalek", "displaydoc", "fs_extra", @@ -3014,7 +3009,7 @@ name = "mc-consensus-service-config" version = "5.0.8" dependencies = [ "base64 0.21.0", - "clap 4.1.14", + "clap 4.1.11", "displaydoc", "hex", "mc-attest-core", @@ -3040,7 +3035,7 @@ dependencies = [ name = "mc-consensus-tool" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "grpcio", "mc-common", "mc-connection", @@ -3055,7 +3050,7 @@ name = "mc-core" version = "5.0.8" dependencies = [ "anyhow", - "clap 4.1.14", + "clap 4.1.11", "curve25519-dalek", "ed25519-dalek", "generic-array", @@ -3346,7 +3341,7 @@ name = "mc-crypto-x509-test-vectors" version = "5.0.8" dependencies = [ "cargo-emit", - "clap 4.1.14", + "clap 4.1.11", "mc-crypto-keys", "mc-util-build-script", "pem", @@ -3412,7 +3407,7 @@ dependencies = [ name = "mc-fog-distribution" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "crossbeam-channel", "curve25519-dalek", "grpcio", @@ -3470,7 +3465,7 @@ name = "mc-fog-ingest-client" version = "5.0.8" dependencies = [ "assert_cmd", - "clap 4.1.14", + "clap 4.1.11", "displaydoc", "grpcio", "hex", @@ -3625,7 +3620,7 @@ dependencies = [ name = "mc-fog-ingest-server" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "dirs", "displaydoc", "futures", @@ -3847,7 +3842,7 @@ name = "mc-fog-ledger-server" version = "5.0.8" dependencies = [ "aes-gcm", - "clap 4.1.14", + "clap 4.1.11", "displaydoc", "futures", "grpcio", @@ -3930,7 +3925,7 @@ dependencies = [ name = "mc-fog-load-testing" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "grpcio", "mc-account-keys", "mc-blockchain-test-utils", @@ -4002,7 +3997,7 @@ dependencies = [ name = "mc-fog-overseer-server" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "displaydoc", "grpcio", "lazy_static", @@ -4085,7 +4080,7 @@ name = "mc-fog-report-cli" version = "5.0.8" dependencies = [ "base64 0.21.0", - "clap 4.1.14", + "clap 4.1.11", "grpcio", "hex", "mc-account-keys", @@ -4140,7 +4135,7 @@ dependencies = [ name = "mc-fog-report-server" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "displaydoc", "futures", "grpcio", @@ -4208,7 +4203,7 @@ name = "mc-fog-sample-paykit" version = "5.0.8" dependencies = [ "cargo-emit", - "clap 4.1.14", + "clap 4.1.11", "displaydoc", "futures", "grpcio", @@ -4306,7 +4301,7 @@ name = "mc-fog-sql-recovery-db" version = "5.0.8" dependencies = [ "chrono", - "clap 4.1.14", + "clap 4.1.11", "diesel", "diesel-derive-enum", "diesel_migrations", @@ -4339,7 +4334,7 @@ name = "mc-fog-sql-recovery-db-cleanup" version = "5.0.8" dependencies = [ "chrono", - "clap 4.1.14", + "clap 4.1.11", "mc-common", "mc-fog-recovery-db-iface", "mc-fog-sql-recovery-db", @@ -4350,7 +4345,7 @@ dependencies = [ name = "mc-fog-test-client" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "displaydoc", "grpcio", "hex_fmt", @@ -4384,7 +4379,7 @@ dependencies = [ name = "mc-fog-test-infra" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "digest 0.10.6", "hex", "mc-account-keys", @@ -4577,7 +4572,7 @@ dependencies = [ name = "mc-fog-view-load-test" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "grpcio", "mc-account-keys", "mc-attest-verifier", @@ -4619,7 +4614,7 @@ dependencies = [ name = "mc-fog-view-server" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "displaydoc", "futures", "grpcio", @@ -4730,7 +4725,7 @@ dependencies = [ name = "mc-ledger-distribution" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "dirs", "displaydoc", "mc-api", @@ -4753,7 +4748,7 @@ dependencies = [ name = "mc-ledger-from-archive" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "mc-api", "mc-common", "mc-ledger-db", @@ -4764,7 +4759,7 @@ dependencies = [ name = "mc-ledger-migration" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "lmdb-rkv", "mc-common", "mc-ledger-db", @@ -4812,7 +4807,7 @@ name = "mc-mobilecoind" version = "5.0.8" dependencies = [ "aes-gcm", - "clap 4.1.14", + "clap 4.1.11", "crossbeam-channel", "displaydoc", "grpcio", @@ -4900,7 +4895,7 @@ name = "mc-mobilecoind-dev-faucet" version = "5.0.8" dependencies = [ "async-channel", - "clap 4.1.14", + "clap 4.1.11", "displaydoc", "grpcio", "hex", @@ -4931,7 +4926,7 @@ dependencies = [ name = "mc-mobilecoind-json" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "grpcio", "hex", "mc-api", @@ -5111,7 +5106,7 @@ dependencies = [ name = "mc-sgx-css-dump" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "hex_fmt", "mc-sgx-css", ] @@ -5397,7 +5392,7 @@ name = "mc-transaction-signer" version = "5.0.8" dependencies = [ "anyhow", - "clap 4.1.14", + "clap 4.1.11", "displaydoc", "hex", "log", @@ -5426,19 +5421,15 @@ name = "mc-transaction-summary" version = "5.0.8" dependencies = [ "displaydoc", - "heapless", "mc-account-keys", "mc-core", "mc-crypto-digestible", "mc-crypto-keys", "mc-crypto-ring-signature", - "mc-transaction-core", "mc-transaction-types", - "mc-util-from-random", "mc-util-vec-map", "mc-util-zip-exact", "prost", - "rand", "serde", "subtle", "zeroize", @@ -5467,7 +5458,7 @@ dependencies = [ name = "mc-util-b58-decoder" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "hex", "mc-account-keys", "mc-api", @@ -5531,7 +5522,7 @@ dependencies = [ name = "mc-util-cli" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "mc-util-build-info", ] @@ -5539,7 +5530,7 @@ dependencies = [ name = "mc-util-dump-ledger" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "displaydoc", "mc-blockchain-types", "mc-common", @@ -5574,7 +5565,7 @@ dependencies = [ name = "mc-util-generate-sample-ledger" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "mc-account-keys", "mc-blockchain-test-utils", "mc-common", @@ -5594,7 +5585,7 @@ name = "mc-util-grpc" version = "5.0.8" dependencies = [ "base64 0.21.0", - "clap 4.1.14", + "clap 4.1.11", "cookie 0.17.0", "displaydoc", "futures", @@ -5627,7 +5618,7 @@ dependencies = [ name = "mc-util-grpc-admin-tool" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "grpcio", "mc-common", "mc-util-grpc", @@ -5638,7 +5629,7 @@ dependencies = [ name = "mc-util-grpc-token-generator" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "mc-common", "mc-util-grpc", "mc-util-parse", @@ -5654,7 +5645,7 @@ name = "mc-util-keyfile" version = "5.0.8" dependencies = [ "base64 0.21.0", - "clap 4.1.14", + "clap 4.1.11", "displaydoc", "hex", "mc-account-keys", @@ -5742,7 +5733,7 @@ dependencies = [ name = "mc-util-seeded-ed25519-key-gen" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "mc-crypto-keys", "mc-util-from-random", "mc-util-parse", @@ -5778,7 +5769,7 @@ dependencies = [ name = "mc-util-test-helper" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "itertools", "lazy_static", "mc-account-keys", @@ -5860,7 +5851,7 @@ dependencies = [ name = "mc-watcher" version = "5.0.8" dependencies = [ - "clap 4.1.14", + "clap 4.1.11", "displaydoc", "futures", "grpcio", @@ -7460,9 +7451,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.164" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" dependencies = [ "serde_derive", ] @@ -7488,9 +7479,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" dependencies = [ "proc-macro2", "quote", diff --git a/transaction/summary/src/error.rs b/transaction/summary/src/error.rs index 120071e7cc..06ab2c55f7 100644 --- a/transaction/summary/src/error.rs +++ b/transaction/summary/src/error.rs @@ -38,6 +38,8 @@ pub enum Error { Amount(AmountError), /// ZipExact error: {0} ZipExact(ZipExactError), + /// Missing address for own output + MissingOutputAddress, } impl From for Error { diff --git a/transaction/summary/src/report.rs b/transaction/summary/src/report.rs index 2f9e5a106f..66fc4d0e39 100644 --- a/transaction/summary/src/report.rs +++ b/transaction/summary/src/report.rs @@ -26,7 +26,7 @@ pub enum TransactionEntity { /// Outputs to other accounts (hash {0}) OtherAddress(ShortAddressHash), - /// Swap counterparty + /// Outputs to swap counterparty Swap, } @@ -117,10 +117,12 @@ pub struct TxSummaryUnblindingReport< /// Total balance change for our account for each type of token in the /// transaction. /// - /// totals = our inputs - sum(change outputs) + /// totals = inputs - sum(change outputs) /// - /// Note that swap inputs are elided as these are not inputs - /// owned by us (ie. are not spent from our account) + /// Note that owned and swap inputs are split as most + /// applications are concerned only with the _cost_ of + /// the transaction to the user. + /// See [elide_swap_totals] for more detail. pub totals: Vec<(TokenId, TotalKind, i64), TOTALS>, /// The network fee that we pay to execute the transaction @@ -132,9 +134,10 @@ pub struct TxSummaryUnblindingReport< #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub enum TotalKind { - /// Input owned by our account + /// Input owned by our account (less change), outgoing from our account Ours, - /// Input owned by SCI counterparty + /// Input owned by SCI counterparty (less change for partial swaps) incoming + /// to our account Sci, } @@ -323,11 +326,32 @@ impl TxSummaryUnblindingReport::new(); + + let addr = TransactionEntity::OurAddress(ShortAddressHash::from(random::<[u8; 16]>())); + + // Add SCI outputs + report + .output_add(TransactionEntity::Swap, Amount::new(10, TokenId::from(1))) + .unwrap(); + report + .output_add(TransactionEntity::Swap, Amount::new(50, TokenId::from(2))) + .unwrap(); + + // Add output to us + report + .output_add(addr.clone(), Amount::new(50, TokenId::from(2))) + .unwrap(); + + // Add input from SCI + report.sci_add(Amount::new(100, TokenId::from(2))).unwrap(); + + // Add owned input + report.input_add(Amount::new(10, TokenId::from(1))).unwrap(); + + // Finalise report + report.finalize().unwrap(); + + // Check full outputs / totals + assert_eq!( + &report.outputs[..], + &[ + (addr.clone(), TokenId::from(2), 50), + (TransactionEntity::Swap, TokenId::from(1), 10), + (TransactionEntity::Swap, TokenId::from(2), 50), + ] + ); + assert_eq!( + &report.totals[..], + &[ + (TokenId::from(1), TotalKind::Ours, 10), + (TokenId::from(2), TotalKind::Sci, 50), + ] + ); + + // Trim swap information (removes swap partial returns and totals) + report.elide_swap_totals(); + + assert_eq!( + &report.outputs[..], + &[ + (addr.clone(), TokenId::from(2), 50), + (TransactionEntity::Swap, TokenId::from(1), 10), + ] + ); + assert_eq!( + &report.totals[..], + &[(TokenId::from(1), TotalKind::Ours, 10),] + ); + } } diff --git a/transaction/summary/src/verifier.rs b/transaction/summary/src/verifier.rs index 9eb40b2bcd..c9a5ddf96d 100644 --- a/transaction/summary/src/verifier.rs +++ b/transaction/summary/src/verifier.rs @@ -149,9 +149,8 @@ impl TxSummaryStreamingVerifierCtx { .output_add(TransactionEntity::OurAddress(address_hash.clone()), amount)?; } } else { - // TODO: If we _don't_ have address information but it's to our own address... - // what then? is this even possible??! - panic!("what's the right thing to do here..?"); + // If we _don't_ have address information but it's to our own address... + return Err(Error::MissingOutputAddress); } // If we didn't match the output, and we have address information, this From 735ef946e0ee022e05808f505870717d3ada9460 Mon Sep 17 00:00:00 2001 From: ryan kurte Date: Wed, 20 Sep 2023 09:47:35 +1200 Subject: [PATCH 05/11] lockfile change --- Cargo.lock | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index d1e7c89c47..dd1b6d4bb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5421,15 +5421,19 @@ name = "mc-transaction-summary" version = "5.0.8" dependencies = [ "displaydoc", + "heapless", "mc-account-keys", "mc-core", "mc-crypto-digestible", "mc-crypto-keys", "mc-crypto-ring-signature", + "mc-transaction-core", "mc-transaction-types", + "mc-util-from-random", "mc-util-vec-map", "mc-util-zip-exact", "prost", + "rand", "serde", "subtle", "zeroize", From 3493a01f1a9d32128822335ce64e75210a909a5c Mon Sep 17 00:00:00 2001 From: ryan kurte Date: Fri, 22 Sep 2023 10:56:53 +1200 Subject: [PATCH 06/11] fix report tests to include sci totals --- transaction/extra/tests/verifier.rs | 34 ++++++++++++++++++++++------- transaction/summary/src/lib.rs | 2 +- transaction/summary/src/report.rs | 2 ++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/transaction/extra/tests/verifier.rs b/transaction/extra/tests/verifier.rs index 439f6e382a..8b009f02c2 100644 --- a/transaction/extra/tests/verifier.rs +++ b/transaction/extra/tests/verifier.rs @@ -18,7 +18,7 @@ use mc_transaction_core::{ Amount, BlockVersion, Token, TokenId, }; use mc_transaction_extra::UnsignedTx; -use mc_transaction_summary::{verify_tx_summary, TransactionEntity}; +use mc_transaction_summary::{verify_tx_summary, TotalKind, TransactionEntity}; use mc_util_from_random::FromRandom; use mc_util_serial::encode; use rand::{rngs::StdRng, SeedableRng}; @@ -221,7 +221,10 @@ fn test_max_size_tx_summary_verification() { 160 )] ); - assert_eq!(&report.totals, &[(TokenId::from(0), 16000),]); + assert_eq!( + &report.totals, + &[(TokenId::from(0), TotalKind::Ours, 16000),] + ); assert_eq!(report.network_fee, Amount::new(15840, TokenId::from(0))); } @@ -256,7 +259,10 @@ fn test_min_size_tx_summary_verification() { 10 )] ); - assert_eq!(&report.totals, &[(TokenId::from(0), 1000),]); + assert_eq!( + &report.totals, + &[(TokenId::from(0), TotalKind::Ours, 1000),] + ); assert_eq!(report.network_fee, Amount::new(990, TokenId::from(0))); } @@ -343,7 +349,11 @@ fn test_two_input_tx_with_change_tx_summary_verification() { let recipient_hash = ShortAddressHash::from(&recipient.default_subaddress()); assert_eq!( &report.totals, - &[(token_id, (value + value2 - change_value) as i64),] + &[( + token_id, + TotalKind::Ours, + (value + value2 - change_value) as i64 + ),] ); assert_eq!( &report.outputs, @@ -431,7 +441,7 @@ fn test_simple_tx_with_change_tx_summary_verification() { let recipient_hash = ShortAddressHash::from(&recipient.default_subaddress()); assert_eq!( &report.totals, - &[(token_id, ((value - change_value) as i64)),] + &[(token_id, TotalKind::Ours, ((value - change_value) as i64)),] ); assert_eq!( &report.outputs, @@ -523,7 +533,11 @@ fn test_two_output_tx_with_change_tx_summary_verification() { let recipient2_hash = ShortAddressHash::from(&recipient2.default_subaddress()); assert_eq!( &report.totals, - &[(token_id, (value + value2 + Mob::MINIMUM_FEE) as i64),] + &[( + token_id, + TotalKind::Ours, + (value + value2 + Mob::MINIMUM_FEE) as i64 + ),] ); let mut outputs = vec![ ( @@ -652,7 +666,9 @@ fn test_sci_tx_summary_verification() { &report.totals, &[ // Bob spends 3x worth of token id 2 in the transaction - (token2, value2 as i64), + (token2, TotalKind::Ours, value2 as i64), + // SCI inputs used in the transaction + (Mob::ID, TotalKind::Sci, value as i64), ] ); let mut outputs = vec![ @@ -781,7 +797,9 @@ fn test_sci_three_way_tx_summary_verification() { &report.totals, &[ // Bob's spend to create the transaction - (token2, value2 as i64), + (token2, TotalKind::Ours, value2 as i64), + // SCI inputs used in the transaction + (Mob::ID, TotalKind::Sci, value as i64), ] ); let mut outputs = vec![ diff --git a/transaction/summary/src/lib.rs b/transaction/summary/src/lib.rs index f035f94f16..302631b14a 100644 --- a/transaction/summary/src/lib.rs +++ b/transaction/summary/src/lib.rs @@ -18,5 +18,5 @@ mod verifier; pub use data::{verify_tx_summary, TxOutSummaryUnblindingData, TxSummaryUnblindingData}; pub use error::Error; -pub use report::{TransactionEntity, TxSummaryUnblindingReport}; +pub use report::{TotalKind, TransactionEntity, TxSummaryUnblindingReport}; pub use verifier::TxSummaryStreamingVerifierCtx; diff --git a/transaction/summary/src/report.rs b/transaction/summary/src/report.rs index 66fc4d0e39..08aaa78f07 100644 --- a/transaction/summary/src/report.rs +++ b/transaction/summary/src/report.rs @@ -132,6 +132,8 @@ pub struct TxSummaryUnblindingReport< pub tombstone_block: u64, } +/// Type of total balance, either `Ours` for inputs from our account or +/// `Sci` for inputs from a swap counterparty. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub enum TotalKind { /// Input owned by our account (less change), outgoing from our account From fdec87f4ee4ecb5a5a9ef23f6f5a4ea2f6da4d31 Mon Sep 17 00:00:00 2001 From: ryan kurte Date: Fri, 22 Sep 2023 11:36:49 +1200 Subject: [PATCH 07/11] fix lints --- transaction/summary/src/report.rs | 2 +- transaction/summary/src/verifier.rs | 32 ++++++++++++++--------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/transaction/summary/src/report.rs b/transaction/summary/src/report.rs index 08aaa78f07..a51df48d6d 100644 --- a/transaction/summary/src/report.rs +++ b/transaction/summary/src/report.rs @@ -521,7 +521,7 @@ mod tests { assert_eq!( &report.outputs[..], &[ - (addr.clone(), TokenId::from(2), 50), + (addr, TokenId::from(2), 50), (TransactionEntity::Swap, TokenId::from(1), 10), ] ); diff --git a/transaction/summary/src/verifier.rs b/transaction/summary/src/verifier.rs index c9a5ddf96d..fdc48d14ee 100644 --- a/transaction/summary/src/verifier.rs +++ b/transaction/summary/src/verifier.rs @@ -394,7 +394,6 @@ mod tests { } #[derive(Clone, Debug, PartialEq)] - #[allow(dead_code)] enum InputType { /// An input we own, reducing our balance Owned, @@ -403,7 +402,6 @@ mod tests { } #[derive(Clone, Debug, PartialEq)] - #[allow(dead_code)] enum OutputTarget { /// An output to ourself (_not_ a change address) Ourself, @@ -439,7 +437,7 @@ mod tests { // Output to ourself, should show output to our address and total of output + fee TxOutReportTest { inputs: vec![(InputType::Owned, Amount::new(amount.value + fee, token_id))], - outputs: vec![(OutputTarget::Ourself, amount.clone())], + outputs: vec![(OutputTarget::Ourself, amount)], changes: vec![( TransactionEntity::OurAddress(ShortAddressHash::from(&sender_subaddress)), token_id, @@ -456,7 +454,7 @@ mod tests { ), (InputType::Owned, Amount::new(amount.value / 2, token_id)), ], - outputs: vec![(OutputTarget::Change, amount.clone())], + outputs: vec![(OutputTarget::Change, amount)], changes: vec![ //(TransactionEntity::Total, token_id, 0), ], @@ -465,7 +463,7 @@ mod tests { // Output to someone else, should show their address and total of output + fee TxOutReportTest { inputs: vec![(InputType::Owned, Amount::new(amount.value + fee, token_id))], - outputs: vec![(OutputTarget::Other, amount.clone())], + outputs: vec![(OutputTarget::Other, amount)], changes: vec![( TransactionEntity::OtherAddress(ShortAddressHash::from(&target_subaddress)), token_id, @@ -499,7 +497,7 @@ mod tests { // The total is the change to _our_ balance spent during the transaction (token_id, TotalKind::Ours, (10_000 + fee) as i64), // And the SCI input - (TokenId::from(2), TotalKind::Sci, (200) as i64), + (TokenId::from(2), TotalKind::Sci, 200_i64), ], }, // Partial SCI @@ -531,14 +529,14 @@ mod tests { // The total is the change to _our_ balance spent during the transaction (token_id, TotalKind::Ours, (7_500 + fee) as i64), // And the SCI input - partial value returned - (TokenId::from(2), TotalKind::Sci, (150) as i64), + (TokenId::from(2), TotalKind::Sci, 150_i64), ], }, ]; // Run tests for t in tests { - println!("Running test: {:?}", t); + println!("Running test: {t:?}"); // Setup verifier let mut report = TxSummaryUnblindingReport::<16>::default(); @@ -547,16 +545,16 @@ mod tests { BlockVersion::THREE, t.outputs.len(), t.inputs.len(), - sender.view_private_key().clone(), + *sender.view_private_key(), PublicSubaddress { - view_public: change_subaddress.view_public_key().clone().into(), - spend_public: change_subaddress.spend_public_key().clone().into(), + view_public: (*change_subaddress.view_public_key()).into(), + spend_public: (*change_subaddress.spend_public_key()).into(), }, ); // Build and process TxOuts for (target, amount) in &t.outputs { - println!("Add output {:?}: {:?}", target, amount); + println!("Add output {target:?}: {amount:?}"); // Select target address let receive_subaddress = match target { @@ -574,8 +572,8 @@ mod tests { // Construct TxOut object let tx_out = TxOut::new( BlockVersion::THREE, - amount.clone(), - &receive_subaddress, + *amount, + receive_subaddress, &tx_private_key, Default::default(), ) @@ -622,7 +620,7 @@ mod tests { // Build and process TxIns? for (kind, amount) in &t.inputs { - println!("Add input: {:?}", amount); + println!("Add input: {amount:?}"); // Setup keys for TxOut (kx against sender key as this is an input) let tx_private_key = RistrettoPrivate::from_random(&mut rng); @@ -632,7 +630,7 @@ mod tests { // Construct TxOut object let tx_out = TxOut::new( BlockVersion::THREE, - amount.clone(), + *amount, &sender_subaddress, &tx_private_key, Default::default(), @@ -647,7 +645,7 @@ mod tests { InputType::Sci => vec![0u8; 32], }; let tx_in_summary = TxInSummary { - pseudo_output_commitment: masked_amount.commitment().clone(), + pseudo_output_commitment: *masked_amount.commitment(), input_rules_digest, }; From 6110326972f147ff6de436bdf6b5644a8c4cb152 Mon Sep 17 00:00:00 2001 From: ryan kurte Date: Sat, 23 Sep 2023 12:40:45 +1200 Subject: [PATCH 08/11] swap transaction summary report to 128-bit types --- transaction/extra/tests/verifier.rs | 30 ++++++++-------- transaction/summary/src/report.rs | 53 +++++++++++++++++++---------- transaction/summary/src/verifier.rs | 26 +++++++------- 3 files changed, 63 insertions(+), 46 deletions(-) diff --git a/transaction/extra/tests/verifier.rs b/transaction/extra/tests/verifier.rs index 8b009f02c2..5ba8c449c9 100644 --- a/transaction/extra/tests/verifier.rs +++ b/transaction/extra/tests/verifier.rs @@ -352,7 +352,7 @@ fn test_two_input_tx_with_change_tx_summary_verification() { &[( token_id, TotalKind::Ours, - (value + value2 - change_value) as i64 + (value + value2 - change_value) as i128 ),] ); assert_eq!( @@ -360,7 +360,7 @@ fn test_two_input_tx_with_change_tx_summary_verification() { &[( TransactionEntity::OtherAddress(recipient_hash), token_id, - (value + value2 - change_value - Mob::MINIMUM_FEE) + (value + value2 - change_value - Mob::MINIMUM_FEE) as u128 ),] ); assert_eq!(report.network_fee, Amount::new(Mob::MINIMUM_FEE, token_id)); @@ -441,14 +441,14 @@ fn test_simple_tx_with_change_tx_summary_verification() { let recipient_hash = ShortAddressHash::from(&recipient.default_subaddress()); assert_eq!( &report.totals, - &[(token_id, TotalKind::Ours, ((value - change_value) as i64)),] + &[(token_id, TotalKind::Ours, ((value - change_value) as i128)),] ); assert_eq!( &report.outputs, &[( TransactionEntity::OtherAddress(recipient_hash), token_id, - (value - change_value - Mob::MINIMUM_FEE) + (value - change_value - Mob::MINIMUM_FEE) as u128 ),] ); assert_eq!(report.network_fee, Amount::new(Mob::MINIMUM_FEE, token_id)); @@ -536,19 +536,19 @@ fn test_two_output_tx_with_change_tx_summary_verification() { &[( token_id, TotalKind::Ours, - (value + value2 + Mob::MINIMUM_FEE) as i64 + (value + value2 + Mob::MINIMUM_FEE) as i128 ),] ); let mut outputs = vec![ ( TransactionEntity::OtherAddress(recipient_hash), token_id, - value, + value as u128, ), ( TransactionEntity::OtherAddress(recipient2_hash), token_id, - value2, + value2 as u128, ), ]; outputs.sort(); @@ -666,19 +666,19 @@ fn test_sci_tx_summary_verification() { &report.totals, &[ // Bob spends 3x worth of token id 2 in the transaction - (token2, TotalKind::Ours, value2 as i64), + (token2, TotalKind::Ours, value2 as i128), // SCI inputs used in the transaction - (Mob::ID, TotalKind::Sci, value as i64), + (Mob::ID, TotalKind::Sci, value as i128), ] ); let mut outputs = vec![ // Output to swap counterparty - (TransactionEntity::Swap, token2, value2), + (TransactionEntity::Swap, token2, value2 as u128), // Converted output to ourself ( TransactionEntity::OurAddress(bob_hash), Mob::ID, - value - Mob::MINIMUM_FEE, + (value - Mob::MINIMUM_FEE) as u128, ), ]; outputs.sort(); @@ -797,9 +797,9 @@ fn test_sci_three_way_tx_summary_verification() { &report.totals, &[ // Bob's spend to create the transaction - (token2, TotalKind::Ours, value2 as i64), + (token2, TotalKind::Ours, value2 as i128), // SCI inputs used in the transaction - (Mob::ID, TotalKind::Sci, value as i64), + (Mob::ID, TotalKind::Sci, value as i128), ] ); let mut outputs = vec![ @@ -807,10 +807,10 @@ fn test_sci_three_way_tx_summary_verification() { ( TransactionEntity::OtherAddress(charlie_hash), Mob::ID, - (value - Mob::MINIMUM_FEE), + (value - Mob::MINIMUM_FEE) as u128, ), // Output to swap counterparty - (TransactionEntity::Swap, token2, value2), + (TransactionEntity::Swap, token2, value2 as u128), ]; outputs.sort(); assert_eq!(&report.outputs[..], &outputs[..]); diff --git a/transaction/summary/src/report.rs b/transaction/summary/src/report.rs index a51df48d6d..fda1cc86fa 100644 --- a/transaction/summary/src/report.rs +++ b/transaction/summary/src/report.rs @@ -105,14 +105,15 @@ pub const MAX_TOTALS: usize = 4; /// balanced. For each token, totals = our inputs - sum(change outputs) == /// sum(other outputs) + fee /// -/// SCI inputs are currently ignored +/// SCI inputs are also summed, and can be elided from the report with +/// [TxSummaryUnblindingReport::elide_swap_totals] #[derive(Clone, Debug, Default)] pub struct TxSummaryUnblindingReport< const RECORDS: usize = MAX_RECORDS, const TOTALS: usize = MAX_TOTALS, > { /// Transaction outputs aggregated by address and token type - pub outputs: Vec<(TransactionEntity, TokenId, u64), RECORDS>, + pub outputs: Vec<(TransactionEntity, TokenId, u128), RECORDS>, /// Total balance change for our account for each type of token in the /// transaction. @@ -120,10 +121,11 @@ pub struct TxSummaryUnblindingReport< /// totals = inputs - sum(change outputs) /// /// Note that owned and swap inputs are split as most - /// applications are concerned only with the _cost_ of + /// applications are concerned only with the cost of /// the transaction to the user. + /// /// See [elide_swap_totals] for more detail. - pub totals: Vec<(TokenId, TotalKind, i64), TOTALS>, + pub totals: Vec<(TokenId, TotalKind, i128), TOTALS>, /// The network fee that we pay to execute the transaction pub network_fee: Amount, @@ -151,7 +153,7 @@ impl TransactionReport let Amount { token_id, value } = amount; // Ensure value will not overflow - let value = i64::try_from(value).map_err(|_| Error::NumericOverflow)?; + let value = i128::try_from(value).map_err(|_| Error::NumericOverflow)?; // Check for existing total entry for this token match self @@ -176,7 +178,7 @@ impl TransactionReport let Amount { token_id, value } = amount; // Ensure value will not overflow - let value = i64::try_from(value).map_err(|_| Error::NumericOverflow)?; + let value = i128::try_from(value).map_err(|_| Error::NumericOverflow)?; // Check for existing total entry for this token match self @@ -185,7 +187,11 @@ impl TransactionReport .find(|(t, k, _)| t == &token_id && *k == TotalKind::Ours) { // If we have an entry, subtract the change value from this - Some(v) => v.2 = v.2.checked_sub(value).ok_or(Error::NumericOverflow)?, + Some(v) => { + v.2 = + v.2.checked_sub(value as i128) + .ok_or(Error::NumericOverflow)? + } // If we do not, create a new entry None => self .totals @@ -201,7 +207,7 @@ impl TransactionReport let Amount { token_id, value } = amount; // Ensure value will not overflow - let value = i64::try_from(value).map_err(|_| Error::NumericOverflow)?; + let value = i128::try_from(value).map_err(|_| Error::NumericOverflow)?; // Check for existing total entry for this token match self @@ -210,7 +216,11 @@ impl TransactionReport .find(|(t, k, _)| t == &token_id && *k == TotalKind::Sci) { // If we have an entry, add the value to this - Some(v) => v.2 = v.2.checked_add(value).ok_or(Error::NumericOverflow)?, + Some(v) => { + v.2 = + v.2.checked_add(value as i128) + .ok_or(Error::NumericOverflow)? + } // If we do not, create a new entry None => self .totals @@ -224,6 +234,9 @@ impl TransactionReport fn output_add(&mut self, entity: TransactionEntity, amount: Amount) -> Result<(), Error> { let Amount { token_id, value } = amount; + // Ensure value will not overflow + let value = u128::try_from(value).map_err(|_| Error::NumericOverflow)?; + // Check for existing output for this address match self .outputs @@ -263,11 +276,9 @@ impl TransactionReport self.sort(); // For each token id, check that inputs match outputs - // (this is only executed where _totals_ exist, so skipped - // for the current SCI implementation) for (token_id, total_kind, value) in &mut self.totals { // Sum outputs for this token id - let mut balance = 0u64; + let mut balance = 0i128; for (e, id, v) in &self.outputs { // Skip other tokens if id != token_id { @@ -279,16 +290,22 @@ impl TransactionReport match total_kind { // If it's coming from our account, track total balance TotalKind::Ours => { - balance = balance.checked_add(*v).ok_or(Error::NumericOverflow)?; + balance = balance + .checked_add(*v as i128) + .ok_or(Error::NumericOverflow)?; } // If it's coming from an SCI, and returned to the counterparty, reduce total by // outgoing value TotalKind::Sci if e == &TransactionEntity::Swap => { - *value = value.checked_sub(*v as i64).ok_or(Error::NumericOverflow)?; + *value = value + .checked_sub(*v as i128) + .ok_or(Error::NumericOverflow)?; } // If it's coming from an SCI to us, add to total balance TotalKind::Sci if e != &TransactionEntity::Swap => { - balance = balance.checked_add(*v).ok_or(Error::NumericOverflow)?; + balance = balance + .checked_add(*v as i128) + .ok_or(Error::NumericOverflow)?; } _ => (), } @@ -297,12 +314,12 @@ impl TransactionReport // Add network fee for matching token id if &self.network_fee.token_id == token_id { balance = balance - .checked_add(self.network_fee.value) + .checked_add(self.network_fee.value as i128) .ok_or(Error::NumericOverflow)?; } // Check that the balance matches the total - if balance != *value as u64 { + if balance != *value { return Err(Error::AmountVerificationFailed); } } @@ -388,7 +405,7 @@ mod tests { #[test] fn test_report_size() { - assert_eq!(core::mem::size_of::(), 1416); + assert_eq!(core::mem::size_of::(), 1704); } #[test] diff --git a/transaction/summary/src/verifier.rs b/transaction/summary/src/verifier.rs index fdc48d14ee..8ea4996f53 100644 --- a/transaction/summary/src/verifier.rs +++ b/transaction/summary/src/verifier.rs @@ -388,9 +388,9 @@ mod tests { /// Outputs produced by the transaction outputs: Vec<(OutputTarget, Amount)>, /// Totals / balances by token - totals: Vec<(TokenId, TotalKind, i64)>, + totals: Vec<(TokenId, TotalKind, i128)>, /// Changes produced by the transaction - changes: Vec<(TransactionEntity, TokenId, u64)>, + changes: Vec<(TransactionEntity, TokenId, u128)>, } #[derive(Clone, Debug, PartialEq)] @@ -441,9 +441,9 @@ mod tests { changes: vec![( TransactionEntity::OurAddress(ShortAddressHash::from(&sender_subaddress)), token_id, - amount.value, + amount.value as u128, )], - totals: vec![(token_id, TotalKind::Ours, (amount.value + fee) as i64)], + totals: vec![(token_id, TotalKind::Ours, (amount.value + fee) as i128)], }, // Output to our change address, should show no outputs with balance change = fee TxOutReportTest { @@ -458,7 +458,7 @@ mod tests { changes: vec![ //(TransactionEntity::Total, token_id, 0), ], - totals: vec![(token_id, TotalKind::Ours, fee as i64)], + totals: vec![(token_id, TotalKind::Ours, fee as i128)], }, // Output to someone else, should show their address and total of output + fee TxOutReportTest { @@ -467,9 +467,9 @@ mod tests { changes: vec![( TransactionEntity::OtherAddress(ShortAddressHash::from(&target_subaddress)), token_id, - amount.value, + amount.value as u128, )], - totals: vec![(token_id, TotalKind::Ours, (amount.value + fee) as i64)], + totals: vec![(token_id, TotalKind::Ours, (amount.value + fee) as i128)], }, // Basic SCI. consuming entire swap, inputs should not count towards totals TxOutReportTest { @@ -489,15 +489,15 @@ mod tests { ( TransactionEntity::OurAddress(ShortAddressHash::from(&sender_subaddress)), TokenId::from(2), - 200, + 200_u128, ), - (TransactionEntity::Swap, token_id, 10_000), + (TransactionEntity::Swap, token_id, 10_000_u128), ], totals: vec![ // The total is the change to _our_ balance spent during the transaction - (token_id, TotalKind::Ours, (10_000 + fee) as i64), + (token_id, TotalKind::Ours, (10_000 + fee) as i128), // And the SCI input - (TokenId::from(2), TotalKind::Sci, 200_i64), + (TokenId::from(2), TotalKind::Sci, 200_i128), ], }, // Partial SCI @@ -527,9 +527,9 @@ mod tests { ], totals: vec![ // The total is the change to _our_ balance spent during the transaction - (token_id, TotalKind::Ours, (7_500 + fee) as i64), + (token_id, TotalKind::Ours, (7_500 + fee) as i128), // And the SCI input - partial value returned - (TokenId::from(2), TotalKind::Sci, 150_i64), + (TokenId::from(2), TotalKind::Sci, 150_i128), ], }, ]; From 80cf635e8b5b6752bd17e97cd4a065d8f7faacb1 Mon Sep 17 00:00:00 2001 From: Henry Holtzman Date: Thu, 1 Aug 2024 15:51:36 -0700 Subject: [PATCH 09/11] lint fix; update Cargo.lock --- Cargo.lock | 46 +++++++++++++++++++++++++++-- transaction/summary/src/verifier.rs | 5 +--- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6e36c12fd4..84e64868fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -283,6 +283,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "atty" version = "0.2.14" @@ -923,6 +932,12 @@ dependencies = [ "itertools 0.10.5", ] +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + [[package]] name = "crossbeam-channel" version = "0.5.12" @@ -1848,6 +1863,15 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hash32" version = "0.3.1" @@ -1898,13 +1922,26 @@ dependencies = [ "http", ] +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32 0.2.1", + "rustc_version", + "spin 0.9.6", + "stable_deref_trait", +] + [[package]] name = "heapless" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ - "hash32", + "hash32 0.3.1", "stable_deref_trait", ] @@ -5968,7 +6005,7 @@ name = "mc-transaction-summary" version = "6.0.2" dependencies = [ "displaydoc", - "heapless", + "heapless 0.7.17", "mc-account-keys", "mc-core", "mc-crypto-digestible", @@ -6374,7 +6411,7 @@ name = "mc-util-vec-map" version = "6.0.2" dependencies = [ "displaydoc", - "heapless", + "heapless 0.8.0", ] [[package]] @@ -8542,6 +8579,9 @@ name = "spin" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5d6e0250b93c8427a177b849d144a96d5acc57006149479403d7861ab721e34" +dependencies = [ + "lock_api", +] [[package]] name = "spki" diff --git a/transaction/summary/src/verifier.rs b/transaction/summary/src/verifier.rs index 6d03d1abba..a0f2c5f41d 100644 --- a/transaction/summary/src/verifier.rs +++ b/transaction/summary/src/verifier.rs @@ -167,10 +167,7 @@ impl TxSummaryStreamingVerifierCtx { Self::expected_tx_out_summary(self.block_version, amount, address, tx_private_key)?; if &expected == tx_out_summary { // Add as an output to the report - report.output_add( - TransactionEntity::OtherAddress(*address_hash), - amount, - )?; + report.output_add(TransactionEntity::OtherAddress(*address_hash), amount)?; } else { return Err(Error::AddressVerificationFailed); } From 0cf5f7e232399b999ba20346e1154b165ae2f356 Mon Sep 17 00:00:00 2001 From: Henry Holtzman Date: Thu, 1 Aug 2024 16:15:37 -0700 Subject: [PATCH 10/11] lint fixes --- transaction/summary/src/report.rs | 4 ++-- transaction/summary/src/verifier.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/transaction/summary/src/report.rs b/transaction/summary/src/report.rs index fda1cc86fa..c590e8e4a6 100644 --- a/transaction/summary/src/report.rs +++ b/transaction/summary/src/report.rs @@ -189,7 +189,7 @@ impl TransactionReport // If we have an entry, subtract the change value from this Some(v) => { v.2 = - v.2.checked_sub(value as i128) + v.2.checked_sub(value) .ok_or(Error::NumericOverflow)? } // If we do not, create a new entry @@ -218,7 +218,7 @@ impl TransactionReport // If we have an entry, add the value to this Some(v) => { v.2 = - v.2.checked_add(value as i128) + v.2.checked_add(value) .ok_or(Error::NumericOverflow)? } // If we do not, create a new entry diff --git a/transaction/summary/src/verifier.rs b/transaction/summary/src/verifier.rs index a0f2c5f41d..3bdbb69fcd 100644 --- a/transaction/summary/src/verifier.rs +++ b/transaction/summary/src/verifier.rs @@ -146,7 +146,7 @@ impl TxSummaryStreamingVerifierCtx { } else { // Otherwise, add this as an output to ourself report - .output_add(TransactionEntity::OurAddress(address_hash.clone()), amount)?; + .output_add(TransactionEntity::OurAddress(*address_hash), amount)?; } } else { // If we _don't_ have address information but it's to our own address... From bd88579060aa1125c0c95d38357d67792eb4e451 Mon Sep 17 00:00:00 2001 From: Henry Holtzman Date: Thu, 1 Aug 2024 16:33:45 -0700 Subject: [PATCH 11/11] lint fixes --- transaction/summary/src/report.rs | 12 ++---------- transaction/summary/src/verifier.rs | 3 +-- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/transaction/summary/src/report.rs b/transaction/summary/src/report.rs index c590e8e4a6..61a21013b7 100644 --- a/transaction/summary/src/report.rs +++ b/transaction/summary/src/report.rs @@ -187,11 +187,7 @@ impl TransactionReport .find(|(t, k, _)| t == &token_id && *k == TotalKind::Ours) { // If we have an entry, subtract the change value from this - Some(v) => { - v.2 = - v.2.checked_sub(value) - .ok_or(Error::NumericOverflow)? - } + Some(v) => v.2 = v.2.checked_sub(value).ok_or(Error::NumericOverflow)?, // If we do not, create a new entry None => self .totals @@ -216,11 +212,7 @@ impl TransactionReport .find(|(t, k, _)| t == &token_id && *k == TotalKind::Sci) { // If we have an entry, add the value to this - Some(v) => { - v.2 = - v.2.checked_add(value) - .ok_or(Error::NumericOverflow)? - } + Some(v) => v.2 = v.2.checked_add(value).ok_or(Error::NumericOverflow)?, // If we do not, create a new entry None => self .totals diff --git a/transaction/summary/src/verifier.rs b/transaction/summary/src/verifier.rs index 3bdbb69fcd..fc421384ad 100644 --- a/transaction/summary/src/verifier.rs +++ b/transaction/summary/src/verifier.rs @@ -145,8 +145,7 @@ impl TxSummaryStreamingVerifierCtx { report.change_sub(amount)?; } else { // Otherwise, add this as an output to ourself - report - .output_add(TransactionEntity::OurAddress(*address_hash), amount)?; + report.output_add(TransactionEntity::OurAddress(*address_hash), amount)?; } } else { // If we _don't_ have address information but it's to our own address...