Skip to content

feat: adapt the layout of Assets to the double word representation#2437

Merged
bobbinth merged 148 commits intonextfrom
pgackst-asset-layout
Mar 3, 2026
Merged

feat: adapt the layout of Assets to the double word representation#2437
bobbinth merged 148 commits intonextfrom
pgackst-asset-layout

Conversation

@PhilippGackstatter
Copy link
Contributor

@PhilippGackstatter PhilippGackstatter commented Feb 12, 2026

Refactors the layout of fungible and non-fungible assets. The main benefit for now is that non-fungible assets include the full account ID. The other good news is that this PR removes the last unsafe code from miden base 😅.

Layout Changes

For direct comparison, the previous layout was:

  • Fungible asset value: [amount, 0, faucet_id_suffix, faucet_id_prefix].
  • Fungible asset key: [0, 0, faucet_id_suffix, faucet_id_prefix].
  • Non-fungible asset value: [hash0, hash1, hash2, faucet_id_prefix].
  • Non-fungible asset key: [faucet_id_prefix, hash1, hash2, hash0'].

The new layout is:

  • Fungible asset value: [amount, 0, 0, 0].
  • Fungible asset key: [0, 0, faucet_id_suffix, faucet_id_prefix].
  • Non-fungible asset value: [hash0, hash1, hash2, hash3].
  • Non-fungible asset key: [hash0, hash1, faucet_id_suffix, faucet_id_prefix].

The most notable change is that non-fungible assets now contain the full AccountId instead of just the AccountIdPrefix. I believe this removes the last reason we had that account ID prefixes needed to be unique in the account tree. So, we could now consider lifting this restriction.

Account Delta

The account delta is impacted by the layout change, namely that non-fungible asset's were previously committed to by hashing their value. Because the value doesn't contain the faucet ID anymore, the delta also doesn't commit to the asset's issuer anymore and so the delta format needed to be changed to commit to the faucet ID. This means the asset key of the non-fungible asset is now included.

For security, the faucet ID prefix (specifically the bit in the account ID that determines the faucet type) must be at the same position in the layout to avoid introducing an ambiguity in the delta format. So, the layout of the fungible asset's delta elements was adapted as well. Overall it is now:

Fungible Assets

  • [domain = 1, was_added, faucet_id_suffix, faucet_id_prefix]
  • [amount_delta, 0, 0, 0]

Non-Fungible Assets

  • [domain = 1, was_added, faucet_id_suffix, faucet_id_prefix]
  • [hash0, hash1, hash2, hash3] (= ASSET_VALUE)

See docs of AccountDelta::to_commitment for all the details.

This change included updates to the delta commitment computation as well as how non-fungible assets are tracked in the delta.

Note that the NonFungibleAssetDelta's internal representation is a bit ugly, but a larger refactors doesn't make sense at this point since this may change more fundamentally when generalizing assets.

Asset Key Ordering

AssetVaultKey implements Ord and PartialOrd based on LexicographicWord for use in the delta. Not strictly necessary, but convenient. I also added implementations for Asset that delegate to AssetVaultKey, but these are currently unused.

Asset Modules

This PR commits further to the fungible_asset and now non_fungible_asset module in the kernel for implementing their respective logic. If we move this logic elsewhere later, this should make this process easier.

Transaction Outputs

Since the fee asset is a fungible asset, and it is represented as a double word, the FEE_ASSET_VALUE tx output needed to be adapted, as it would now only contain the amount, but not the faucet ID. The new approach is to output the individual required elements from the fungible asset, i.e. its faucet ID limbs and the amount. This also happens to make room for a full word on the output stack.

Tests

Fixes a couple of tests where asset key and value were incorrectly ordered but the test didn't fail before, presumably due to the similar layout of key and value.

Migration

  • miden::protocol::active_account::has_non_fungible_asset now takes an ASSET_KEY as an input.
  • miden::protocol::asset::create_non_fungible_asset now takes the full account ID as an input.
  • The previous asset to/from word conversions were removed, since an asset is now represented as a double word. The replacements are the to_key_word and to_value_word methods, and from_key_value and from_key_value_words constructors.
  • NonFungibleAssetDelta::new's signature has changed.
  • FungibleAsset::new and Asset::is_(non_)fungible are no longer const (I'm not sure these can realistically be used in a const context and it restricts future non-breaking internal changes).
  • {Asset, NonFungibleAsset, FungibleAsset}::faucet_id_prefix was removed.
  • NonFungibleAsset's and NonFungibleAssetDetails' structure has changed. They both contain the full AccountId now.
  • AssetVaultKey::{new_unchecked, faucet_id_prefix, as_word} were removed. Use new, faucet_id and to_word instead.
  • AssetVaultKey::from_account_id renamed to new_fungible since non-fungible asset vault keys cannot be created from just a faucet ID.

Follow-Up

closes #2328

Copy link
Contributor

@bobbinth bobbinth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Thank you! Not a full review - but I did review pretty much all Rust code and some MASM code as well. Left some comments inline - mostly nits.

Comment on lines 205 to 208
/// Returns ID of the faucet which issued this asset.
pub fn faucet_id(&self) -> AccountIdPrefix {
pub fn faucet_id(&self) -> AccountId {
self.faucet_id
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to one of the other comments (and not for this PR), I wonder if we should name methods like this as issuer().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally agreed. Should we replace the whole "faucet account" terminology with "issuer account" or only some of it? Have we made a decision on whether all accounts will be able to issue assets (and remove the faucet account types)?

I think issuer makes sense for these kinds of methods or fields, but "issuer account" is not really clearer than "faucet account", I think. I think my favorite option would be removing AccountType altogether.

Comment on lines +38 to +40
/// Creates an [`AssetVaultKey`] from its parts.
pub fn new(asset_id: AssetId, faucet_id: AccountId) -> Self {
Self { asset_id, faucet_id }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I find the order here a bit odd as the faucet_id is the "bigger" category. I'd probably do this like so:

pub fn new(faucet_id: AccountId, asset_id: AssetId) -> Self {
        Self { faucet_id, asset_id }
}

And in the future would use the issuer nomenclature:

pub fn new(issuer: AccountId, asset_id: AssetId) -> Self {
        Self { issuer, asset_id }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the VM migration (specifically #2512) we would have:

impl AssetId {
    pub fn new(suffix: Felt, prefix: Felt) -> Self { ... }
}

impl StorageSlotId {
    pub fn new(suffix: Felt, prefix: Felt) -> Self { ... }
}

impl AccountId {
    pub fn try_from_elements(suffix: Felt, prefix: Felt) -> Result<Self, AccountIdError> { ... }
}

impl AssetVaultKey {
    pub fn new(asset_id: AssetId, faucet_id: AccountId) -> Self { ... }
}

In my mind, these are consistent in that the "more significant" element comes last. This is nice because at that point we always have suffix then prefix order (whereas before the migration we had both, and it lead to confusion) and AssetVaultKey is consistent with that, too.

We can reverse it to have the more significant element come first, but it would re-introduce APIs where we need to use prefix then suffix order. Maybe this would prioritize something other than that kind of consistency, which may be fine.

Copy link
Collaborator

@igamigo igamigo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Just left a couple of very minor comments

Copy link
Contributor

@bobbinth bobbinth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Thank you! I left some comments inline, but these are mostly naming nits that can be addressed in follow-up PRs.

@@ -61,3 +113,94 @@ end
pub proc get_amount
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would maybe rename this into value_into_amount.

Base automatically changed from pgackst-user-asset-key-value to next March 3, 2026 07:53
@PhilippGackstatter PhilippGackstatter force-pushed the pgackst-asset-layout branch 3 times, most recently from 1e2ca48 to 24a98a6 Compare March 3, 2026 08:46
@bobbinth bobbinth merged commit e857006 into next Mar 3, 2026
34 checks passed
@bobbinth bobbinth deleted the pgackst-asset-layout branch March 3, 2026 09:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-from-maintainers PRs that come from internal contributors or integration partners. They should be given priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expand asset layout from one to two words

5 participants