diff --git a/src/bundle/burn_validation.rs b/src/bundle/burn_validation.rs index d05c43294..d6388f10c 100644 --- a/src/bundle/burn_validation.rs +++ b/src/bundle/burn_validation.rs @@ -2,9 +2,9 @@ //! //! The module provides a function `validate_bundle_burn` that can be used to validate the burn values for the bundle. //! -use std::fmt; +use std::{collections::HashSet, fmt}; -use crate::note::AssetBase; +use crate::{note::AssetBase, value::NoteValue}; /// Possible errors that can occur during bundle burn validation. #[derive(Debug)] @@ -14,36 +14,36 @@ pub enum BurnError { DuplicateAsset, /// Cannot burn a native asset. NativeAsset, - /// Cannot burn an asset with a non-positive value. - NonPositiveAmount, + /// Cannot burn an asset with a zero value. + ZeroAmount, } -/// Validates burn for a bundle by ensuring each asset is unique, non-native, and has a positive value. +/// Validates burn for a bundle by ensuring each asset is unique, non-native, and has a non-zero value. /// -/// Each burn element is represented as a tuple of `AssetBase` and `i64` (value for the burn). +/// Each burn element is represented as a tuple of `AssetBase` and `NoteValue` (value for the burn). /// /// # Arguments /// -/// * `burn` - A vector of assets, where each asset is represented as a tuple of `AssetBase` and `i64` (value the burn). +/// * `burn` - A vector of assets, where each asset is represented as a tuple of `AssetBase` and `NoteValue` (value the burn). /// /// # Errors /// /// Returns a `BurnError` if: -/// * Any asset in the `burn` vector is not unique (`BurnError::DuplicateAsset`). /// * Any asset in the `burn` vector is native (`BurnError::NativeAsset`). -/// * Any asset in the `burn` vector has a non-positive value (`BurnError::NonPositiveAmount`). -pub fn validate_bundle_burn(bundle_burn: &Vec<(AssetBase, i64)>) -> Result<(), BurnError> { - let mut asset_set = std::collections::HashSet::<&AssetBase>::new(); +/// * Any asset in the `burn` vector has a zero value (`BurnError::ZeroAmount`). +/// * Any asset in the `burn` vector is not unique (`BurnError::DuplicateAsset`). +pub fn validate_bundle_burn(burn: &[(AssetBase, NoteValue)]) -> Result<(), BurnError> { + let mut burn_set = HashSet::new(); - for (asset, value) in bundle_burn { - if !asset_set.insert(asset) { - return Err(BurnError::DuplicateAsset); - } + for (asset, value) in burn { if asset.is_native().into() { return Err(BurnError::NativeAsset); } - if *value <= 0 { - return Err(BurnError::NonPositiveAmount); + if value.inner() == 0 { + return Err(BurnError::ZeroAmount); + } + if !burn_set.insert(*asset) { + return Err(BurnError::DuplicateAsset); } } @@ -55,8 +55,8 @@ impl fmt::Display for BurnError { match *self { BurnError::DuplicateAsset => write!(f, "Encountered a duplicate asset to burn."), BurnError::NativeAsset => write!(f, "Cannot burn a native asset."), - BurnError::NonPositiveAmount => { - write!(f, "Cannot burn an asset with a non-positive value.") + BurnError::ZeroAmount => { + write!(f, "Cannot burn an asset with a zero value.") } } } @@ -64,6 +64,8 @@ impl fmt::Display for BurnError { #[cfg(test)] mod tests { + use crate::value::NoteValue; + use super::*; /// Creates an item of bundle burn list for a given asset description and value. @@ -80,14 +82,14 @@ mod tests { /// /// A tuple `(AssetBase, Amount)` representing the burn list item. /// - pub fn get_burn_tuple(asset_desc: &[u8], value: i64) -> (AssetBase, i64) { + pub fn get_burn_tuple(asset_desc: &[u8], value: u64) -> (AssetBase, NoteValue) { use crate::keys::{IssuanceAuthorizingKey, IssuanceValidatingKey}; let isk = IssuanceAuthorizingKey::from_bytes([1u8; 32]).unwrap(); ( AssetBase::derive(&IssuanceValidatingKey::from(&isk), asset_desc), - value, + NoteValue::from_raw(value), ) } @@ -121,7 +123,7 @@ mod tests { fn validate_bundle_burn_native_asset() { let bundle_burn = vec![ get_burn_tuple(b"Asset 1", 10), - (AssetBase::native(), 20), + (AssetBase::native(), NoteValue::from_raw(20)), get_burn_tuple(b"Asset 3", 10), ]; @@ -140,6 +142,6 @@ mod tests { let result = validate_bundle_burn(&bundle_burn); - assert_eq!(result, Err(BurnError::NonPositiveAmount)); + assert_eq!(result, Err(BurnError::ZeroAmount)); } } diff --git a/src/issuance.rs b/src/issuance.rs index 08ea749e7..78832fe7c 100644 --- a/src/issuance.rs +++ b/src/issuance.rs @@ -4,7 +4,7 @@ use group::Group; use k256::schnorr; use nonempty::NonEmpty; use rand::RngCore; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::fmt; use crate::bundle::commitments::{hash_issue_bundle_auth_data, hash_issue_bundle_txid_data}; @@ -12,17 +12,26 @@ use crate::constants::reference_keys::ReferenceKeys; use crate::issuance::Error::{ AssetBaseCannotBeIdentityPoint, IssueActionNotFound, IssueActionPreviouslyFinalizedAssetBase, IssueActionWithoutNoteNotFinalized, IssueBundleIkMismatchAssetBase, - IssueBundleInvalidSignature, ValueSumOverflow, WrongAssetDescSize, + IssueBundleInvalidSignature, ValueOverflow, WrongAssetDescSize, }; use crate::keys::{IssuanceAuthorizingKey, IssuanceValidatingKey}; use crate::note::asset_base::is_asset_desc_of_valid_size; use crate::note::{AssetBase, Nullifier, Rho}; -use crate::value::{NoteValue, ValueSum}; +use crate::value::NoteValue; use crate::{Address, Note}; use crate::supply_info::{AssetSupply, SupplyInfo}; +/// Checks if a given note is a reference note. +/// +/// A reference note satisfies the following conditions: +/// - The note's value is zero. +/// - The note's recipient matches the reference recipient. +fn is_reference_note(note: &Note) -> bool { + note.value() == NoteValue::zero() && note.recipient() == ReferenceKeys::recipient() +} + /// A bundle of actions to be applied to the ledger. #[derive(Debug, Clone, PartialEq, Eq)] pub struct IssueBundle { @@ -114,7 +123,7 @@ impl IssueAction { /// /// This function may return an error in any of the following cases: /// - /// * `ValueSumOverflow`: If the total amount value of all notes in the `IssueAction` overflows. + /// * `ValueOverflow`: If the total amount value of all notes in the `IssueAction` overflows. /// /// * `IssueBundleIkMismatchAssetBase`: If the provided `ik` is not used to derive the /// `AssetBase` for **all** internal notes. @@ -132,7 +141,7 @@ impl IssueAction { let value_sum = self .notes .iter() - .try_fold(ValueSum::zero(), |value_sum, ¬e| { + .try_fold(NoteValue::zero(), |value_sum, ¬e| { //The asset base should not be the identity point of the Pallas curve. if bool::from(note.asset().cv_base().is_identity()) { return Err(AssetBaseCannotBeIdentityPoint); @@ -145,12 +154,16 @@ impl IssueAction { .ok_or(IssueBundleIkMismatchAssetBase)?; // The total amount should not overflow - (value_sum + note.value()).ok_or(ValueSumOverflow) + (value_sum + note.value()).ok_or(ValueOverflow) })?; Ok(( issue_asset, - AssetSupply::new(value_sum, self.is_finalized()), + AssetSupply::new( + value_sum, + self.is_finalized(), + self.get_reference_note().cloned(), + ), )) } @@ -163,6 +176,15 @@ impl IssueAction { 0b0000_0000 } } + + /// Returns the reference note if the first note matches the reference note criteria. + /// + /// A reference note must be the first note in the `notes` vector and satisfy the following: + /// - The note's value is zero. + /// - The note's recipient matches the reference recipient. + pub fn get_reference_note(&self) -> Option<&Note> { + self.notes.first().filter(|note| is_reference_note(note)) + } } /// Defines the authorization type of an Issue bundle. @@ -468,23 +490,6 @@ impl IssueBundle { } } -impl IssueBundle { - /// Returns the reference notes for the `IssueBundle`. - pub fn get_reference_notes(self) -> HashMap { - let mut reference_notes = HashMap::new(); - self.actions.iter().for_each(|action| { - action.notes.iter().for_each(|note| { - if (note.recipient() == ReferenceKeys::recipient()) - && (note.value() == NoteValue::zero()) - { - reference_notes.insert(note.asset(), *note); - } - }) - }); - reference_notes - } -} - fn create_reference_note(asset: AssetBase, mut rng: impl RngCore) -> Note { Note::new( ReferenceKeys::recipient(), @@ -561,12 +566,13 @@ impl IssueBundle { /// * For each `Note` inside an `IssueAction`: /// * All notes have the same, correct `AssetBase`. /// -// # Returns +/// # Returns /// /// A Result containing a SupplyInfo struct, which stores supply information in a HashMap. -/// The HashMap uses AssetBase as the key, and an AssetSupply struct as the value. The -/// AssetSupply contains a ValueSum (representing the total value of all notes for the asset) -/// and a bool indicating whether the asset is finalized. +/// The HashMap `assets` uses AssetBase as the key, and an AssetSupply struct as the +/// value. The AssetSupply contains a NoteValue (representing the total value of all notes for +/// the asset), a bool indicating whether the asset is finalized and a Note (the reference note +/// for this asset). /// /// # Errors /// @@ -576,7 +582,7 @@ impl IssueBundle { /// asset in the bundle is incorrect. /// * `IssueActionPreviouslyFinalizedAssetBase`: This error occurs if the asset has already been /// finalized (inserted into the `finalized` collection). -/// * `ValueSumOverflow`: This error occurs if an overflow happens during the calculation of +/// * `ValueOverflow`: This error occurs if an overflow happens during the calculation of /// the value sum for the notes in the asset. /// * `IssueBundleIkMismatchAssetBase`: This error is raised if the `AssetBase` derived from /// the `ik` (Issuance Validating Key) and the `asset_desc` (Asset Description) does not match @@ -636,7 +642,7 @@ pub enum Error { IssueActionPreviouslyFinalizedAssetBase(AssetBase), /// Overflow error occurred while calculating the value of the asset - ValueSumOverflow, + ValueOverflow, } impl fmt::Display for Error { @@ -672,7 +678,7 @@ impl fmt::Display for Error { IssueActionPreviouslyFinalizedAssetBase(_) => { write!(f, "the provided `AssetBase` has been previously finalized") } - ValueSumOverflow => { + ValueOverflow => { write!( f, "overflow error occurred while calculating the value of the asset" @@ -685,18 +691,19 @@ impl fmt::Display for Error { #[cfg(test)] mod tests { use super::{AssetSupply, IssueBundle, IssueInfo}; - use crate::constants::reference_keys::ReferenceKeys; use crate::issuance::Error::{ AssetBaseCannotBeIdentityPoint, IssueActionNotFound, IssueActionPreviouslyFinalizedAssetBase, IssueBundleIkMismatchAssetBase, IssueBundleInvalidSignature, WrongAssetDescSize, }; - use crate::issuance::{verify_issue_bundle, IssueAction, Signed, Unauthorized}; + use crate::issuance::{ + is_reference_note, verify_issue_bundle, IssueAction, Signed, Unauthorized, + }; use crate::keys::{ FullViewingKey, IssuanceAuthorizingKey, IssuanceValidatingKey, Scope, SpendingKey, }; use crate::note::{AssetBase, Nullifier, Rho}; - use crate::value::{NoteValue, ValueSum}; + use crate::value::NoteValue; use crate::{Address, Note}; use group::{Group, GroupEncoding}; use nonempty::NonEmpty; @@ -709,12 +716,11 @@ mod tests { /// /// The following checks are performed: /// - the note value of the reference note is equal to 0 - /// - the asset of the reference note is equal to the provided asset /// - the recipient of the reference note is equal to the reference recipient + /// - the asset of the reference note is equal to the provided asset fn verify_reference_note(note: &Note, asset: AssetBase) { - assert_eq!(note.value(), NoteValue::from_raw(0)); + assert!(is_reference_note(note)); assert_eq!(note.asset(), asset); - assert_eq!(note.recipient(), ReferenceKeys::recipient()); } fn setup_params() -> ( @@ -829,7 +835,7 @@ mod tests { let (asset, supply) = result.unwrap(); assert_eq!(asset, test_asset); - assert_eq!(supply.amount, ValueSum::from_raw(30)); + assert_eq!(supply.amount, NoteValue::from_raw(30)); assert!(!supply.is_finalized); } @@ -854,7 +860,7 @@ mod tests { let (asset, supply) = result.unwrap(); assert_eq!(asset, test_asset); - assert_eq!(supply.amount, ValueSum::from_raw(30)); + assert_eq!(supply.amount, NoteValue::from_raw(30)); assert!(supply.is_finalized); } @@ -975,10 +981,8 @@ mod tests { assert_eq!(first_note.value().inner(), 15); assert_eq!(first_note.asset(), third_asset); - let reference_notes = bundle.get_reference_notes(); - assert_eq!(reference_notes.len(), 2); - verify_reference_note(reference_notes.get(&asset).unwrap(), asset); - verify_reference_note(reference_notes.get(&third_asset).unwrap(), third_asset); + verify_reference_note(action.get_reference_note().unwrap(), asset); + verify_reference_note(action2.get_reference_note().unwrap(), third_asset); } #[test] @@ -1227,17 +1231,33 @@ mod tests { assert_eq!(supply_info.assets.len(), 3); + let reference_note1 = signed.actions()[0].notes()[0]; + let reference_note2 = signed.actions()[1].notes()[0]; + let reference_note3 = signed.actions()[2].notes()[0]; + assert_eq!( supply_info.assets.get(&asset1_base), - Some(&AssetSupply::new(ValueSum::from_raw(15), true)) + Some(&AssetSupply::new( + NoteValue::from_raw(15), + true, + Some(reference_note1) + )) ); assert_eq!( supply_info.assets.get(&asset2_base), - Some(&AssetSupply::new(ValueSum::from_raw(10), true)) + Some(&AssetSupply::new( + NoteValue::from_raw(10), + true, + Some(reference_note2) + )) ); assert_eq!( supply_info.assets.get(&asset3_base), - Some(&AssetSupply::new(ValueSum::from_raw(5), false)) + Some(&AssetSupply::new( + NoteValue::from_raw(5), + false, + Some(reference_note3) + )) ); } diff --git a/src/supply_info.rs b/src/supply_info.rs index 6fb69d516..6444201ec 100644 --- a/src/supply_info.rs +++ b/src/supply_info.rs @@ -2,24 +2,31 @@ use std::collections::{hash_map, HashMap, HashSet}; -use crate::{issuance::Error, note::AssetBase, value::ValueSum}; +use crate::{issuance::Error, note::AssetBase, value::NoteValue, Note}; -/// Represents the amount of an asset and its finalization status. +/// Represents the amount of an asset, its finalization status and reference note. #[derive(Debug, Clone, Copy)] #[cfg_attr(test, derive(PartialEq, Eq))] pub struct AssetSupply { /// The amount of the asset. - pub amount: ValueSum, + pub amount: NoteValue, + /// Whether or not the asset is finalized. pub is_finalized: bool, + + /// The reference note, `None` if this `AssetSupply` instance is created from an issue bundle that does not include + /// a reference note (a non-first issuance) + pub reference_note: Option, } impl AssetSupply { - /// Creates a new AssetSupply instance with the given amount and finalization status. - pub fn new(amount: ValueSum, is_finalized: bool) -> Self { + /// Creates a new AssetSupply instance with the given amount, finalization status and reference + /// note. + pub fn new(amount: NoteValue, is_finalized: bool, reference_note: Option) -> Self { Self { amount, is_finalized, + reference_note, } } } @@ -46,9 +53,9 @@ impl SupplyInfo { match self.assets.entry(asset) { hash_map::Entry::Occupied(entry) => { let supply = entry.into_mut(); - supply.amount = - (supply.amount + new_supply.amount).ok_or(Error::ValueSumOverflow)?; + supply.amount = (supply.amount + new_supply.amount).ok_or(Error::ValueOverflow)?; supply.is_finalized |= new_supply.is_finalized; + supply.reference_note = supply.reference_note.or(new_supply.reference_note); } hash_map::Entry::Vacant(entry) => { entry.insert(new_supply); @@ -87,11 +94,11 @@ mod tests { AssetBase::derive(&IssuanceValidatingKey::from(&isk), asset_desc) } - fn sum<'a, T: IntoIterator>(supplies: T) -> Option { + fn sum<'a, T: IntoIterator>(supplies: T) -> Option { supplies .into_iter() .map(|supply| supply.amount) - .try_fold(ValueSum::from_raw(0), |sum, value| sum + value) + .try_fold(NoteValue::from_raw(0), |sum, value| sum + value) } #[test] @@ -101,11 +108,11 @@ mod tests { let asset1 = create_test_asset(b"Asset 1"); let asset2 = create_test_asset(b"Asset 2"); - let supply1 = AssetSupply::new(ValueSum::from_raw(20), false); - let supply2 = AssetSupply::new(ValueSum::from_raw(30), true); - let supply3 = AssetSupply::new(ValueSum::from_raw(10), false); - let supply4 = AssetSupply::new(ValueSum::from_raw(10), true); - let supply5 = AssetSupply::new(ValueSum::from_raw(50), false); + let supply1 = AssetSupply::new(NoteValue::from_raw(20), false, None); + let supply2 = AssetSupply::new(NoteValue::from_raw(30), true, None); + let supply3 = AssetSupply::new(NoteValue::from_raw(10), false, None); + let supply4 = AssetSupply::new(NoteValue::from_raw(10), true, None); + let supply5 = AssetSupply::new(NoteValue::from_raw(50), false, None); assert_eq!(supply_info.assets.len(), 0); @@ -114,7 +121,7 @@ mod tests { assert_eq!(supply_info.assets.len(), 1); assert_eq!( supply_info.assets.get(&asset1), - Some(&AssetSupply::new(sum([&supply1]).unwrap(), false)) + Some(&AssetSupply::new(sum([&supply1]).unwrap(), false, None)) ); // Add supply2 @@ -122,7 +129,11 @@ mod tests { assert_eq!(supply_info.assets.len(), 1); assert_eq!( supply_info.assets.get(&asset1), - Some(&AssetSupply::new(sum([&supply1, &supply2]).unwrap(), true)) + Some(&AssetSupply::new( + sum([&supply1, &supply2]).unwrap(), + true, + None + )) ); // Add supply3 @@ -132,7 +143,8 @@ mod tests { supply_info.assets.get(&asset1), Some(&AssetSupply::new( sum([&supply1, &supply2, &supply3]).unwrap(), - true + true, + None )) ); @@ -143,7 +155,8 @@ mod tests { supply_info.assets.get(&asset1), Some(&AssetSupply::new( sum([&supply1, &supply2, &supply3, &supply4]).unwrap(), - true + true, + None )) ); @@ -154,12 +167,13 @@ mod tests { supply_info.assets.get(&asset1), Some(&AssetSupply::new( sum([&supply1, &supply2, &supply3, &supply4]).unwrap(), - true + true, + None )) ); assert_eq!( supply_info.assets.get(&asset2), - Some(&AssetSupply::new(sum([&supply5]).unwrap(), false)) + Some(&AssetSupply::new(sum([&supply5]).unwrap(), false, None)) ); } @@ -171,10 +185,10 @@ mod tests { let asset2 = create_test_asset(b"Asset 2"); let asset3 = create_test_asset(b"Asset 3"); - let supply1 = AssetSupply::new(ValueSum::from_raw(10), false); - let supply2 = AssetSupply::new(ValueSum::from_raw(20), true); - let supply3 = AssetSupply::new(ValueSum::from_raw(40), false); - let supply4 = AssetSupply::new(ValueSum::from_raw(50), true); + let supply1 = AssetSupply::new(NoteValue::from_raw(10), false, None); + let supply2 = AssetSupply::new(NoteValue::from_raw(20), true, None); + let supply3 = AssetSupply::new(NoteValue::from_raw(40), false, None); + let supply4 = AssetSupply::new(NoteValue::from_raw(50), true, None); assert!(supply_info.add_supply(asset1, supply1).is_ok()); assert!(supply_info.add_supply(asset1, supply2).is_ok()); diff --git a/src/value.rs b/src/value.rs index a9cb843d0..e8ab3365b 100644 --- a/src/value.rs +++ b/src/value.rs @@ -153,6 +153,14 @@ impl Sub for NoteValue { } } +impl Add for NoteValue { + type Output = Option; + + fn add(self, rhs: Self) -> Self::Output { + self.0.checked_add(rhs.0).map(NoteValue) + } +} + pub(crate) enum Sign { Positive, Negative,