Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- name: Install dependencies (needed for yeslogic-fontconfig-sys crate)
if: runner.os == 'Linux'
run: sudo apt update && sudo apt install -y libfontconfig1-dev
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
id: toolchain
Expand Down Expand Up @@ -91,6 +94,9 @@ jobs:
name: Intra-doc links
runs-on: ubuntu-latest
steps:
- name: Install dependencies (needed for yeslogic-fontconfig-sys crate)
if: runner.os == 'Linux'
run: sudo apt update && sudo apt install -y libfontconfig1-dev
- uses: actions/checkout@v4
- run: cargo fetch
# Requires #![deny(rustdoc::broken_intra_doc_links)] in crates.
Expand Down
49 changes: 26 additions & 23 deletions src/bundle/burn_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::HashMap, fmt};

use crate::note::AssetBase;
use crate::{note::AssetBase, value::NoteValue};

/// Possible errors that can occur during bundle burn validation.
#[derive(Debug)]
Expand All @@ -14,56 +14,59 @@ 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 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).
/// * `burns` - 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();
pub fn validate_bundle_burn<'a, I: IntoIterator<Item = &'a (AssetBase, NoteValue)>>(
burn: I,
) -> Result<HashMap<AssetBase, NoteValue>, BurnError> {
let mut burn_set = HashMap::<AssetBase, NoteValue>::new();

for (asset, value) in bundle_burn {
if !asset_set.insert(asset) {
return Err(BurnError::DuplicateAsset);
}
for (asset, value) in burn.into_iter().cloned() {
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, value).is_some() {
return Err(BurnError::DuplicateAsset);
}
}

Ok(())
Ok(burn_set)
}

impl fmt::Display for BurnError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
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.")
}
}
}
}

#[cfg(test)]
mod tests {
use crate::value::NoteValue;

use super::*;

/// Creates an item of bundle burn list for a given asset description and value.
Expand All @@ -80,14 +83,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),
)
}

Expand Down Expand Up @@ -121,7 +124,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),
];

Expand All @@ -140,6 +143,6 @@ mod tests {

let result = validate_bundle_burn(&bundle_burn);

assert_eq!(result, Err(BurnError::NonPositiveAmount));
assert_eq!(result, Err(BurnError::ZeroAmount));
}
}
103 changes: 51 additions & 52 deletions src/issuance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ 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};
Expand Down Expand Up @@ -114,7 +114,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.
Expand All @@ -132,7 +132,7 @@ impl IssueAction {
let value_sum = self
.notes
.iter()
.try_fold(ValueSum::zero(), |value_sum, &note| {
.try_fold(NoteValue::zero(), |value_sum, &note| {
//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);
Expand All @@ -145,7 +145,7 @@ 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((
Expand Down Expand Up @@ -298,6 +298,21 @@ impl<T: IssueAuth> IssueBundle<T> {
authorization: map_auth(authorization),
}
}

/// Returns the reference notes for the `IssueBundle`.
pub fn get_reference_notes(&self) -> HashMap<AssetBase, Note> {
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
}
}

impl IssueBundle<Unauthorized> {
Expand Down Expand Up @@ -468,23 +483,6 @@ impl IssueBundle<Unauthorized> {
}
}

impl<T: IssueAuth> IssueBundle<T> {
/// Returns the reference notes for the `IssueBundle`.
pub fn get_reference_notes(self) -> HashMap<AssetBase, Note> {
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(),
Expand Down Expand Up @@ -561,12 +559,14 @@ impl IssueBundle<Signed> {
/// * 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.
/// A Result containing a SupplyInfo struct, which stores supply information and reference notes in
/// two HashMaps. 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) and a bool indicating whether the asset is finalized. The HashMap `reference_notes` uses
/// AssetBase as the key, and a Note struct as the value. The Note is the reference note for this
/// asset.
///
/// # Errors
///
Expand All @@ -576,7 +576,7 @@ impl IssueBundle<Signed> {
/// 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
Expand All @@ -591,26 +591,25 @@ pub fn verify_issue_bundle(
.verify(&sighash, &bundle.authorization.signature)
.map_err(|_| IssueBundleInvalidSignature)?;

let supply_info =
bundle
.actions()
.iter()
.try_fold(SupplyInfo::new(), |mut supply_info, action| {
if !is_asset_desc_of_valid_size(action.asset_desc()) {
return Err(WrongAssetDescSize);
}
let supply_info = bundle.actions().iter().try_fold(
SupplyInfo::new(bundle.get_reference_notes()),
|mut supply_info, action| {
if !is_asset_desc_of_valid_size(action.asset_desc()) {
return Err(WrongAssetDescSize);
}

let (asset, supply) = action.verify_supply(bundle.ik())?;
let (asset, supply) = action.verify_supply(bundle.ik())?;

// Fail if the asset was previously finalized.
if finalized.contains(&asset) {
return Err(IssueActionPreviouslyFinalizedAssetBase(asset));
}
// Fail if the asset was previously finalized.
if finalized.contains(&asset) {
return Err(IssueActionPreviouslyFinalizedAssetBase(asset));
}

supply_info.add_supply(asset, supply)?;
supply_info.add_supply(asset, supply)?;

Ok(supply_info)
})?;
Ok(supply_info)
},
)?;

Ok(supply_info)
}
Expand All @@ -636,7 +635,7 @@ pub enum Error {
IssueActionPreviouslyFinalizedAssetBase(AssetBase),

/// Overflow error occurred while calculating the value of the asset
ValueSumOverflow,
ValueOverflow,
}

impl fmt::Display for Error {
Expand Down Expand Up @@ -672,7 +671,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"
Expand All @@ -696,7 +695,7 @@ mod tests {
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;
Expand Down Expand Up @@ -829,7 +828,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);
}

Expand All @@ -854,7 +853,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);
}

Expand Down Expand Up @@ -1229,15 +1228,15 @@ mod tests {

assert_eq!(
supply_info.assets.get(&asset1_base),
Some(&AssetSupply::new(ValueSum::from_raw(15), true))
Some(&AssetSupply::new(NoteValue::from_raw(15), true))
);
assert_eq!(
supply_info.assets.get(&asset2_base),
Some(&AssetSupply::new(ValueSum::from_raw(10), true))
Some(&AssetSupply::new(NoteValue::from_raw(10), true))
);
assert_eq!(
supply_info.assets.get(&asset3_base),
Some(&AssetSupply::new(ValueSum::from_raw(5), false))
Some(&AssetSupply::new(NoteValue::from_raw(5), false))
);
}

Expand Down
Loading