diff --git a/masp_primitives/Cargo.toml b/masp_primitives/Cargo.toml index daec9491..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" @@ -53,9 +56,6 @@ lazy_static = "1" # - Test dependencies proptest = { version = "1.0.0", optional = true } -# - Transparent inputs -secp256k1 = { version = "0.24.1", features = [ "rand" ] } - # - ZIP 339 bip0039 = { version = "0.9", features = ["std", "all-languages"] } diff --git a/masp_primitives/src/consensus.rs b/masp_primitives/src/consensus.rs index 75fc8b77..1be8a9cb 100644 --- a/masp_primitives/src/consensus.rs +++ b/masp_primitives/src/consensus.rs @@ -1,5 +1,6 @@ //! Consensus logic and parameters. +use borsh::{BorshDeserialize, BorshSerialize}; use memuse::DynamicUsage; use std::cmp::{Ord, Ordering}; use std::convert::TryFrom; @@ -9,7 +10,7 @@ use std::ops::{Add, Bound, RangeBounds, Sub}; /// A wrapper type representing blockchain heights. Safe conversion from /// various integer types, as well as addition and subtraction, are provided. #[repr(transparent)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)] pub struct BlockHeight(u32); memuse::impl_no_dynamic_usage!(BlockHeight); 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/lib.rs b/masp_primitives/src/lib.rs index 7649170d..d1b2d6d2 100644 --- a/masp_primitives/src/lib.rs +++ b/masp_primitives/src/lib.rs @@ -26,5 +26,10 @@ pub mod sapling; pub mod transaction; pub mod zip32; +pub use bls12_381; +pub use ff; +pub use group; +pub use jubjub; + #[cfg(test)] mod test_vectors; diff --git a/masp_primitives/src/merkle_tree.rs b/masp_primitives/src/merkle_tree.rs index fd1f6801..26ae6635 100644 --- a/masp_primitives/src/merkle_tree.rs +++ b/masp_primitives/src/merkle_tree.rs @@ -771,7 +771,10 @@ impl BorshDeserialize for MerklePath { // Begin to construct the authentication path // Do not use any data in the witness after the expected depth let iter = witness[..33 * depth + 8].chunks_exact(33); - *witness = iter.remainder(); + // Update the witness to its final position + *witness = &witness[33 * depth + 8..]; + // Read the position from the witness + let position = iter.remainder().read_u64::()?; // The vector works in reverse let mut auth_path = iter @@ -798,9 +801,6 @@ impl BorshDeserialize for MerklePath { return Err(std::io::Error::from(std::io::ErrorKind::InvalidData)); } - // Read the position from the witness - let position = witness.read_u64::()?; - // Given the position, let's finish constructing the authentication // path let mut tmp = position; diff --git a/masp_primitives/src/sapling.rs b/masp_primitives/src/sapling.rs index 914894f0..05b74f97 100644 --- a/masp_primitives/src/sapling.rs +++ b/masp_primitives/src/sapling.rs @@ -85,8 +85,7 @@ pub struct Node { } impl Node { - #[cfg(test)] - pub(crate) fn new(repr: [u8; 32]) -> Self { + pub fn new(repr: [u8; 32]) -> Self { Node { repr } } @@ -506,7 +505,9 @@ pub enum Rseed { } /// Typesafe wrapper for nullifier values. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive( + Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, BorshSerialize, BorshDeserialize, +)] pub struct Nullifier(pub [u8; 32]); impl Nullifier { @@ -551,7 +552,7 @@ impl From for u64 { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Copy)] pub struct Note { /// The asset type that the note represents pub asset_type: AssetType, diff --git a/masp_primitives/src/sapling/keys.rs b/masp_primitives/src/sapling/keys.rs index cb265ee1..c73dbe9b 100644 --- a/masp_primitives/src/sapling/keys.rs +++ b/masp_primitives/src/sapling/keys.rs @@ -8,6 +8,7 @@ use crate::{ constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, keys::prf_expand, }; +use borsh::{BorshDeserialize, BorshSerialize}; use ff::PrimeField; use group::{Group, GroupEncoding}; use std::{ @@ -31,7 +32,7 @@ pub enum DecodingError { } /// An outgoing viewing key -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)] pub struct OutgoingViewingKey(pub [u8; 32]); /// A Sapling expanded spending key 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 4ba6c5a3..4525b425 100644 --- a/masp_primitives/src/transaction.rs +++ b/masp_primitives/src/transaction.rs @@ -10,7 +10,6 @@ use blake2b_simd::Hash as Blake2bHash; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use ff::PrimeField; use memuse::DynamicUsage; -pub use secp256k1::PublicKey as TransparentAddress; use std::{ fmt::{self, Debug}, hash::Hash, @@ -26,7 +25,7 @@ use crate::{ use self::{ components::{ - amount::Amount, + amount::{I128Sum, ValueSum}, sapling::{ self, ConvertDescriptionV5, OutputDescriptionV5, SpendDescription, SpendDescriptionV5, }, @@ -35,6 +34,9 @@ use self::{ txid::{to_txid, BlockTxCommitmentDigester, TxIdDigester}, }; +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)] +pub struct TransparentAddress(pub [u8; 20]); + pub const GROTH_PROOF_SIZE: usize = 48 + 96 + 48; pub type GrothProofBytes = [u8; GROTH_PROOF_SIZE]; @@ -147,7 +149,7 @@ pub trait Authorization { } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct Unproven; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct Authorized; impl Authorization for Authorized { @@ -163,7 +165,7 @@ impl Authorization for Unauthorized { } /// A MASP transaction. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Transaction { txid: TxId, data: TransactionData, @@ -183,7 +185,7 @@ impl PartialEq for Transaction { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct TransactionData { version: TxVersion, consensus_branch_id: BranchId, @@ -267,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()) } } @@ -280,6 +282,32 @@ impl TransactionData { } } +impl BorshSerialize for Transaction { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + self.write(writer) + } +} + +impl BorshDeserialize for Transaction { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + Self::read(buf, BranchId::MASP) + } +} + +impl borsh::BorshSchema for Transaction { + fn add_definitions_recursively( + _definitions: &mut std::collections::HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + } + + fn declaration() -> borsh::schema::Declaration { + "Transaction".into() + } +} + impl Transaction { fn from_data(data: TransactionData) -> io::Result { match data.version { @@ -327,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", @@ -379,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 050e2e59..fc65dcfd 100644 --- a/masp_primitives/src/transaction/builder.rs +++ b/masp_primitives/src/transaction/builder.rs @@ -1,24 +1,24 @@ //! Structs for building transactions. -use std::convert::TryInto; use std::error; use std::fmt; use std::sync::mpsc::Sender; -use secp256k1::PublicKey as TransparentAddress; +use borsh::{BorshDeserialize, BorshSerialize}; use rand::{rngs::OsRng, CryptoRng, RngCore}; use crate::{ asset_type::AssetType, consensus::{self, BlockHeight, BranchId}, + convert::AllowedConversion, keys::OutgoingViewingKey, memo::MemoBytes, merkle_tree::MerklePath, 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}, @@ -28,7 +28,7 @@ use crate::{ fees::FeeRule, sighash::{signature_hash, SignableInput}, txid::TxIdDigester, - Transaction, TransactionData, TxVersion, Unauthorized, + Transaction, TransactionData, TransparentAddress, TxVersion, Unauthorized, }, zip32::ExtendedSpendingKey, }; @@ -42,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 @@ -115,17 +115,19 @@ impl Progress { } /// Generates a [`Transaction`] from its inputs and outputs. -pub struct Builder { +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +pub struct Builder> { params: P, rng: R, target_height: BlockHeight, expiry_height: BlockHeight, transparent_builder: TransparentBuilder, - sapling_builder: SaplingBuilder

, - progress_notifier: Option>, + sapling_builder: SaplingBuilder, + #[borsh_skip] + progress_notifier: Option, } -impl Builder { +impl Builder { /// Returns the network parameters that the builder has been configured for. pub fn params(&self) -> &P { &self.params @@ -150,7 +152,7 @@ impl Builder { /// Returns the set of Sapling inputs currently committed to be consumed /// by the transaction. - pub fn sapling_inputs(&self) -> &[impl sapling::fees::InputView<()>] { + pub fn sapling_inputs(&self) -> &[impl sapling::fees::InputView<(), K>] { self.sapling_builder.inputs() } @@ -159,6 +161,12 @@ impl Builder { pub fn sapling_outputs(&self) -> &[impl sapling::fees::OutputView] { self.sapling_builder.outputs() } + + /// Returns the set of Sapling converts currently set to be produced by + /// the transaction. + pub fn sapling_converts(&self) -> &[impl sapling::fees::ConvertView] { + self.sapling_builder.converts() + } } impl Builder { @@ -219,6 +227,20 @@ impl Builder { .add_spend(&mut self.rng, extsk, diversifier, note, merkle_path) } + /// Adds a Sapling note to be spent in this transaction. + /// + /// Returns an error if the given Merkle path does not have the same anchor as the + /// paths for previous Sapling notes. + pub fn add_sapling_convert( + &mut self, + allowed: AllowedConversion, + value: u64, + merkle_path: MerklePath, + ) -> Result<(), sapling::builder::Error> { + self.sapling_builder + .add_convert(allowed, value, merkle_path) + } + /// Adds a Sapling address to send funds to. pub fn add_sapling_output( &mut self, @@ -228,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 @@ -250,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); } @@ -270,13 +292,13 @@ impl Builder { } /// Returns the sum of the transparent, Sapling, and TZE value balances. - 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. @@ -303,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); @@ -315,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)); }; @@ -388,6 +410,30 @@ impl Builder { } } +pub trait MapBuilder: + sapling::builder::MapBuilder +{ + fn map_rng(&self, s: R1) -> R2; + fn map_notifier(&self, s: N1) -> N2; +} + +impl Builder { + pub fn map_builder>( + self, + f: F, + ) -> Builder { + Builder:: { + params: f.map_params(self.params), + rng: f.map_rng(self.rng), + target_height: self.target_height, + expiry_height: self.expiry_height, + transparent_builder: self.transparent_builder, + progress_notifier: self.progress_notifier.map(|x| f.map_notifier(x)), + sapling_builder: self.sapling_builder.map_builder(f), + } + } +} + #[cfg(any(test, feature = "test-dependencies"))] mod testing { use rand::RngCore; @@ -423,8 +469,8 @@ mod testing { #[cfg(test)] mod tests { use ff::Field; + use rand::Rng; use rand_core::OsRng; - use secp256k1::Secp256k1; use crate::{ asset_type::AssetType, @@ -433,16 +479,16 @@ 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, }; use super::{Builder, Error}; - #[test] + /*#[test] fn fails_on_overflow_output() { let extsk = ExtendedSpendingKey::master(&[]); let dfvk = extsk.to_diversifiable_full_viewing_key(); @@ -459,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 { @@ -473,7 +519,9 @@ mod tests { #[test] fn binding_sig_present_if_shielded_spend() { - let (_, transparent_address) = Secp256k1::new().generate_keypair(&mut OsRng); + let mut rng = OsRng; + + let transparent_address = TransparentAddress(rng.gen::<[u8; 20]>()); let extsk = ExtendedSpendingKey::master(&[]); let dfvk = extsk.to_diversifiable_full_viewing_key(); @@ -515,27 +563,11 @@ mod tests { ); } - #[test] - fn fails_on_negative_transparent_output() { - let secret_key = - secp256k1::SecretKey::from_slice(&[0xcd; 32]).expect("32 bytes, within curve order"); - let transparent_address = - secp256k1::PublicKey::from_secret_key(&secp256k1::Secp256k1::new(), &secret_key); - 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; - let (_, transparent_address) = Secp256k1::new().generate_keypair(&mut OsRng); + let transparent_address = TransparentAddress(rng.gen::<[u8; 20]>()); // Just use the master key as the ExtendedSpendingKey for this test let extsk = ExtendedSpendingKey::master(&[]); let tx_height = TEST_NETWORK @@ -548,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() + ))) ); } @@ -566,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()) )) ); } @@ -581,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()) )) ); } @@ -603,12 +639,7 @@ mod tests { { let mut builder = Builder::new(TEST_NETWORK, tx_height); builder - .add_sapling_spend( - extsk, - *to.diversifier(), - note1.clone(), - witness1.path().unwrap(), - ) + .add_sapling_spend(extsk, *to.diversifier(), note1, witness1.path().unwrap()) .unwrap(); builder .add_sapling_output(ovk, to, zec(), 30000, MemoBytes::empty()) @@ -619,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 44fa4d89..cc041955 100644 --- a/masp_primitives/src/transaction/components.rs +++ b/masp_primitives/src/transaction/components.rs @@ -4,8 +4,10 @@ pub mod amount; pub mod sapling; pub mod transparent; pub use self::{ - amount::Amount, - sapling::{OutputDescription, SpendDescription}, + 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 3e5ced86..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, @@ -34,6 +34,7 @@ use crate::{ }, zip32::ExtendedSpendingKey, }; +use borsh::maybestd::io::Write; use borsh::{BorshDeserialize, BorshSerialize}; /// If there are any shielded inputs, always have at least two shielded outputs, padding @@ -66,30 +67,64 @@ impl fmt::Display for Error { } #[derive(Debug, Clone, PartialEq)] -pub struct SpendDescriptionInfo { - extsk: ExtendedSpendingKey, +pub struct SpendDescriptionInfo { + extsk: Key, diversifier: Diversifier, note: Note, alpha: jubjub::Fr, merkle_path: MerklePath, } -impl fees::InputView<()> for SpendDescriptionInfo { +impl BorshSerialize for SpendDescriptionInfo { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + self.extsk.serialize(writer)?; + self.diversifier.serialize(writer)?; + self.note.serialize(writer)?; + self.alpha.to_bytes().serialize(writer)?; + self.merkle_path.serialize(writer) + } +} + +impl BorshDeserialize for SpendDescriptionInfo { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + let extsk = Key::deserialize(buf)?; + let diversifier = Diversifier::deserialize(buf)?; + let note = Note::deserialize(buf)?; + let alpha: Option<_> = jubjub::Fr::from_bytes(&<[u8; 32]>::deserialize(buf)?).into(); + let alpha = alpha.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData))?; + let merkle_path = MerklePath::::deserialize(buf)?; + Ok(SpendDescriptionInfo { + extsk, + diversifier, + note, + alpha, + merkle_path, + }) + } +} + +impl fees::InputView<(), K> for SpendDescriptionInfo { fn note_id(&self) -> &() { // The builder does not make use of note identifiers, so we can just return the unit value. &() } - fn value(&self) -> Amount { - // An existing note to be spent must have a valid amount value. - Amount::from_pair(self.note.asset_type, self.note.value) - .expect("Existing note value with invalid asset type or value.") + fn value(&self) -> u64 { + self.note.value + } + + fn asset_type(&self) -> AssetType { + self.note.asset_type + } + + fn key(&self) -> &K { + &self.extsk } } /// A struct containing the information required in order to construct a /// MASP output to a transaction. -#[derive(Clone)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct SaplingOutputInfo { /// `None` represents the `ovk = ⊥` case. ovk: Option, @@ -111,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); } @@ -139,8 +174,7 @@ impl SaplingOutputInfo { ctx: &mut Pr::SaplingProvingContext, rng: &mut R, ) -> OutputDescription { - let encryptor = - sapling_note_encryption::

(self.ovk, self.note.clone(), self.to, self.memo); + let encryptor = sapling_note_encryption::

(self.ovk, self.note, self.to, self.memo); let (zkproof, cv) = prover.output_proof( ctx, @@ -170,9 +204,16 @@ impl SaplingOutputInfo { } impl fees::OutputView for SaplingOutputInfo { - fn value(&self) -> Amount { - Amount::from_pair(self.note.asset_type, self.note.value) - .expect("Note values should be checked at construction.") + fn value(&self) -> u64 { + self.note.value + } + + fn asset_type(&self) -> AssetType { + self.note.asset_type + } + + fn address(&self) -> PaymentAddress { + self.to } } @@ -226,17 +267,64 @@ impl SaplingMetadata { } } -pub struct SaplingBuilder

{ +#[derive(Clone, Debug)] +pub struct SaplingBuilder { params: P, spend_anchor: Option, target_height: BlockHeight, - value_balance: Amount, + value_balance: I128Sum, convert_anchor: Option, - spends: Vec, + spends: Vec>, converts: Vec, outputs: Vec, } +impl BorshSerialize for SaplingBuilder { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + self.params.serialize(writer)?; + self.spend_anchor.map(|x| x.to_bytes()).serialize(writer)?; + self.target_height.serialize(writer)?; + self.value_balance.serialize(writer)?; + self.convert_anchor + .map(|x| x.to_bytes()) + .serialize(writer)?; + self.spends.serialize(writer)?; + self.converts.serialize(writer)?; + self.outputs.serialize(writer) + } +} + +impl BorshDeserialize for SaplingBuilder { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + let params = P::deserialize(buf)?; + let spend_anchor: Option> = + Option::<[u8; 32]>::deserialize(buf)?.map(|x| bls12_381::Scalar::from_bytes(&x).into()); + let spend_anchor = spend_anchor + .map(|x| x.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData))) + .transpose()?; + let target_height = BlockHeight::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 + .map(|x| x.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData))) + .transpose()?; + let spends = Vec::>::deserialize(buf)?; + let converts = Vec::::deserialize(buf)?; + let outputs = Vec::::deserialize(buf)?; + Ok(SaplingBuilder { + params, + spend_anchor, + target_height, + value_balance, + convert_anchor, + spends, + converts, + outputs, + }) + } +} + #[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct Unauthorized { tx_metadata: SaplingMetadata, @@ -253,13 +341,13 @@ impl Authorization for Unauthorized { type AuthSig = SpendDescriptionInfo; } -impl

SaplingBuilder

{ +impl SaplingBuilder { pub fn new(params: P, target_height: BlockHeight) -> Self { SaplingBuilder { params, spend_anchor: None, target_height, - value_balance: Amount::zero(), + value_balance: ValueSum::zero(), convert_anchor: None, spends: vec![], converts: vec![], @@ -269,11 +357,11 @@ impl

SaplingBuilder

{ /// Returns the list of Sapling inputs that will be consumed by the transaction being /// constructed. - pub fn inputs(&self) -> &[impl fees::InputView<()>] { + pub fn inputs(&self) -> &[impl fees::InputView<(), K>] { &self.spends } - pub fn converts(&self) -> &[ConvertDescriptionInfo] { + pub fn converts(&self) -> &[impl fees::ConvertView] { &self.converts } /// Returns the Sapling outputs that will be produced by the transaction being constructed @@ -282,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() } } @@ -313,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, @@ -331,7 +419,7 @@ impl SaplingBuilder

{ /// /// Returns an error if the given Merkle path does not have the same anchor as the /// paths for previous convert notes. - pub fn add_convert( + pub fn add_convert( &mut self, allowed: AllowedConversion, value: u64, @@ -349,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, @@ -384,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); @@ -400,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(); @@ -635,7 +724,7 @@ impl SaplingBuilder

{ shielded_spends, shielded_converts, shielded_outputs, - value_balance: self.value_balance, + value_balance, authorization: Unauthorized { tx_metadata }, }) }; @@ -695,13 +784,56 @@ impl Bundle { /// A struct containing the information required in order to construct a /// MASP conversion in a transaction. -#[derive(Clone)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct ConvertDescriptionInfo { allowed: AllowedConversion, value: u64, merkle_path: MerklePath, } +impl fees::ConvertView for ConvertDescriptionInfo { + fn value(&self) -> u64 { + self.value + } + + fn conversion(&self) -> &AllowedConversion { + &self.allowed + } +} + +pub trait MapBuilder { + fn map_params(&self, s: P1) -> P2; + fn map_key(&self, s: K1) -> K2; +} + +impl SaplingBuilder { + pub fn map_builder>( + self, + f: F, + ) -> SaplingBuilder { + SaplingBuilder:: { + params: f.map_params(self.params), + spend_anchor: self.spend_anchor, + target_height: self.target_height, + value_balance: self.value_balance, + convert_anchor: self.convert_anchor, + converts: self.converts, + outputs: self.outputs, + spends: self + .spends + .into_iter() + .map(|x| SpendDescriptionInfo { + extsk: f.map_key(x.extsk), + diversifier: x.diversifier, + note: x.note, + alpha: x.alpha, + merkle_path: x.merkle_path, + }) + .collect(), + } + } +} + #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { use proptest::collection::vec; diff --git a/masp_primitives/src/transaction/components/sapling/fees.rs b/masp_primitives/src/transaction/components/sapling/fees.rs index 10d72adb..93bf9497 100644 --- a/masp_primitives/src/transaction/components/sapling/fees.rs +++ b/masp_primitives/src/transaction/components/sapling/fees.rs @@ -1,20 +1,39 @@ //! Types related to computation of fees and change related to the Sapling components //! of a transaction. -use crate::transaction::components::amount::Amount; +use crate::asset_type::AssetType; +use crate::convert::AllowedConversion; +use crate::sapling::PaymentAddress; /// A trait that provides a minimized view of a Sapling input suitable for use in /// fee and change calculation. -pub trait InputView { +pub trait InputView { /// An identifier for the input being spent. fn note_id(&self) -> &NoteRef; /// The value of the input being spent. - fn value(&self) -> Amount; + fn value(&self) -> u64; + /// The asset type of the input being spent. + fn asset_type(&self) -> AssetType; + /// The spend/view key of the input being spent. + fn key(&self) -> &Key; +} + +/// A trait that provides a minimized view of a Sapling conversion suitable for use in +/// fee and change calculation. +pub trait ConvertView { + /// The amount of the conversion being used. + fn value(&self) -> u64; + /// The allowed conversion being used. + fn conversion(&self) -> &AllowedConversion; } /// A trait that provides a minimized view of a Sapling output suitable for use in /// fee and change calculation. pub trait OutputView { /// The value of the output being produced. - fn value(&self) -> Amount; + fn value(&self) -> u64; + /// The asset type of the output being produced. + fn asset_type(&self) -> AssetType; + /// The destination of this output + fn address(&self) -> PaymentAddress; } diff --git a/masp_primitives/src/transaction/components/transparent.rs b/masp_primitives/src/transaction/components/transparent.rs index a1c81b56..85391e8c 100644 --- a/masp_primitives/src/transaction/components/transparent.rs +++ b/masp_primitives/src/transaction/components/transparent.rs @@ -1,13 +1,13 @@ //! Structs representing the components within Zcash transactions. use borsh::{BorshDeserialize, BorshSerialize}; -use secp256k1::PublicKey as TransparentAddress; use std::fmt::{self, Debug}; 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; @@ -24,12 +24,13 @@ impl Authorization for Authorized { } pub trait MapAuth { + fn map_script_sig(&self, s: A::TransparentSig) -> B::TransparentSig; fn map_authorization(&self, s: A) -> B; } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] pub struct Bundle { - pub vin: Vec, + pub vin: Vec>, pub vout: Vec, pub authorization: A, } @@ -37,7 +38,16 @@ pub struct Bundle { impl Bundle { pub fn map_authorization>(self, f: F) -> Bundle { Bundle { - vin: self.vin, + vin: self + .vin + .into_iter() + .map(|txin| TxIn { + asset_type: txin.asset_type, + address: txin.address, + transparent_sig: f.map_script_sig(txin.transparent_sig), + value: txin.value, + }) + .collect(), vout: self.vout, authorization: f.map_authorization(self.authorization), } @@ -48,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 @@ -84,12 +82,14 @@ impl Bundle { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct TxIn { +pub struct TxIn { pub asset_type: AssetType, - pub value: i64, + pub value: u64, + pub address: TransparentAddress, + pub transparent_sig: A::TransparentSig, } -impl TxIn { +impl TxIn { pub fn read(reader: &mut R) -> io::Result { let asset_type = { let mut tmp = [0u8; 32]; @@ -100,29 +100,40 @@ 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", )); } + let address = { + let mut tmp = [0u8; 20]; + reader.read_exact(&mut tmp)?; + TransparentAddress(tmp) + }; - Ok(TxIn { asset_type, value }) + Ok(TxIn { + asset_type, + value, + address, + transparent_sig: (), + }) } pub fn write(&self, mut writer: W) -> io::Result<()> { writer.write_all(self.asset_type.get_identifier())?; - writer.write_all(&self.value.to_le_bytes()) + writer.write_all(&self.value.to_le_bytes())?; + writer.write_all(&self.address.0) } } #[derive(Clone, Debug, Hash, PartialOrd, PartialEq, Ord, Eq)] pub struct TxOut { pub asset_type: AssetType, - pub value: i64, - pub transparent_address: TransparentAddress, + pub value: u64, + pub address: TransparentAddress, } impl TxOut { @@ -136,35 +147,37 @@ 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", )); } - let mut tmp = [0u8; 33]; - reader.read_exact(&mut tmp)?; - let transparent_address = TransparentAddress::from_slice(&tmp) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "bad public key"))?; + let address = { + let mut tmp = [0u8; 20]; + reader.read_exact(&mut tmp)?; + TransparentAddress(tmp) + }; Ok(TxOut { asset_type, value, - transparent_address, + address, }) } pub fn write(&self, mut writer: W) -> io::Result<()> { writer.write_all(self.asset_type.get_identifier())?; writer.write_all(&self.value.to_le_bytes())?; - writer.write_all(&self.transparent_address.serialize()) + writer.write_all(&self.address.0) } + /// Returns the address to which the TxOut was sent, if this is a valid P2SH or P2PKH output. pub fn recipient_address(&self) -> TransparentAddress { - self.transparent_address + self.address } } @@ -186,23 +199,28 @@ pub mod testing { use proptest::prelude::*; use crate::transaction::components::amount::testing::arb_nonnegative_amount; + use crate::transaction::TransparentAddress; use super::{Authorized, Bundle, TxIn, TxOut}; prop_compose! { - pub fn arb_txin()(amt in arb_nonnegative_amount()) -> TxIn { + pub fn arb_transparent_address()(value in prop::array::uniform20(prop::num::u8::ANY)) -> TransparentAddress { + TransparentAddress(value) + } + } + + prop_compose! { + pub fn arb_txin()(amt in arb_nonnegative_amount(), addr in arb_transparent_address()) -> TxIn { let (asset_type, value) = amt.components().next().unwrap(); - TxIn { asset_type: *asset_type, value: *value } + TxIn { asset_type: *asset_type, value: *value, address: addr, transparent_sig: () } } } prop_compose! { - pub fn arb_txout()(amt in arb_nonnegative_amount()) -> TxOut { - let secp = secp256k1::Secp256k1::new(); - let (_, public_key) = secp.generate_keypair(&mut rand_core::OsRng); + pub fn arb_txout()(amt in arb_nonnegative_amount(), addr in arb_transparent_address()) -> TxOut { let (asset_type, value) = amt.components().next().unwrap(); - TxOut { asset_type: *asset_type, value: *value, transparent_address : public_key } + TxOut { asset_type: *asset_type, value: *value, address : addr } } } diff --git a/masp_primitives/src/transaction/components/transparent/builder.rs b/masp_primitives/src/transaction/components/transparent/builder.rs index d620b020..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, @@ -57,6 +57,7 @@ impl fees::InputView for TransparentInputInfo { } } +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct TransparentBuilder { #[cfg(feature = "transparent-inputs")] inputs: Vec, @@ -104,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(()) @@ -117,50 +114,38 @@ 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); } self.vout.push(TxOut { asset_type, value, - transparent_address: *to, + address: *to, }); 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 @@ -169,12 +154,14 @@ impl TransparentBuilder { pub fn build(self) -> Option> { #[cfg(feature = "transparent-inputs")] - let vin: Vec = self + let vin: Vec> = self .inputs .iter() - .map(|i| TxIn { + .map(|i| TxIn:: { asset_type: i.coin.asset_type, value: i.coin.value, + address: i.coin.address, + transparent_sig: (), }) .collect(); @@ -198,18 +185,18 @@ impl TransparentBuilder { #[cfg(not(feature = "transparent-inputs"))] impl TransparentAuthorizingContext for Unauthorized { - fn input_amounts(&self) -> Vec { + fn input_amounts(&self) -> Vec<(AssetType, i64)> { vec![] } } #[cfg(feature = "transparent-inputs")] impl TransparentAuthorizingContext for Unauthorized { - fn input_amounts(&self) -> Vec> { + fn input_amounts(&self) -> Vec<(AssetType, u64)> { return self .inputs .iter() - .map(|txin| Amount::from_pair(txin.coin.asset_type, txin.coin.value)) + .map(|txin| (txin.coin.asset_type, txin.coin.value)) .collect(); } } @@ -217,7 +204,16 @@ impl TransparentAuthorizingContext for Unauthorized { impl Bundle { pub fn apply_signatures(self) -> Bundle { transparent::Bundle { - vin: self.vin, + vin: self + .vin + .iter() + .map(|txin| TxIn { + asset_type: txin.asset_type, + address: txin.address, + value: txin.value, + transparent_sig: (), + }) + .collect(), vout: self.vout, authorization: Authorized, } diff --git a/masp_primitives/src/transaction/components/transparent/fees.rs b/masp_primitives/src/transaction/components/transparent/fees.rs index 812e8f0a..99522494 100644 --- a/masp_primitives/src/transaction/components/transparent/fees.rs +++ b/masp_primitives/src/transaction/components/transparent/fees.rs @@ -2,7 +2,8 @@ //! of a transaction. use super::TxOut; -use crate::transaction::{components::amount::Amount, TransparentAddress}; +use crate::asset_type::AssetType; +use crate::transaction::TransparentAddress; /// This trait provides a minimized view of a transparent input suitable for use in /// fee and change computation. @@ -15,17 +16,23 @@ pub trait InputView { /// fee and change computation. pub trait OutputView { /// Returns the value of the output being created. - fn value(&self) -> Amount; + 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. fn transparent_address(&self) -> &TransparentAddress; } impl OutputView for TxOut { - fn value(&self) -> Amount { - Amount::from_pair(self.asset_type, self.value).unwrap() + fn value(&self) -> u64 { + self.value + } + + fn asset_type(&self) -> AssetType { + self.asset_type } fn transparent_address(&self) -> &TransparentAddress { - &self.transparent_address + &self.address } } 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 20b3498c..c4d4f5d7 100644 --- a/masp_primitives/src/transaction/sighash.rs +++ b/masp_primitives/src/transaction/sighash.rs @@ -5,7 +5,7 @@ use blake2b_simd::Hash as Blake2bHash; use super::{ components::{ sapling::{self, GrothProofBytes}, - transparent, Amount, + transparent, }, sighash_v5::v5_signature_hash, Authorization, TransactionData, TxDigests, TxVersion, @@ -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>; + fn input_amounts(&self) -> Vec<(AssetType, u64)>; } /// Computes the signature hash for an input to a transaction, given diff --git a/masp_primitives/src/transaction/txid.rs b/masp_primitives/src/transaction/txid.rs index 2a02b757..abede069 100644 --- a/masp_primitives/src/transaction/txid.rs +++ b/masp_primitives/src/transaction/txid.rs @@ -11,7 +11,7 @@ use group::GroupEncoding; use crate::consensus::{BlockHeight, BranchId}; use super::{ - sapling::{self, OutputDescription, SpendDescription}, + sapling::{self, ConvertDescription, OutputDescription, SpendDescription}, transparent::{self, TxIn, TxOut}, Authorization, Authorized, TransactionDigest, TransparentDigests, TxDigests, TxId, TxVersion, }; @@ -33,6 +33,8 @@ const ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendsHash" const ZCASH_SAPLING_SPENDS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendCHash"; const ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendNHash"; +const ZCASH_SAPLING_CONVERTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdConvertHash"; + const ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutputHash"; const ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutC__Hash"; const ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutM__Hash"; @@ -62,10 +64,18 @@ pub(crate) fn transparent_outputs_hash>(vout: &[T]) -> Blake2bH /// to a hash personalized by ZCASH_INPUTS_HASH_PERSONALIZATION. /// In the case that no inputs are provided, this produces a default /// hash from just the personalization string. -pub(crate) fn transparent_inputs_hash>(vin: &[T]) -> Blake2bHash { +pub(crate) fn transparent_inputs_hash< + TransparentAuth: transparent::Authorization, + T: Borrow>, +>( + vin: &[T], +) -> Blake2bHash { let mut h = hasher(ZCASH_INPUTS_HASH_PERSONALIZATION); for t_in in vin { - t_in.borrow().write(&mut h).unwrap(); + let t_in = t_in.borrow(); + h.write_all(t_in.asset_type.get_identifier()).unwrap(); + h.write_all(&t_in.value.to_le_bytes()).unwrap(); + h.write_all(&t_in.address.0).unwrap(); } h.finalize() } @@ -101,6 +111,24 @@ pub(crate) fn hash_sapling_spends( h.finalize() } +/// Implements ZIP 244-like hashing of MASP convert descriptions. +/// +/// Write disjoint parts of each MASP shielded convert to a hash: +/// * \[(cv, anchor)*\] - personalized with ZCASH_SAPLING_CONVERTS_HASH_PERSONALIZATION +/// +pub(crate) fn hash_sapling_converts( + shielded_converts: &[ConvertDescription], +) -> Blake2bHash { + let mut h = hasher(ZCASH_SAPLING_CONVERTS_HASH_PERSONALIZATION); + if !shielded_converts.is_empty() { + for s_convert in shielded_converts { + h.write_all(&s_convert.cv.to_bytes()).unwrap(); + h.write_all(&s_convert.anchor.to_repr()).unwrap(); + } + } + h.finalize() +} + /// Implements [ZIP 244 section T.3b](https://zips.z.cash/zip-0244#t-3b-sapling-outputs-digest) /// /// Write disjoint parts of each Sapling shielded output as 3 separate hashes: @@ -120,12 +148,18 @@ pub(crate) fn hash_sapling_outputs( for s_out in shielded_outputs { ch.write_all(s_out.cmu.to_repr().as_ref()).unwrap(); ch.write_all(s_out.ephemeral_key.as_ref()).unwrap(); - ch.write_all(&s_out.enc_ciphertext[..52]).unwrap(); + ch.write_all(&s_out.enc_ciphertext[..masp_note_encryption::COMPACT_NOTE_SIZE]) + .unwrap(); - mh.write_all(&s_out.enc_ciphertext[52..564]).unwrap(); + mh.write_all( + &s_out.enc_ciphertext[masp_note_encryption::COMPACT_NOTE_SIZE + ..masp_note_encryption::NOTE_PLAINTEXT_SIZE], + ) + .unwrap(); nh.write_all(&s_out.cv.to_bytes()).unwrap(); - nh.write_all(&s_out.enc_ciphertext[564..]).unwrap(); + nh.write_all(&s_out.enc_ciphertext[masp_note_encryption::NOTE_PLAINTEXT_SIZE..]) + .unwrap(); nh.write_all(&s_out.out_ciphertext).unwrap(); } @@ -186,10 +220,14 @@ fn hash_sapling_txid_data< bundle: &sapling::Bundle, ) -> Blake2bHash { let mut h = hasher(ZCASH_SAPLING_HASH_PERSONALIZATION); - if !(bundle.shielded_spends.is_empty() && bundle.shielded_outputs.is_empty()) { + if !(bundle.shielded_spends.is_empty() + && bundle.shielded_converts.is_empty() + && bundle.shielded_outputs.is_empty()) + { h.write_all(hash_sapling_spends(&bundle.shielded_spends).as_bytes()) .unwrap(); - + h.write_all(hash_sapling_converts(&bundle.shielded_converts).as_bytes()) + .unwrap(); h.write_all(hash_sapling_outputs(&bundle.shielded_outputs).as_bytes()) .unwrap(); @@ -332,7 +370,7 @@ impl TransactionDigest for BlockTxCommitmentDigester { for txout in &bundle.vout { h.write_all(txout.asset_type.get_identifier()).unwrap(); h.write_all(&txout.value.to_le_bytes()).unwrap(); - h.write_all(&txout.transparent_address.serialize()).unwrap(); + h.write_all(&txout.address.0).unwrap(); } } h.finalize() diff --git a/masp_proofs/Cargo.toml b/masp_proofs/Cargo.toml index b7b90d08..e7618e95 100644 --- a/masp_proofs/Cargo.toml +++ b/masp_proofs/Cargo.toml @@ -36,7 +36,6 @@ tracing = "0.1" blake2b_simd = "1" directories = { version = "4", optional = true } redjubjub = "0.5" -wagyu-zcash-parameters = { version = "0.2", optional = true } getrandom = { version = "0.2", features = ["js"] } itertools = "0.10.1" @@ -50,7 +49,7 @@ pprof = { version = "0.11", features = ["criterion", "flamegraph"] } # MSRV 1.56 [features] default = ["local-prover", "multicore"] -bundled-prover = ["wagyu-zcash-parameters"] +bundled-prover = [] download-params = ["minreq", "directories"] local-prover = ["directories"] multicore = ["bellman/multicore"] 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/lib.rs b/masp_proofs/src/lib.rs index ff6ade0d..d9cb7bd1 100644 --- a/masp_proofs/src/lib.rs +++ b/masp_proofs/src/lib.rs @@ -15,6 +15,11 @@ use std::fs::File; use std::io::{self, BufReader}; use std::path::Path; +pub use bellman; +pub use bls12_381; +pub use group; +pub use jubjub; + #[cfg(feature = "directories")] use directories::BaseDirs; #[cfg(feature = "directories")] diff --git a/masp_proofs/src/prover.rs b/masp_proofs/src/prover.rs index b61fbc1b..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; @@ -150,7 +150,7 @@ impl LocalTxProver { // spend_vk: p.spend_vk, // output_params: p.output_params, // } - // } + //} } impl TxProver for LocalTxProver { @@ -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 {