Refactor AssetSupply to AssetRecords and update issuance verification functions#138
Refactor AssetSupply to AssetRecords and update issuance verification functions#138
Conversation
… a finalization set, fix tests accordingly
… update verify_issue_bundle function to ensure the first asset issuance contains the reference note, update tests and docs accordingly
… verification only, update IssueAction::verify and return (AssetBase, NoteValue) from it instead of (AssetBase, AssetInfo<...>)
…ication test functions updated to check the result of the verify_issue_bundle directly
PaulLaux
left a comment
There was a problem hiding this comment.
Very good.
Added some semantic coments and one significant name change AssetInfo -> AssetRecord
Also, for get_action_by_asset() please fix comment to
/// # Returns
///
/// Returns `Some(&IssueAction)` if a single matching action is found.
/// Returns `None` if no action matches the given asset base.
(It is not very consise to start with If here)
Also, Add to the top of the file:
//! Issuance logic for Zcash Shielded Assets (ZSAs).
//!
//! This module defines structures and methods for creating, authorizing, and verifying
//! issuance bundles, which introduce new shielded assets into the Orchard protocol.
//!
//! The core components include:
//! - `IssueBundle`: Represents a collection of issuance actions with authorization states.
//! - `IssueAction`: Defines an individual issuance event, including asset details and notes.
//! - `IssueAuth` variants: Track issuance states from creation to finalization.
//! - `verify_issue_bundle`: Ensures issuance validity and prevents unauthorized asset creation.
//!
//! Errors related to issuance, such as invalid signatures or supply overflows,
//! are handled through the `Error` enum.
src/issuance.rs
Outdated
| /// - **Signature Verification**: | ||
| /// - Ensures the signature on the provided `sighash` matches the bundle’s authorization. | ||
| /// - **IssueAction Verification**: | ||
| /// - Checks that the asset description size is correct. |
There was a problem hiding this comment.
let's move this check (here and code) inside verify()
src/issuance.rs
Outdated
| })?; | ||
|
|
||
| Ok(supply_info) | ||
| Ok(verified_asset_states) |
There was a problem hiding this comment.
very good.
I did renames to simplify and adjust to the new record terminology. Semantics are the same:
pub fn verify_issue_bundle(
bundle: &IssueBundle<Signed>,
sighash: [u8; 32],
get_global_records: impl Fn(&AssetBase) -> Option<AssetRecord>,
) -> Result<HashMap<AssetBase, AssetRecord>, Error> {
bundle
.ik()
.verify(&sighash, bundle.authorization().signature())
.map_err(|_| IssueBundleInvalidSignature)?;
let new_records =
bundle
.actions()
.iter()
.try_fold(HashMap::new(), |mut new_records, action| {
if !is_asset_desc_of_valid_size(action.asset_desc()) {
return Err(WrongAssetDescSize);
}
let (asset, amount) = action.verify(bundle.ik())?;
let is_finalized = action.is_finalized();
let ref_note = action.get_reference_note();
let new_asset_record = match new_records
.get(&asset)
.cloned()
.or_else(|| get_global_records(&asset))
{
// The first issuance of the asset
None => AssetRecord::new(
amount,
is_finalized,
*ref_note.ok_or(MissingReferenceNoteOnFirstIssuance)?,
),
// Subsequent issuances of the asset
Some(current_record) => {
let amount =
(current_record.amount + amount).ok_or(ValueOverflow)?;
if current_record.is_finalized {
return Err(IssueActionPreviouslyFinalizedAssetBase(asset));
}
AssetRecord::new(amount, is_finalized, current_record.reference_note)
}
};
new_records.insert(asset, new_asset_record);
Ok(new_records)
})?;
Ok(new_records)
}
src/issuance.rs
Outdated
|
|
||
| // A closure to build an issue bundle using parameters from `setup_params`. | ||
| // Using a closure avoids passing `rng`, `ik`, etc. each time. | ||
| let build_issue_bundle = |data: &[(&Vec<u8>, u64, bool, bool)]| -> IssueBundle<Signed> { |
There was a problem hiding this comment.
Consider defining a proper function here for clerity
There was a problem hiding this comment.
Fixed (converted it to a function).
PaulLaux
left a comment
There was a problem hiding this comment.
Good,
One more iteration is needed to finilize.
Also, please move is_reference_note() down, under create_referance_note()
src/issuance.rs
Outdated
| *ref_note.ok_or(MissingReferenceNoteOnFirstIssuance)?, | ||
| ), | ||
|
|
||
| // Subsequent issuances of the asset |
There was a problem hiding this comment.
Subsequent issuance (issuance is already plural)
src/issuance.rs
Outdated
| fn issue_bundle_verify_with_global_state() { | ||
| type GlobalState = HashMap<AssetBase, AssetRecord>; | ||
|
|
||
| fn first_note(bundle: &IssueBundle<Signed>, action_index: usize) -> Note { |
There was a problem hiding this comment.
consider
fn get_first_note(bundle: &IssueBundle<Signed>, action_index: usize) -> &Note {
bundle.actions()[action_index].notes().first().unwrap()
}better naming + you are cloning the notes anyway later.
src/issuance.rs
Outdated
| bundle.actions()[action_index].notes()[0] | ||
| } | ||
|
|
||
| fn build_expected_global_state(data: &[(&AssetBase, u64, bool, Note)]) -> GlobalState { |
There was a problem hiding this comment.
consider &Note. clone inside, if needed. Aslo for compatibility with get_first_note()->&Note
There was a problem hiding this comment.
also fn build_global_state()
There was a problem hiding this comment.
I refactored the code and removed the build_expected_global_state function. Now, I simply use HashMap::from([...]) instead, passing an array into it. To create the array elements, I added a simple build_state_entryfunction (forNote- it receives it by reference, i.e.&Note`).
src/issuance.rs
Outdated
| AssetRecord::new(NoteValue::from_raw(amount), is_finalized, reference_note), | ||
| ) | ||
| }) | ||
| .collect::<HashMap<_, _>>() |
There was a problem hiding this comment.
redundent, just collect()
There was a problem hiding this comment.
I refactored the code and use HashMap::from([...]) now, as mentioned above.
src/issuance.rs
Outdated
| .map(|(asset_base, amount, is_finalized, reference_note)| { | ||
| ( | ||
| asset_base.clone(), | ||
| AssetRecord::new(NoteValue::from_raw(amount), is_finalized, reference_note), |
There was a problem hiding this comment.
Fixed it (in new build_state_entry function, see comments above).
src/issuance.rs
Outdated
| [u8; 32], | ||
| Nullifier, | ||
| ), | ||
| data: &[(&Vec<u8>, u64, bool, bool)], |
There was a problem hiding this comment.
replace the tuple with a proper data struct and a constructor.
The bool,bool is very confusing
There was a problem hiding this comment.
Fixed - added BundleTestData struct for that with a constructor.
src/issuance.rs
Outdated
| ) -> IssueBundle<Signed> { | ||
| let (rng, ref isk, ref ik, recipient, sighash, ref first_nullifier) = *params; | ||
|
|
||
| let (asset_desc, amount, first_issuance, is_finalized) = data.first().unwrap().clone(); |
There was a problem hiding this comment.
we always do asset_desc, amount, is_finilized, .... Let's stick to this order
There was a problem hiding this comment.
Fixed (in the new BundleTestData struct I mentioned above).
src/issuance.rs
Outdated
| } | ||
|
|
||
| fn build_issue_bundle( | ||
| params: &( |
There was a problem hiding this comment.
why params is needed? Let's just go with named input params.
fn build_issue_bundle(
rng: OsRng,
isk: IssuanceAuthorizingKey,
ik: IssuanceValidatingKey,
recipient: Address,
sighash: [u8; 32],
first_nullifier: Nullifier,
note_list: &[TestNoteList],
) -> IssueBundle<Signed> {(it does not make sence to preserve the un-named tuple inside the function)
There was a problem hiding this comment.
I see that it is based on the old setup_params() function but complexity has grown since then.
(sighash and first_nullifer cannot be identified by their type)
Two ways forward:
-
continue using the
setup_params()but dcompose it into named params before calling the newbuild_issue_bundle() -
change
setup_paramsto return a struct and use the struct as an input to `build_issue_bundle.
Anyway we should have names to the params.
There was a problem hiding this comment.
Fixed - I introduced new struct called TestParams with those fields, so setup_params returns that struct now instead of a tuple. Updated all tests to use it (replaced tuple destructuring with struct destructuring).
…t as a return value of setup_params instead of a tuple
…refactor it according the #138 review comments
PaulLaux
left a comment
There was a problem hiding this comment.
Good restructure, added comments to finilize
…s issuance_global_state.rs, and use copies of setup_params and TestParams there. The code doesn't compile because some methods need to be public. Further fixes are required.
…b(crate) method to generate random keys
tests/issuance_global_state.rs
Outdated
|
|
||
| fn setup_params() -> TestParams { | ||
| let mut rng = OsRng; | ||
| use group::ff::{FromUniformBytes, PrimeField}; |
There was a problem hiding this comment.
Add a comment that these parameters are for testing global state only and should not be used in an actual setting.
This PR modifies the
verify_issue_bundlefunction and refactors thesupply_info.rsmodule by removingSupplyInfoand introducing a new generic type,AssetInfo, in place of the previousAssetSupplystruct.AssetInfoallows to properly distinguish the asset verification logic between reference notes required for first issuances versus optional references for subsequent issuances.Key Changes
Rename
AssetSupplystruct toAssetInfoRename
IssueAction::verify_supplytoIssueAction::verifyand update its return valueverifymethod now returns only the sum of the action's note values instead ofAssetInfo.is_finalizedandreference_noteaction properties are now handled directly in theverify_issue_bundlefunction.Revise
verify_issue_bundleget_global_asset_statecallback instead of relying on afinalizationset.HashMap<AssetBase, AssetInfo>(instead ofSupplyInfo).Remove
supply_info.rsSupplyInfostruct and related tests, sinceverify_issue_bundlenow returns aHashMapofAssetInfoobjects.Test updates
verify_issue_bundlesignature and theget_global_asset_stateapproach.Add issuance workflow test
issue_bundle_verify_with_global_statethat performs a series of bundle creations and verifications, with a global state simulation.Miscellaneous fixes
Errorvariant imports inissuance.rsby referencing the localErrorenum directly.