From e83521d0b4499a564e384d61ef6c1dadbfd64356 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Fri, 11 Aug 2023 03:04:38 +0200 Subject: [PATCH] Made Amount more generic. (#52) --- masp_primitives/Cargo.toml | 3 + masp_primitives/src/convert.rs | 33 +- masp_primitives/src/sapling/prover.rs | 8 +- masp_primitives/src/transaction.rs | 14 +- masp_primitives/src/transaction/builder.rs | 61 +- masp_primitives/src/transaction/components.rs | 4 +- .../src/transaction/components/amount.rs | 671 +++++++++++++----- .../src/transaction/components/sapling.rs | 8 +- .../transaction/components/sapling/builder.rs | 25 +- .../src/transaction/components/transparent.rs | 36 +- .../components/transparent/builder.rs | 36 +- .../components/transparent/fees.rs | 4 +- masp_primitives/src/transaction/fees.rs | 4 +- masp_primitives/src/transaction/fees/fixed.rs | 10 +- masp_primitives/src/transaction/sighash.rs | 2 +- masp_proofs/benches/convert.rs | 16 +- masp_proofs/src/circuit/convert.rs | 14 +- masp_proofs/src/prover.rs | 4 +- masp_proofs/src/sapling/mod.rs | 11 +- masp_proofs/src/sapling/prover.rs | 6 +- masp_proofs/src/sapling/verifier.rs | 4 +- masp_proofs/src/sapling/verifier/single.rs | 4 +- 22 files changed, 621 insertions(+), 357 deletions(-) diff --git a/masp_primitives/Cargo.toml b/masp_primitives/Cargo.toml index 31dd3c75..9eb2bf48 100644 --- a/masp_primitives/Cargo.toml +++ b/masp_primitives/Cargo.toml @@ -36,6 +36,9 @@ sha2 = "0.9" # - Metrics memuse = "0.2.1" +# - Checked arithmetic +num-traits = "0.2.14" + # - Secret management subtle = "2.2.3" diff --git a/masp_primitives/src/convert.rs b/masp_primitives/src/convert.rs index 82d78a36..5a345cf5 100644 --- a/masp_primitives/src/convert.rs +++ b/masp_primitives/src/convert.rs @@ -3,7 +3,7 @@ use crate::{ pedersen_hash::{pedersen_hash, Personalization}, Node, ValueCommitment, }, - transaction::components::amount::Amount, + transaction::components::amount::{I32Sum, ValueSum}, }; use borsh::{BorshDeserialize, BorshSerialize}; use group::{Curve, GroupEncoding}; @@ -16,7 +16,7 @@ use std::{ #[derive(Clone, Debug, PartialEq, Eq)] pub struct AllowedConversion { /// The asset type that the note represents - assets: Amount, + assets: I32Sum, /// Memorize generator because it's expensive to recompute generator: jubjub::ExtendedPoint, } @@ -71,15 +71,15 @@ impl AllowedConversion { } } -impl From for Amount { - fn from(allowed_conversion: AllowedConversion) -> Amount { +impl From for I32Sum { + fn from(allowed_conversion: AllowedConversion) -> I32Sum { allowed_conversion.assets } } -impl From for AllowedConversion { +impl From for AllowedConversion { /// Produces an asset generator without cofactor cleared - fn from(assets: Amount) -> Self { + fn from(assets: I32Sum) -> Self { let mut asset_generator = jubjub::ExtendedPoint::identity(); for (asset, value) in assets.components() { // Compute the absolute value (failing if -i64::MAX is @@ -123,7 +123,7 @@ impl BorshDeserialize for AllowedConversion { /// computation of checking whether the asset generator corresponds to the /// deserialized amount. fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { - let assets = Amount::read(buf)?; + let assets = I32Sum::read(buf)?; let gen_bytes = <::Repr as BorshDeserialize>::deserialize(buf)?; let generator = Option::from(jubjub::ExtendedPoint::from_bytes(&gen_bytes)) @@ -174,7 +174,7 @@ impl SubAssign for AllowedConversion { impl Sum for AllowedConversion { fn sum>(iter: I) -> Self { - iter.fold(AllowedConversion::from(Amount::zero()), Add::add) + iter.fold(AllowedConversion::from(ValueSum::zero()), Add::add) } } @@ -182,7 +182,7 @@ impl Sum for AllowedConversion { mod tests { use crate::asset_type::AssetType; use crate::convert::AllowedConversion; - use crate::transaction::components::amount::Amount; + use crate::transaction::components::amount::ValueSum; /// Generate ZEC asset type fn zec() -> AssetType { @@ -199,11 +199,12 @@ mod tests { #[test] fn test_homomorphism() { // Left operand - let a = Amount::from_pair(zec(), 5).unwrap() - + Amount::from_pair(btc(), 6).unwrap() - + Amount::from_pair(xan(), 7).unwrap(); + let a = ValueSum::from_pair(zec(), 5i32).unwrap() + + ValueSum::from_pair(btc(), 6i32).unwrap() + + ValueSum::from_pair(xan(), 7i32).unwrap(); // Right operand - let b = Amount::from_pair(zec(), 2).unwrap() + Amount::from_pair(xan(), 10).unwrap(); + let b = + ValueSum::from_pair(zec(), 2i32).unwrap() + ValueSum::from_pair(xan(), 10i32).unwrap(); // Test homomorphism assert_eq!( AllowedConversion::from(a.clone() + b.clone()), @@ -213,9 +214,9 @@ mod tests { #[test] fn test_serialization() { // Make conversion - let a: AllowedConversion = (Amount::from_pair(zec(), 5).unwrap() - + Amount::from_pair(btc(), 6).unwrap() - + Amount::from_pair(xan(), 7).unwrap()) + let a: AllowedConversion = (ValueSum::from_pair(zec(), 5i32).unwrap() + + ValueSum::from_pair(btc(), 6i32).unwrap() + + ValueSum::from_pair(xan(), 7i32).unwrap()) .into(); // Serialize conversion let mut data = Vec::new(); diff --git a/masp_primitives/src/sapling/prover.rs b/masp_primitives/src/sapling/prover.rs index 03a442fb..946641d1 100644 --- a/masp_primitives/src/sapling/prover.rs +++ b/masp_primitives/src/sapling/prover.rs @@ -8,7 +8,7 @@ use crate::{ redjubjub::{PublicKey, Signature}, Node, }, - transaction::components::{Amount, GROTH_PROOF_SIZE}, + transaction::components::{I128Sum, GROTH_PROOF_SIZE}, }; use super::{Diversifier, PaymentAddress, ProofGenerationKey, Rseed}; @@ -73,7 +73,7 @@ pub trait TxProver { fn binding_sig( &self, ctx: &mut Self::SaplingProvingContext, - amount: &Amount, + amount: &I128Sum, sighash: &[u8; 32], ) -> Result; } @@ -92,7 +92,7 @@ pub mod mock { redjubjub::{PublicKey, Signature}, Diversifier, Node, PaymentAddress, ProofGenerationKey, Rseed, }, - transaction::components::{Amount, GROTH_PROOF_SIZE}, + transaction::components::{I128Sum, GROTH_PROOF_SIZE}, }; use super::TxProver; @@ -169,7 +169,7 @@ pub mod mock { fn binding_sig( &self, _ctx: &mut Self::SaplingProvingContext, - _value: &Amount, + _value: &I128Sum, _sighash: &[u8; 32], ) -> Result { Err(()) diff --git a/masp_primitives/src/transaction.rs b/masp_primitives/src/transaction.rs index 3938ee30..4525b425 100644 --- a/masp_primitives/src/transaction.rs +++ b/masp_primitives/src/transaction.rs @@ -25,7 +25,7 @@ use crate::{ use self::{ components::{ - amount::Amount, + amount::{I128Sum, ValueSum}, sapling::{ self, ConvertDescriptionV5, OutputDescriptionV5, SpendDescription, SpendDescriptionV5, }, @@ -269,10 +269,10 @@ impl TransactionData { } impl TransactionData { - pub fn sapling_value_balance(&self) -> Amount { + pub fn sapling_value_balance(&self) -> I128Sum { self.sapling_bundle .as_ref() - .map_or(Amount::zero(), |b| b.value_balance.clone()) + .map_or(ValueSum::zero(), |b| b.value_balance.clone()) } } @@ -355,8 +355,8 @@ impl Transaction { }) } - fn read_amount(mut reader: R) -> io::Result { - Amount::read(&mut reader).map_err(|_| { + fn read_i128_sum(mut reader: R) -> io::Result { + I128Sum::read(&mut reader).map_err(|_| { io::Error::new( io::ErrorKind::InvalidData, "Amount valueBalance out of range", @@ -407,9 +407,9 @@ impl Transaction { let n_converts = cd_v5s.len(); let n_outputs = od_v5s.len(); let value_balance = if n_spends > 0 || n_outputs > 0 { - Self::read_amount(&mut reader)? + Self::read_i128_sum(&mut reader)? } else { - Amount::zero() + ValueSum::zero() }; let spend_anchor = if n_spends > 0 { diff --git a/masp_primitives/src/transaction/builder.rs b/masp_primitives/src/transaction/builder.rs index 63681b3b..fc65dcfd 100644 --- a/masp_primitives/src/transaction/builder.rs +++ b/masp_primitives/src/transaction/builder.rs @@ -1,6 +1,5 @@ //! Structs for building transactions. -use std::convert::TryInto; use std::error; use std::fmt; use std::sync::mpsc::Sender; @@ -19,7 +18,7 @@ use crate::{ sapling::{prover::TxProver, Diversifier, Node, Note, PaymentAddress}, transaction::{ components::{ - amount::{Amount, BalanceError, MAX_MONEY}, + amount::{BalanceError, I128Sum, U64Sum, ValueSum, MAX_MONEY}, sapling::{ self, builder::{SaplingBuilder, SaplingMetadata}, @@ -43,10 +42,10 @@ const DEFAULT_TX_EXPIRY_DELTA: u32 = 20; pub enum Error { /// Insufficient funds were provided to the transaction builder; the given /// additional amount is required in order to construct the transaction. - InsufficientFunds(Amount), + InsufficientFunds(I128Sum), /// The transaction has inputs in excess of outputs and fees; the user must /// add a change output. - ChangeRequired(Amount), + ChangeRequired(U64Sum), /// An error occurred in computing the fees for a transaction. Fee(FeeError), /// An overflow or underflow occurred when computing value balances @@ -251,7 +250,7 @@ impl Builder { value: u64, memo: MemoBytes, ) -> Result<(), sapling::builder::Error> { - if value > MAX_MONEY.try_into().unwrap() { + if value > MAX_MONEY { return Err(sapling::builder::Error::InvalidAmount); } self.sapling_builder @@ -273,9 +272,9 @@ impl Builder { &mut self, to: &TransparentAddress, asset_type: AssetType, - value: i64, + value: u64, ) -> Result<(), transparent::builder::Error> { - if value < 0 || value > MAX_MONEY { + if value > MAX_MONEY { return Err(transparent::builder::Error::InvalidAmount); } @@ -293,13 +292,13 @@ impl Builder { } /// Returns the sum of the transparent, Sapling, and TZE value balances. - pub fn value_balance(&self) -> Result { + pub fn value_balance(&self) -> Result { let value_balances = [ self.transparent_builder.value_balance()?, self.sapling_builder.value_balance(), ]; - Ok(value_balances.into_iter().sum::()) + Ok(value_balances.into_iter().sum::()) } /// Builds a transaction from the configured spends and outputs. @@ -326,7 +325,7 @@ impl Builder { fn build_internal( self, prover: &impl TxProver, - fee: Amount, + fee: U64Sum, ) -> Result<(Transaction, SaplingMetadata), Error> { let consensus_branch_id = BranchId::for_height(&self.params, self.target_height); @@ -338,9 +337,9 @@ impl Builder { // // After fees are accounted for, the value balance of the transaction must be zero. - let balance_after_fees = self.value_balance()? - fee; + let balance_after_fees = self.value_balance()? - I128Sum::from_sum(fee); - if balance_after_fees != Amount::zero() { + if balance_after_fees != ValueSum::zero() { return Err(Error::InsufficientFunds(-balance_after_fees)); }; @@ -480,9 +479,8 @@ mod tests { merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::Rseed, transaction::{ - components::amount::{Amount, DEFAULT_FEE, MAX_MONEY}, - sapling::builder::{self as build_s}, - transparent::builder::{self as build_t}, + components::amount::{I128Sum, ValueSum, DEFAULT_FEE}, + sapling::builder as build_s, TransparentAddress, }, zip32::ExtendedSpendingKey, @@ -490,7 +488,7 @@ mod tests { use super::{Builder, Error}; - #[test] + /*#[test] fn fails_on_overflow_output() { let extsk = ExtendedSpendingKey::master(&[]); let dfvk = extsk.to_diversifiable_full_viewing_key(); @@ -507,12 +505,12 @@ mod tests { Some(ovk), to, zec(), - MAX_MONEY as u64 + 1, + MAX_MONEY + 1, MemoBytes::empty() ), Err(build_s::Error::InvalidAmount) ); - } + }*/ /// Generate ZEC asset type fn zec() -> AssetType { @@ -565,21 +563,6 @@ mod tests { ); } - #[test] - fn fails_on_negative_transparent_output() { - let mut rng = OsRng; - - let transparent_address = TransparentAddress(rng.gen::<[u8; 20]>()); - let tx_height = TEST_NETWORK - .activation_height(NetworkUpgrade::MASP) - .unwrap(); - let mut builder = Builder::new(TEST_NETWORK, tx_height); - assert_eq!( - builder.add_transparent_output(&transparent_address, zec(), -1,), - Err(build_t::Error::InvalidAmount) - ); - } - #[test] fn fails_on_negative_change() { let mut rng = OsRng; @@ -597,7 +580,9 @@ mod tests { let builder = Builder::new(TEST_NETWORK, tx_height); assert_eq!( builder.mock_build(), - Err(Error::InsufficientFunds(DEFAULT_FEE.clone())) + Err(Error::InsufficientFunds(I128Sum::from_sum( + DEFAULT_FEE.clone() + ))) ); } @@ -615,7 +600,8 @@ mod tests { assert_eq!( builder.mock_build(), Err(Error::InsufficientFunds( - Amount::from_pair(zec(), 50000).unwrap() + &*DEFAULT_FEE + I128Sum::from_pair(zec(), 50000).unwrap() + + &I128Sum::from_sum(DEFAULT_FEE.clone()) )) ); } @@ -630,7 +616,8 @@ mod tests { assert_eq!( builder.mock_build(), Err(Error::InsufficientFunds( - Amount::from_pair(zec(), 50000).unwrap() + &*DEFAULT_FEE + I128Sum::from_pair(zec(), 50000).unwrap() + + &I128Sum::from_sum(DEFAULT_FEE.clone()) )) ); } @@ -663,7 +650,7 @@ mod tests { assert_eq!( builder.mock_build(), Err(Error::InsufficientFunds( - Amount::from_pair(zec(), 1).unwrap() + ValueSum::from_pair(zec(), 1).unwrap() )) ); } diff --git a/masp_primitives/src/transaction/components.rs b/masp_primitives/src/transaction/components.rs index db07c9a5..cc041955 100644 --- a/masp_primitives/src/transaction/components.rs +++ b/masp_primitives/src/transaction/components.rs @@ -4,7 +4,9 @@ pub mod amount; pub mod sapling; pub mod transparent; pub use self::{ - amount::Amount, + amount::{ + I128Sum, I16Sum, I32Sum, I64Sum, I8Sum, U128Sum, U16Sum, U32Sum, U64Sum, U8Sum, ValueSum, + }, sapling::{ConvertDescription, OutputDescription, SpendDescription}, transparent::{TxIn, TxOut}, }; diff --git a/masp_primitives/src/transaction/components/amount.rs b/masp_primitives/src/transaction/components/amount.rs index 49da8501..fac323c5 100644 --- a/masp_primitives/src/transaction/components/amount.rs +++ b/masp_primitives/src/transaction/components/amount.rs @@ -1,36 +1,62 @@ use crate::asset_type::AssetType; use borsh::{BorshDeserialize, BorshSerialize}; +use num_traits::{CheckedAdd, CheckedMul, CheckedNeg, CheckedSub, One}; use std::cmp::Ordering; use std::collections::btree_map::Keys; use std::collections::btree_map::{IntoIter, Iter}; use std::collections::BTreeMap; -use std::convert::TryInto; use std::hash::Hash; use std::io::{Read, Write}; use std::iter::Sum; use std::ops::{Add, AddAssign, Index, Mul, MulAssign, Neg, Sub, SubAssign}; use zcash_encoding::Vector; -pub const MAX_MONEY: i64 = i64::MAX; +pub const MAX_MONEY: u64 = u64::MAX; lazy_static::lazy_static! { -pub static ref DEFAULT_FEE: Amount = Amount::from_pair(zec(), 1000).unwrap(); +pub static ref DEFAULT_FEE: U64Sum = ValueSum::from_pair(zec(), 1000).unwrap(); } /// A type-safe representation of some quantity of Zcash. /// -/// An Amount can only be constructed from an integer that is within the valid monetary +/// An ValueSum can only be constructed from an integer that is within the valid monetary /// range of `{-MAX_MONEY..MAX_MONEY}` (where `MAX_MONEY` = i64::MAX). /// However, this range is not preserved as an invariant internally; it is possible to -/// add two valid Amounts together to obtain an invalid Amount. It is the user's -/// responsibility to handle the result of serializing potentially-invalid Amounts. In -/// particular, a `Transaction` containing serialized invalid Amounts will be rejected +/// add two valid ValueSums together to obtain an invalid ValueSum. It is the user's +/// responsibility to handle the result of serializing potentially-invalid ValueSums. In +/// particular, a `Transaction` containing serialized invalid ValueSums will be rejected /// by the network consensus rules. /// -#[derive(Clone, Default, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Hash)] -pub struct Amount( - pub BTreeMap, -); -impl memuse::DynamicUsage for Amount { +pub type I8Sum = ValueSum; + +pub type U8Sum = ValueSum; + +pub type I16Sum = ValueSum; + +pub type U16Sum = ValueSum; + +pub type I32Sum = ValueSum; + +pub type U32Sum = ValueSum; + +pub type I64Sum = ValueSum; + +pub type U64Sum = ValueSum; + +pub type I128Sum = ValueSum; + +pub type U128Sum = ValueSum; + +#[derive(Clone, Default, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Hash)] +pub struct ValueSum< + Unit: Hash + Ord + BorshSerialize + BorshDeserialize, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq, +>(pub BTreeMap); + +impl memuse::DynamicUsage for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + Default + PartialOrd, +{ #[inline(always)] fn dynamic_usage(&self) -> usize { unimplemented!() @@ -44,72 +70,128 @@ impl memuse::DynamicUsage for Amount { } } -impl Amount { - /// Returns a zero-valued Amount. - pub fn zero() -> Self { - Amount(BTreeMap::new()) - } - - /// Creates a non-negative Amount from an i64. - /// - /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. - pub fn from_nonnegative>(atype: Unit, amount: Amt) -> Result { - let amount = amount.try_into().map_err(|_| ())?; - if amount == 0 { +impl ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + Default + PartialOrd, +{ + /// Creates a non-negative ValueSum from a Value. + pub fn from_nonnegative(atype: Unit, amount: Value) -> Result { + if amount == Value::default() { Ok(Self::zero()) - } else if 0 <= amount && amount <= MAX_MONEY { + } else if Value::default() <= amount { let mut ret = BTreeMap::new(); ret.insert(atype, amount); - Ok(Amount(ret)) + Ok(ValueSum(ret)) } else { Err(()) } } - /// Creates an Amount from a type convertible to i64. - /// - /// Returns an error if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`. - pub fn from_pair>(atype: Unit, amount: Amt) -> Result { - let amount = amount.try_into().map_err(|_| ())?; - if amount == 0 { +} + +impl ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + Default, +{ + /// Creates an ValueSum from a Value. + pub fn from_pair(atype: Unit, amount: Value) -> Result { + if amount == Value::default() { Ok(Self::zero()) - } else if -MAX_MONEY <= amount && amount <= MAX_MONEY { + } else { let mut ret = BTreeMap::new(); ret.insert(atype, amount); - Ok(Amount(ret)) - } else { - Err(()) + Ok(ValueSum(ret)) } } + /// Filters out everything but the given AssetType from this ValueSum + pub fn project(&self, index: Unit) -> Self { + let val = self.0.get(&index).copied().unwrap_or_default(); + Self::from_pair(index, val).unwrap() + } + + /// Get the given AssetType within this ValueSum + pub fn get(&self, index: &Unit) -> Value { + *self.0.get(index).unwrap_or(&Value::default()) + } +} + +impl ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy, +{ + /// Returns a zero-valued ValueSum. + pub fn zero() -> Self { + ValueSum(BTreeMap::new()) + } + + /// Check if ValueSum is zero + pub fn is_zero(&self) -> bool { + self.0.is_empty() + } + /// Returns an iterator over the amount's non-zero asset-types - pub fn asset_types(&self) -> Keys<'_, Unit, i64> { + pub fn asset_types(&self) -> Keys<'_, Unit, Value> { self.0.keys() } /// Returns an iterator over the amount's non-zero components - pub fn components(&self) -> Iter<'_, Unit, i64> { + pub fn components(&self) -> Iter<'_, Unit, Value> { self.0.iter() } /// Returns an iterator over the amount's non-zero components - pub fn into_components(self) -> IntoIter { + pub fn into_components(self) -> IntoIter { self.0.into_iter() } - /// Filters out everything but the given AssetType from this Amount - pub fn project(&self, index: Unit) -> Self { - let val = self.0.get(&index).copied().unwrap_or(0); - Self::from_pair(index, val).unwrap() + /// Filters out the given AssetType from this ValueSum + pub fn reject(&self, index: Unit) -> Self { + let mut val = self.clone(); + val.0.remove(&index); + val } +} - /// Filters out the given AssetType from this Amount - pub fn reject(&self, index: Unit) -> Self { - self.clone() - self.project(index) +impl ValueSum { + /// Deserialize an ValueSum object from a list of amounts denominated by + /// different assets + pub fn read(reader: &mut R) -> std::io::Result { + let vec = Vector::read(reader, |reader| { + let mut atype = [0; 32]; + let mut value = [0; 4]; + reader.read_exact(&mut atype)?; + reader.read_exact(&mut value)?; + let atype = AssetType::from_identifier(&atype).ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid asset type") + })?; + Ok((atype, i32::from_le_bytes(value))) + })?; + let mut ret = Self::zero(); + for (atype, amt) in vec { + ret += Self::from_pair(atype, amt).map_err(|_| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "amount out of range") + })?; + } + Ok(ret) + } + + /// Serialize an ValueSum object into a list of amounts denominated by + /// distinct asset types + pub fn write(&self, writer: &mut W) -> std::io::Result<()> { + let vec: Vec<_> = self.components().collect(); + Vector::write(writer, vec.as_ref(), |writer, elt| { + writer.write_all(elt.0.get_identifier())?; + writer.write_all(elt.1.to_le_bytes().as_ref())?; + Ok(()) + }) } } -impl Amount { - /// Deserialize an Amount object from a list of amounts denominated by +impl ValueSum { + /// Deserialize an ValueSum object from a list of amounts denominated by /// different assets pub fn read(reader: &mut R) -> std::io::Result { let vec = Vector::read(reader, |reader| { @@ -131,7 +213,7 @@ impl Amount { Ok(ret) } - /// Serialize an Amount object into a list of amounts denominated by + /// Serialize an ValueSum object into a list of amounts denominated by /// distinct asset types pub fn write(&self, writer: &mut W) -> std::io::Result<()> { let vec: Vec<_> = self.components().collect(); @@ -143,180 +225,390 @@ impl Amount { } } -impl From for Amount { +impl ValueSum { + /// Deserialize an ValueSum object from a list of amounts denominated by + /// different assets + pub fn read(reader: &mut R) -> std::io::Result { + let vec = Vector::read(reader, |reader| { + let mut atype = [0; 32]; + let mut value = [0; 16]; + reader.read_exact(&mut atype)?; + reader.read_exact(&mut value)?; + let atype = AssetType::from_identifier(&atype).ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid asset type") + })?; + Ok((atype, i128::from_le_bytes(value))) + })?; + let mut ret = Self::zero(); + for (atype, amt) in vec { + ret += Self::from_pair(atype, amt).map_err(|_| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "amount out of range") + })?; + } + Ok(ret) + } + + /// Serialize an ValueSum object into a list of amounts denominated by + /// distinct asset types + pub fn write(&self, writer: &mut W) -> std::io::Result<()> { + let vec: Vec<_> = self.components().collect(); + Vector::write(writer, vec.as_ref(), |writer, elt| { + writer.write_all(elt.0.get_identifier())?; + writer.write_all(elt.1.to_le_bytes().as_ref())?; + Ok(()) + }) + } +} + +impl From for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + One, +{ fn from(atype: Unit) -> Self { let mut ret = BTreeMap::new(); - ret.insert(atype, 1); - Amount(ret) + ret.insert(atype, Value::one()); + ValueSum(ret) } } -impl PartialOrd for Amount { - /// One Amount is more than or equal to another if each corresponding - /// coordinate is more than the other's. +impl PartialOrd for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + Default + PartialOrd, +{ + /// One ValueSum is more than or equal to another if each corresponding + /// coordinate is more than or equal to the other's. fn partial_cmp(&self, other: &Self) -> Option { - let mut diff = other.clone(); - for (atype, amount) in self.components() { - let ent = diff[atype] - amount; - if ent == 0 { - diff.0.remove(atype); - } else { - diff.0.insert(atype.clone(), ent); + let zero = Value::default(); + let mut ordering = Some(Ordering::Equal); + for k in self.0.keys().chain(other.0.keys()) { + let v1 = self.0.get(k).unwrap_or(&zero); + let v2 = other.0.get(k).unwrap_or(&zero); + match (v1.partial_cmp(v2), ordering) { + // Sums cannot be compared if even a single coordinate cannot be + // compared + (None, _) => ordering = None, + // If sums are uncomparable, less, greater, or equal, another + // equal coordinate will not change that + (Some(Ordering::Equal), _) => {} + // A lesser coordinate is inconsistent with the sum being + // greater, and vice-versa + (Some(Ordering::Less), Some(Ordering::Greater) | None) => ordering = None, + (Some(Ordering::Greater), Some(Ordering::Less) | None) => ordering = None, + // It only takes one lesser coordinate, to make a sum that + // otherwise would have been equal, to be lesser + (Some(Ordering::Less), Some(Ordering::Less | Ordering::Equal)) => { + ordering = Some(Ordering::Less) + } + (Some(Ordering::Greater), Some(Ordering::Greater | Ordering::Equal)) => { + ordering = Some(Ordering::Greater) + } } } - if diff.0.values().all(|x| *x == 0) { - Some(Ordering::Equal) - } else if diff.0.values().all(|x| *x >= 0) { - Some(Ordering::Less) - } else if diff.0.values().all(|x| *x <= 0) { - Some(Ordering::Greater) - } else { - None - } - } -} - -impl Index<&Unit> for Amount { - type Output = i64; - /// Query how much of the given asset this amount contains - fn index(&self, index: &Unit) -> &Self::Output { - self.0.get(index).unwrap_or(&0) + ordering } } -impl MulAssign for Amount { - fn mul_assign(&mut self, rhs: i64) { - for (_atype, amount) in self.0.iter_mut() { - let ent = *amount * rhs; - if -MAX_MONEY <= ent && ent <= MAX_MONEY { - *amount = ent; - } else { - panic!("multiplication should remain in range"); +macro_rules! impl_index { + ($struct_type:ty) => { + impl Index<&Unit> for ValueSum + where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize, + { + type Output = $struct_type; + /// Query how much of the given asset this amount contains + fn index(&self, index: &Unit) -> &Self::Output { + self.0.get(index).unwrap_or(&0) } } - } + }; } -impl Mul for Amount { - type Output = Self; +impl_index!(i8); + +impl_index!(u8); + +impl_index!(i16); + +impl_index!(u16); - fn mul(mut self, rhs: i64) -> Self { - self *= rhs; - self +impl_index!(i32); + +impl_index!(u32); + +impl_index!(i64); + +impl_index!(u64); + +impl_index!(i128); + +impl_index!(u128); + +impl MulAssign for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedMul, +{ + fn mul_assign(&mut self, rhs: Value) { + *self = self.clone() * rhs; } } -impl AddAssign<&Amount> - for Amount +impl Mul for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedMul, { - fn add_assign(&mut self, rhs: &Self) { - for (atype, amount) in rhs.components() { - let ent = self[atype] + amount; - if ent == 0 { - self.0.remove(atype); - } else if -MAX_MONEY <= ent && ent <= MAX_MONEY { - self.0.insert(atype.clone(), ent); - } else { - panic!("addition should remain in range"); - } + type Output = ValueSum; + + fn mul(self, rhs: Value) -> Self::Output { + let mut comps = BTreeMap::new(); + for (atype, amount) in self.0.iter() { + comps.insert( + atype.clone(), + amount.checked_mul(&rhs).expect("overflow detected"), + ); } + comps.retain(|_, v| *v != Value::default()); + ValueSum(comps) } } -impl AddAssign> - for Amount +impl AddAssign<&ValueSum> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedAdd, { - fn add_assign(&mut self, rhs: Self) { + fn add_assign(&mut self, rhs: &ValueSum) { + *self = self.clone() + rhs; + } +} + +impl AddAssign> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedAdd, +{ + fn add_assign(&mut self, rhs: ValueSum) { *self += &rhs } } -impl Add<&Amount> - for Amount +impl Add<&ValueSum> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedAdd, { - type Output = Self; + type Output = ValueSum; - fn add(mut self, rhs: &Self) -> Self { - self += rhs; - self + fn add(self, rhs: &ValueSum) -> Self::Output { + let mut comps = self.0.clone(); + for (atype, amount) in rhs.components() { + comps.insert( + atype.clone(), + self.get(atype) + .checked_add(amount) + .expect("overflow detected"), + ); + } + comps.retain(|_, v| *v != Value::default()); + ValueSum(comps) } } -impl Add> - for Amount +impl Add> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedAdd, { - type Output = Self; + type Output = ValueSum; - fn add(mut self, rhs: Self) -> Self { - self += &rhs; - self + fn add(self, rhs: ValueSum) -> Self::Output { + self + &rhs } } -impl SubAssign<&Amount> - for Amount +impl SubAssign<&ValueSum> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedSub, { - fn sub_assign(&mut self, rhs: &Self) { - for (atype, amount) in rhs.components() { - let ent = self[atype] - amount; - if ent == 0 { - self.0.remove(atype); - } else if -MAX_MONEY <= ent && ent <= MAX_MONEY { - self.0.insert(atype.clone(), ent); - } else { - panic!("subtraction should remain in range"); - } - } + fn sub_assign(&mut self, rhs: &ValueSum) { + *self = self.clone() - rhs } } -impl SubAssign> - for Amount +impl SubAssign> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedSub, { - fn sub_assign(&mut self, rhs: Self) { + fn sub_assign(&mut self, rhs: ValueSum) { *self -= &rhs } } -impl Neg for Amount { - type Output = Self; - - fn neg(mut self) -> Self { - for (_, amount) in self.0.iter_mut() { - *amount = -*amount; +impl Neg for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedNeg, +{ + type Output = ValueSum; + + fn neg(mut self) -> Self::Output { + let mut comps = BTreeMap::new(); + for (atype, amount) in self.0.iter_mut() { + comps.insert( + atype.clone(), + amount.checked_neg().expect("overflow detected"), + ); } - self + comps.retain(|_, v| *v != Value::default()); + ValueSum(comps) } } -impl Sub<&Amount> - for Amount +impl Sub<&ValueSum> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + Default + CheckedSub, { - type Output = Self; + type Output = ValueSum; - fn sub(mut self, rhs: &Self) -> Self { - self -= rhs; - self + fn sub(self, rhs: &ValueSum) -> Self::Output { + let mut comps = self.0.clone(); + for (atype, amount) in rhs.components() { + comps.insert( + atype.clone(), + self.get(atype) + .checked_sub(amount) + .expect("overflow detected"), + ); + } + comps.retain(|_, v| *v != Value::default()); + ValueSum(comps) } } -impl Sub> - for Amount +impl Sub> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + Default + CheckedSub, { - type Output = Self; + type Output = ValueSum; - fn sub(mut self, rhs: Self) -> Self { - self -= &rhs; - self + fn sub(self, rhs: ValueSum) -> Self::Output { + self - &rhs } } -impl Sum for Amount { +impl Sum for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + Default + PartialOrd, + Self: Add, +{ fn sum>(iter: I) -> Self { iter.fold(Self::zero(), Add::add) } } +impl ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Output: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy, +{ + pub fn try_from_sum( + x: ValueSum, + ) -> Result>::Error> + where + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy, + Output: TryFrom, + { + let mut comps = BTreeMap::new(); + for (atype, amount) in x.0 { + comps.insert(atype, amount.try_into()?); + } + Ok(Self(comps)) + } + + pub fn from_sum(x: ValueSum) -> Self + where + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy, + Output: From, + { + let mut comps = BTreeMap::new(); + for (atype, amount) in x.0 { + comps.insert(atype, amount.into()); + } + Self(comps) + } +} + /// A type for balance violations in amount addition and subtraction /// (overflow and underflow of allowed ranges) #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -331,12 +623,12 @@ impl std::fmt::Display for BalanceError { BalanceError::Overflow => { write!( f, - "Amount addition resulted in a value outside the valid range." + "ValueSum addition resulted in a value outside the valid range." ) } BalanceError::Underflow => write!( f, - "Amount subtraction resulted in a value outside the valid range." + "ValueSum subtraction resulted in a value outside the valid range." ), } } @@ -346,102 +638,105 @@ pub fn zec() -> AssetType { AssetType::new(b"ZEC").unwrap() } -pub fn default_fee() -> Amount { - Amount::from_pair(zec(), 10000).unwrap() +pub fn default_fee() -> ValueSum { + ValueSum::from_pair(zec(), 10000).unwrap() } #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { use proptest::prelude::prop_compose; - use super::{Amount, MAX_MONEY}; + use super::{I128Sum, I64Sum, U64Sum, ValueSum, MAX_MONEY}; use crate::asset_type::testing::arb_asset_type; prop_compose! { - pub fn arb_amount()(asset_type in arb_asset_type(), amt in -MAX_MONEY..MAX_MONEY) -> Amount { - Amount::from_pair(asset_type, amt).unwrap() + pub fn arb_i64_sum()(asset_type in arb_asset_type(), amt in i64::MIN..i64::MAX) -> I64Sum { + ValueSum::from_pair(asset_type, amt).unwrap() } } prop_compose! { - pub fn arb_nonnegative_amount()(asset_type in arb_asset_type(), amt in 0i64..MAX_MONEY) -> Amount { - Amount::from_pair(asset_type, amt).unwrap() + pub fn arb_i128_sum()(asset_type in arb_asset_type(), amt in i128::MIN..i128::MAX) -> I128Sum { + ValueSum::from_pair(asset_type, amt as i128).unwrap() } } prop_compose! { - pub fn arb_positive_amount()(asset_type in arb_asset_type(), amt in 1i64..MAX_MONEY) -> Amount { - Amount::from_pair(asset_type, amt).unwrap() + pub fn arb_nonnegative_amount()(asset_type in arb_asset_type(), amt in 0u64..MAX_MONEY) -> U64Sum { + ValueSum::from_pair(asset_type, amt).unwrap() + } + } + + prop_compose! { + pub fn arb_positive_amount()(asset_type in arb_asset_type(), amt in 1u64..MAX_MONEY) -> U64Sum { + ValueSum::from_pair(asset_type, amt).unwrap() } } } #[cfg(test)] mod tests { - use super::{zec, Amount, MAX_MONEY}; + use super::{zec, I64Sum, ValueSum, MAX_MONEY}; #[test] fn amount_in_range() { let zero = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\x00\x00\x00\x00\x00\x00\x00\x00"; - assert_eq!(Amount::read(&mut zero.as_ref()).unwrap(), Amount::zero()); + assert_eq!(I64Sum::read(&mut zero.as_ref()).unwrap(), ValueSum::zero()); let neg_one = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\xff\xff\xff\xff\xff\xff\xff\xff"; assert_eq!( - Amount::read(&mut neg_one.as_ref()).unwrap(), - Amount::from_pair(zec(), -1).unwrap() + I64Sum::read(&mut neg_one.as_ref()).unwrap(), + I64Sum::from_pair(zec(), -1).unwrap() ); let max_money = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\xff\xff\xff\xff\xff\xff\xff\x7f"; assert_eq!( - Amount::read(&mut max_money.as_ref()).unwrap(), - Amount::from_pair(zec(), MAX_MONEY).unwrap() + I64Sum::read(&mut max_money.as_ref()).unwrap(), + I64Sum::from_pair(zec(), i64::MAX).unwrap() ); //let max_money_p1 = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\x01\x40\x07\x5a\xf0\x75\x07\x00"; - //assert!(Amount::read(&mut max_money_p1.as_ref()).is_err()); + //assert!(ValueSum::read(&mut max_money_p1.as_ref()).is_err()); //let mut neg_max_money = [0u8; 41]; - //let mut amount = Amount::from_pair(zec(), -MAX_MONEY).unwrap(); + //let mut amount = ValueSum::from_pair(zec(), -MAX_MONEY).unwrap(); //*amount.0.get_mut(&zec()).unwrap() = i64::MIN; //amount.write(&mut neg_max_money.as_mut()); //dbg!(std::str::from_utf8(&neg_max_money.as_ref().iter().map(|b| std::ascii::escape_default(*b)).flatten().collect::>()).unwrap()); let neg_max_money = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\x01\x00\x00\x00\x00\x00\x00\x80"; assert_eq!( - Amount::read(&mut neg_max_money.as_ref()).unwrap(), - Amount::from_pair(zec(), -MAX_MONEY).unwrap() + I64Sum::read(&mut neg_max_money.as_ref()).unwrap(), + I64Sum::from_pair(zec(), -i64::MAX).unwrap() ); - - let neg_max_money_m1 = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\x00\x00\x00\x00\x00\x00\x00\x80"; - assert!(Amount::read(&mut neg_max_money_m1.as_ref()).is_err()); } #[test] #[should_panic] fn add_panics_on_overflow() { - let v = Amount::from_pair(zec(), MAX_MONEY).unwrap(); - let _sum = v + Amount::from_pair(zec(), 1).unwrap(); + let v = ValueSum::from_pair(zec(), MAX_MONEY).unwrap(); + let _sum = v + ValueSum::from_pair(zec(), 1).unwrap(); } #[test] #[should_panic] fn add_assign_panics_on_overflow() { - let mut a = Amount::from_pair(zec(), MAX_MONEY).unwrap(); - a += Amount::from_pair(zec(), 1).unwrap(); + let mut a = ValueSum::from_pair(zec(), MAX_MONEY).unwrap(); + a += ValueSum::from_pair(zec(), 1).unwrap(); } #[test] #[should_panic] fn sub_panics_on_underflow() { - let v = Amount::from_pair(zec(), -MAX_MONEY).unwrap(); - let _diff = v - Amount::from_pair(zec(), 1).unwrap(); + let v = ValueSum::from_pair(zec(), 0u64).unwrap(); + let _diff = v - ValueSum::from_pair(zec(), 1).unwrap(); } #[test] #[should_panic] fn sub_assign_panics_on_underflow() { - let mut a = Amount::from_pair(zec(), -MAX_MONEY).unwrap(); - a -= Amount::from_pair(zec(), 1).unwrap(); + let mut a = ValueSum::from_pair(zec(), 0u64).unwrap(); + a -= ValueSum::from_pair(zec(), 1).unwrap(); } } diff --git a/masp_primitives/src/transaction/components/sapling.rs b/masp_primitives/src/transaction/components/sapling.rs index 2048abd9..7ce04153 100644 --- a/masp_primitives/src/transaction/components/sapling.rs +++ b/masp_primitives/src/transaction/components/sapling.rs @@ -23,7 +23,7 @@ use crate::{ }, }; -use super::{amount::Amount, GROTH_PROOF_SIZE}; +use super::{amount::I128Sum, GROTH_PROOF_SIZE}; pub type GrothProofBytes = [u8; GROTH_PROOF_SIZE]; @@ -90,7 +90,7 @@ pub struct Bundle>, pub shielded_converts: Vec>, pub shielded_outputs: Vec>, - pub value_balance: Amount, + pub value_balance: I128Sum, pub authorization: A, } @@ -535,7 +535,7 @@ pub mod testing { Nullifier, }, transaction::{ - components::{amount::testing::arb_amount, GROTH_PROOF_SIZE}, + components::{amount::testing::arb_i128_sum, GROTH_PROOF_SIZE}, TxVersion, }, }; @@ -614,7 +614,7 @@ pub mod testing { shielded_spends in vec(arb_spend_description(), 0..30), shielded_converts in vec(arb_convert_description(), 0..30), shielded_outputs in vec(arb_output_description(), 0..30), - value_balance in arb_amount(), + value_balance in arb_i128_sum(), rng_seed in prop::array::uniform32(prop::num::u8::ANY), fake_bvk_bytes in prop::array::uniform32(prop::num::u8::ANY), ) -> Option> { diff --git a/masp_primitives/src/transaction/components/sapling/builder.rs b/masp_primitives/src/transaction/components/sapling/builder.rs index 381295f0..9d5594ca 100644 --- a/masp_primitives/src/transaction/components/sapling/builder.rs +++ b/masp_primitives/src/transaction/components/sapling/builder.rs @@ -25,7 +25,7 @@ use crate::{ transaction::{ builder::Progress, components::{ - amount::{Amount, MAX_MONEY}, + amount::{I128Sum, I32Sum, ValueSum, MAX_MONEY}, sapling::{ fees, Authorization, Authorized, Bundle, ConvertDescription, GrothProofBytes, OutputDescription, SpendDescription, @@ -146,7 +146,7 @@ impl SaplingOutputInfo { memo: MemoBytes, ) -> Result { let g_d = to.g_d().ok_or(Error::InvalidAddress)?; - if value > MAX_MONEY.try_into().unwrap() { + if value > MAX_MONEY { return Err(Error::InvalidAmount); } @@ -272,7 +272,7 @@ pub struct SaplingBuilder { params: P, spend_anchor: Option, target_height: BlockHeight, - value_balance: Amount, + value_balance: I128Sum, convert_anchor: Option, spends: Vec>, converts: Vec, @@ -303,7 +303,7 @@ impl BorshDeserialize for SaplingBui .map(|x| x.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData))) .transpose()?; let target_height = BlockHeight::deserialize(buf)?; - let value_balance: Amount = Amount::deserialize(buf)?; + let value_balance = I128Sum::deserialize(buf)?; let convert_anchor: Option> = Option::<[u8; 32]>::deserialize(buf)?.map(|x| bls12_381::Scalar::from_bytes(&x).into()); let convert_anchor = convert_anchor @@ -347,7 +347,7 @@ impl SaplingBuilder { params, spend_anchor: None, target_height, - value_balance: Amount::zero(), + value_balance: ValueSum::zero(), convert_anchor: None, spends: vec![], converts: vec![], @@ -370,7 +370,7 @@ impl SaplingBuilder { } /// Returns the net value represented by the spends and outputs added to this builder. - pub fn value_balance(&self) -> Amount { + pub fn value_balance(&self) -> I128Sum { self.value_balance.clone() } } @@ -401,8 +401,8 @@ impl SaplingBuilder

{ let alpha = jubjub::Fr::random(&mut rng); - self.value_balance += - Amount::from_pair(note.asset_type, note.value).map_err(|_| Error::InvalidAmount)?; + self.value_balance += ValueSum::from_pair(note.asset_type, note.value.into()) + .map_err(|_| Error::InvalidAmount)?; self.spends.push(SpendDescriptionInfo { extsk, @@ -437,8 +437,8 @@ impl SaplingBuilder

{ self.convert_anchor = Some(merkle_path.root(node).into()) } - let allowed_amt: Amount = allowed.clone().into(); - self.value_balance += allowed_amt * value.try_into().unwrap(); + let allowed_amt: I32Sum = allowed.clone().into(); + self.value_balance += I128Sum::from_sum(allowed_amt) * (value as i128); self.converts.push(ConvertDescriptionInfo { allowed, @@ -472,7 +472,7 @@ impl SaplingBuilder

{ )?; self.value_balance -= - Amount::from_pair(asset_type, value).map_err(|_| Error::InvalidAmount)?; + ValueSum::from_pair(asset_type, value.into()).map_err(|_| Error::InvalidAmount)?; self.outputs.push(output); @@ -488,6 +488,7 @@ impl SaplingBuilder

{ progress_notifier: Option<&Sender>, ) -> Result>, Error> { // Record initial positions of spends and outputs + let value_balance = self.value_balance(); let params = self.params; let mut indexed_spends: Vec<_> = self.spends.into_iter().enumerate().collect(); let mut indexed_converts: Vec<_> = self.converts.into_iter().enumerate().collect(); @@ -723,7 +724,7 @@ impl SaplingBuilder

{ shielded_spends, shielded_converts, shielded_outputs, - value_balance: self.value_balance, + value_balance, authorization: Unauthorized { tx_metadata }, }) }; diff --git a/masp_primitives/src/transaction/components/transparent.rs b/masp_primitives/src/transaction/components/transparent.rs index 06efcaee..85391e8c 100644 --- a/masp_primitives/src/transaction/components/transparent.rs +++ b/masp_primitives/src/transaction/components/transparent.rs @@ -7,7 +7,7 @@ use std::io::{self, Read, Write}; use crate::asset_type::AssetType; use crate::transaction::TransparentAddress; -use super::amount::{Amount, BalanceError, MAX_MONEY}; +use super::amount::{BalanceError, I128Sum, ValueSum, MAX_MONEY}; pub mod builder; pub mod fees; @@ -58,34 +58,22 @@ impl Bundle { /// transferred out of the transparent pool into shielded pools or to fees; a negative value /// means that the containing transaction has funds being transferred into the transparent pool /// from the shielded pools. - pub fn value_balance(&self) -> Result + pub fn value_balance(&self) -> Result where E: From, { let input_sum = self .vin .iter() - .map(|p| { - if p.value >= 0 { - Amount::from_pair(p.asset_type, p.value) - } else { - Err(()) - } - }) - .sum::>() + .map(|p| ValueSum::from_pair(p.asset_type, p.value as i128)) + .sum::>() .map_err(|_| BalanceError::Overflow)?; let output_sum = self .vout .iter() - .map(|p| { - if p.value >= 0 { - Amount::from_pair(p.asset_type, p.value) - } else { - Err(()) - } - }) - .sum::>() + .map(|p| ValueSum::from_pair(p.asset_type, p.value as i128)) + .sum::>() .map_err(|_| BalanceError::Overflow)?; // Cannot panic when subtracting two positive i64 @@ -96,7 +84,7 @@ impl Bundle { #[derive(Debug, Clone, PartialEq, Eq)] pub struct TxIn { pub asset_type: AssetType, - pub value: i64, + pub value: u64, pub address: TransparentAddress, pub transparent_sig: A::TransparentSig, } @@ -112,9 +100,9 @@ impl TxIn { let value = { let mut tmp = [0u8; 8]; reader.read_exact(&mut tmp)?; - i64::from_le_bytes(tmp) + u64::from_le_bytes(tmp) }; - if value < 0 || value > MAX_MONEY { + if value > MAX_MONEY { return Err(io::Error::new( io::ErrorKind::InvalidData, "value out of range", @@ -144,7 +132,7 @@ impl TxIn { #[derive(Clone, Debug, Hash, PartialOrd, PartialEq, Ord, Eq)] pub struct TxOut { pub asset_type: AssetType, - pub value: i64, + pub value: u64, pub address: TransparentAddress, } @@ -159,9 +147,9 @@ impl TxOut { let value = { let mut tmp = [0u8; 8]; reader.read_exact(&mut tmp)?; - i64::from_le_bytes(tmp) + u64::from_le_bytes(tmp) }; - if value < 0 || value > MAX_MONEY { + if value > MAX_MONEY { return Err(io::Error::new( io::ErrorKind::InvalidData, "value out of range", diff --git a/masp_primitives/src/transaction/components/transparent/builder.rs b/masp_primitives/src/transaction/components/transparent/builder.rs index 6c0cc642..f97eb491 100644 --- a/masp_primitives/src/transaction/components/transparent/builder.rs +++ b/masp_primitives/src/transaction/components/transparent/builder.rs @@ -6,7 +6,7 @@ use crate::{ asset_type::AssetType, transaction::{ components::{ - amount::{Amount, BalanceError, MAX_MONEY}, + amount::{BalanceError, I128Sum, ValueSum, MAX_MONEY}, transparent::{self, fees, Authorization, Authorized, Bundle, TxIn, TxOut}, }, sighash::TransparentAuthorizingContext, @@ -105,10 +105,6 @@ impl TransparentBuilder { /// Adds a coin (the output of a previous transaction) to be spent to the transaction. #[cfg(feature = "transparent-inputs")] pub fn add_input(&mut self, coin: TxOut) -> Result<(), Error> { - if coin.value.is_negative() { - return Err(Error::InvalidAmount); - } - self.inputs.push(TransparentInputInfo { coin }); Ok(()) @@ -118,9 +114,9 @@ impl TransparentBuilder { &mut self, to: &TransparentAddress, asset_type: AssetType, - value: i64, + value: u64, ) -> Result<(), Error> { - if value < 0 || value > MAX_MONEY { + if value > MAX_MONEY { return Err(Error::InvalidAmount); } @@ -133,35 +129,23 @@ impl TransparentBuilder { Ok(()) } - pub fn value_balance(&self) -> Result { + pub fn value_balance(&self) -> Result { #[cfg(feature = "transparent-inputs")] let input_sum = self .inputs .iter() - .map(|input| { - if input.coin.value >= 0 { - Amount::from_pair(input.coin.asset_type, input.coin.value) - } else { - Err(()) - } - }) - .sum::>() + .map(|input| ValueSum::from_pair(input.coin.asset_type, input.coin.value as i128)) + .sum::>() .map_err(|_| BalanceError::Overflow)?; #[cfg(not(feature = "transparent-inputs"))] - let input_sum = Amount::zero(); + let input_sum = ValueSum::zero(); let output_sum = self .vout .iter() - .map(|vo| { - if vo.value >= 0 { - Amount::from_pair(vo.asset_type, vo.value) - } else { - Err(()) - } - }) - .sum::>() + .map(|vo| ValueSum::from_pair(vo.asset_type, vo.value as i128)) + .sum::>() .map_err(|_| BalanceError::Overflow)?; // Cannot panic when subtracting two positive i64 @@ -208,7 +192,7 @@ impl TransparentAuthorizingContext for Unauthorized { #[cfg(feature = "transparent-inputs")] impl TransparentAuthorizingContext for Unauthorized { - fn input_amounts(&self) -> Vec<(AssetType, i64)> { + fn input_amounts(&self) -> Vec<(AssetType, u64)> { return self .inputs .iter() diff --git a/masp_primitives/src/transaction/components/transparent/fees.rs b/masp_primitives/src/transaction/components/transparent/fees.rs index cff0a544..99522494 100644 --- a/masp_primitives/src/transaction/components/transparent/fees.rs +++ b/masp_primitives/src/transaction/components/transparent/fees.rs @@ -16,7 +16,7 @@ pub trait InputView { /// fee and change computation. pub trait OutputView { /// Returns the value of the output being created. - fn value(&self) -> i64; + fn value(&self) -> u64; /// Returns the asset type of the output being created. fn asset_type(&self) -> AssetType; /// Returns the script corresponding to the newly created output. @@ -24,7 +24,7 @@ pub trait OutputView { } impl OutputView for TxOut { - fn value(&self) -> i64 { + fn value(&self) -> u64 { self.value } diff --git a/masp_primitives/src/transaction/fees.rs b/masp_primitives/src/transaction/fees.rs index 0108e20f..24d50aad 100644 --- a/masp_primitives/src/transaction/fees.rs +++ b/masp_primitives/src/transaction/fees.rs @@ -2,7 +2,7 @@ use crate::{ consensus::{self, BlockHeight}, - transaction::components::{amount::Amount, transparent::fees as transparent}, + transaction::components::{amount::U64Sum, transparent::fees as transparent}, }; pub mod fixed; @@ -24,5 +24,5 @@ pub trait FeeRule { transparent_outputs: &[impl transparent::OutputView], sapling_input_count: usize, sapling_output_count: usize, - ) -> Result; + ) -> Result; } diff --git a/masp_primitives/src/transaction/fees/fixed.rs b/masp_primitives/src/transaction/fees/fixed.rs index 02e3bd93..8ea28c72 100644 --- a/masp_primitives/src/transaction/fees/fixed.rs +++ b/masp_primitives/src/transaction/fees/fixed.rs @@ -1,7 +1,7 @@ use crate::{ consensus::{self, BlockHeight}, transaction::components::{ - amount::{Amount, DEFAULT_FEE}, + amount::{U64Sum, DEFAULT_FEE}, transparent::fees as transparent, }, }; @@ -10,12 +10,12 @@ use crate::{ /// the transaction being constructed. #[derive(Clone, Debug)] pub struct FeeRule { - fixed_fee: Amount, + fixed_fee: U64Sum, } impl FeeRule { /// Creates a new nonstandard fixed fee rule with the specified fixed fee. - pub fn non_standard(fixed_fee: Amount) -> Self { + pub fn non_standard(fixed_fee: U64Sum) -> Self { Self { fixed_fee } } @@ -27,7 +27,7 @@ impl FeeRule { } /// Returns the fixed fee amount which which this rule was configured. - pub fn fixed_fee(&self) -> Amount { + pub fn fixed_fee(&self) -> U64Sum { self.fixed_fee.clone() } } @@ -42,7 +42,7 @@ impl super::FeeRule for FeeRule { _transparent_outputs: &[impl transparent::OutputView], _sapling_input_count: usize, _sapling_output_count: usize, - ) -> Result { + ) -> Result { Ok(self.fixed_fee.clone()) } } diff --git a/masp_primitives/src/transaction/sighash.rs b/masp_primitives/src/transaction/sighash.rs index e6c9d3c4..c4d4f5d7 100644 --- a/masp_primitives/src/transaction/sighash.rs +++ b/masp_primitives/src/transaction/sighash.rs @@ -55,7 +55,7 @@ pub trait TransparentAuthorizingContext: transparent::Authorization { /// so that wallets can commit to the transparent input breakdown /// without requiring the full data of the previous transactions /// providing these inputs. - fn input_amounts(&self) -> Vec<(AssetType, i64)>; + fn input_amounts(&self) -> Vec<(AssetType, u64)>; } /// Computes the signature hash for an input to a transaction, given diff --git a/masp_proofs/benches/convert.rs b/masp_proofs/benches/convert.rs index da496ad1..60fbbc74 100644 --- a/masp_proofs/benches/convert.rs +++ b/masp_proofs/benches/convert.rs @@ -6,7 +6,7 @@ use bls12_381::Bls12; use criterion::Criterion; use group::ff::Field; use masp_primitives::{ - asset_type::AssetType, convert::AllowedConversion, transaction::components::Amount, + asset_type::AssetType, convert::AllowedConversion, transaction::components::ValueSum, }; use masp_proofs::circuit::convert::{Convert, TREE_DEPTH}; use rand_core::{RngCore, SeedableRng}; @@ -29,19 +29,19 @@ fn criterion_benchmark(c: &mut Criterion) { .unwrap(); c.bench_function("convert", |b| { - let i = rng.next_u32(); + let i = rng.next_u32() >> 1; let spend_asset = AssetType::new(format!("asset {}", i).as_bytes()).unwrap(); let output_asset = AssetType::new(format!("asset {}", i + 1).as_bytes()).unwrap(); let mint_asset = AssetType::new(b"reward").unwrap(); - let spend_value = -(i as i64 + 1); - let output_value = i as i64 + 1; - let mint_value = i as i64 + 1; + let spend_value = -(i as i32 + 1); + let output_value = i as i32 + 1; + let mint_value = i as i32 + 1; - let allowed_conversion: AllowedConversion = (Amount::from_pair(spend_asset, spend_value) + let allowed_conversion: AllowedConversion = (ValueSum::from_pair(spend_asset, spend_value) .unwrap() - + Amount::from_pair(output_asset, output_value).unwrap() - + Amount::from_pair(mint_asset, mint_value).unwrap()) + + ValueSum::from_pair(output_asset, output_value).unwrap() + + ValueSum::from_pair(mint_asset, mint_value).unwrap()) .into(); let value = rng.next_u64(); diff --git a/masp_proofs/src/circuit/convert.rs b/masp_proofs/src/circuit/convert.rs index d555ed67..c8bcb300 100644 --- a/masp_proofs/src/circuit/convert.rs +++ b/masp_proofs/src/circuit/convert.rs @@ -132,7 +132,7 @@ fn test_convert_circuit_with_bls12_381() { use group::{ff::Field, ff::PrimeField, ff::PrimeFieldBits, Curve}; use masp_primitives::{ asset_type::AssetType, convert::AllowedConversion, sapling::pedersen_hash, - transaction::components::Amount, + transaction::components::ValueSum, }; use rand_core::{RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; @@ -150,14 +150,14 @@ fn test_convert_circuit_with_bls12_381() { let output_asset = AssetType::new(format!("asset {}", i + 1).as_bytes()).unwrap(); let mint_asset = AssetType::new(b"reward").unwrap(); - let spend_value = -(i as i64 + 1); - let output_value = i as i64 + 1; - let mint_value = i as i64 + 1; + let spend_value = -(i as i32 + 1); + let output_value = i as i32 + 1; + let mint_value = i as i32 + 1; - let allowed_conversion: AllowedConversion = (Amount::from_pair(spend_asset, spend_value) + let allowed_conversion: AllowedConversion = (ValueSum::from_pair(spend_asset, spend_value) .unwrap() - + Amount::from_pair(output_asset, output_value).unwrap() - + Amount::from_pair(mint_asset, mint_value).unwrap()) + + ValueSum::from_pair(output_asset, output_value).unwrap() + + ValueSum::from_pair(mint_asset, mint_value).unwrap()) .into(); let value = rng.next_u64(); diff --git a/masp_proofs/src/prover.rs b/masp_proofs/src/prover.rs index adefc463..355fb350 100644 --- a/masp_proofs/src/prover.rs +++ b/masp_proofs/src/prover.rs @@ -11,7 +11,7 @@ use masp_primitives::{ redjubjub::{PublicKey, Signature}, Diversifier, Node, PaymentAddress, ProofGenerationKey, Rseed, }, - transaction::components::{Amount, GROTH_PROOF_SIZE}, + transaction::components::{I128Sum, GROTH_PROOF_SIZE}, }; use std::path::Path; @@ -247,7 +247,7 @@ impl TxProver for LocalTxProver { fn binding_sig( &self, ctx: &mut Self::SaplingProvingContext, - assets_and_values: &Amount, //&[(AssetType, i64)], + assets_and_values: &I128Sum, //&[(AssetType, i64)], sighash: &[u8; 32], ) -> Result { ctx.binding_sig(assets_and_values, sighash) diff --git a/masp_proofs/src/sapling/mod.rs b/masp_proofs/src/sapling/mod.rs index eb14fc60..16d90e6e 100644 --- a/masp_proofs/src/sapling/mod.rs +++ b/masp_proofs/src/sapling/mod.rs @@ -9,11 +9,11 @@ pub use self::prover::SaplingProvingContext; pub use self::verifier::{BatchValidator, SaplingVerificationContext}; // This function computes `value` in the exponent of the value commitment base -fn masp_compute_value_balance(asset_type: AssetType, value: i64) -> Option { - // Compute the absolute value (failing if -i64::MAX is +fn masp_compute_value_balance(asset_type: AssetType, value: i128) -> Option { + // Compute the absolute value (failing if -i128::MAX is // the value) let abs = match value.checked_abs() { - Some(a) => a as u64, + Some(a) => a as u128, None => return None, }; @@ -21,7 +21,10 @@ fn masp_compute_value_balance(asset_type: AssetType, value: i64) -> Option Result { // Initialize secure RNG @@ -304,7 +304,7 @@ impl SaplingProvingContext { .components() .map(|(asset_type, value_balance)| { // Compute value balance for each asset - // Error for bad value balances (-INT64_MAX value) + // Error for bad value balances (-INT128_MAX value) masp_compute_value_balance(*asset_type, *value_balance) }) .try_fold(self.cv_sum, |tmp, value_balance| { diff --git a/masp_proofs/src/sapling/verifier.rs b/masp_proofs/src/sapling/verifier.rs index 22cceafb..c9c297dd 100644 --- a/masp_proofs/src/sapling/verifier.rs +++ b/masp_proofs/src/sapling/verifier.rs @@ -5,7 +5,7 @@ use bls12_381::Bls12; use group::{Curve, GroupEncoding}; use masp_primitives::{ sapling::redjubjub::{PublicKey, Signature}, - transaction::components::Amount, + transaction::components::I128Sum, }; use super::masp_compute_value_balance; @@ -172,7 +172,7 @@ impl SaplingVerificationContextInner { /// have been checked before calling this function. fn final_check( &self, - value_balance: Amount, + value_balance: I128Sum, sighash_value: &[u8; 32], binding_sig: Signature, binding_sig_verifier: impl FnOnce(PublicKey, [u8; 64], Signature) -> bool, diff --git a/masp_proofs/src/sapling/verifier/single.rs b/masp_proofs/src/sapling/verifier/single.rs index 2df7dfae..8abedb48 100644 --- a/masp_proofs/src/sapling/verifier/single.rs +++ b/masp_proofs/src/sapling/verifier/single.rs @@ -3,7 +3,7 @@ use bls12_381::Bls12; use masp_primitives::{ constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR}, sapling::redjubjub::{PublicKey, Signature}, - transaction::components::Amount, + transaction::components::I128Sum, }; use super::SaplingVerificationContextInner; @@ -98,7 +98,7 @@ impl SaplingVerificationContext { /// have been checked before calling this function. pub fn final_check( &self, - value_balance: Amount, + value_balance: I128Sum, sighash_value: &[u8; 32], binding_sig: Signature, ) -> bool {