Skip to content

Account storage schema validation #2104

@mmagician

Description

@mmagician

Defining the component schema itself is rather straightforward, and is outlined in #2062 (comment).

Once the schema is defined, we need some way for apps (wallet, explorer, etc) to access this schema defined by contract developers, and apply it to the onchain data (storage slot keys, values, types) they retrieve to correctly display the metadata about the storage slots. Let’s assume that we have a registry that hosts the schemas.

This issue is concerned with the validation of the schema.

Challenge 1: Schema validation

One of the challenges discussed in the retreat workshop is how the registry can validate storage schemas. Specifically, multiple schema definitions can be valid for the same account component.

Here’s an example (assuming named storage slots), where the first schema is the original one defined by the contract developer, and the second one is malicious:

[[storage]]
name = "approvers"
description = "Map with approvers"
type = "map"

[[storage.approvers.key]]
type = "u8"
name = "index"
description = "Index of the approver"

[[storage.approvers.value]]
type = "auth::rpo_falcon512::pub_key"
name = "pubkey"
description = "Falcon public signing key of the approver" 

and

[[storage]]
name = "approvers"
description = "Map with approvers"
type = "map"

[[storage.approvers.key]]
type = "u8"
name = "index"
description = "Index of the approver"

[[storage.approvers.value]]
type = "word"
name = "approver metadata"
description = "hash of the approver contact details" 

Without schema validation, if the wallet displays the second schema to the user, the user could be fooled into signing the more innocent-looking approver metadata changes, where in fact they might be giving up control over the account.


One potential solution to this is storing the commitment to the schema in one of the account storage slots itself. With named storage slots, this can become a common standard to store the commitment e.g. in the 0x1234::schema_commitment slot.

The commitment would be computed by canonically serializing the contents of the TOML, and hashing it to obtain a single-word value.

Then, to validate the schema, the registry (and the consuming apps) can easily validate that when the developer uploads the schema for a specific account, it matches that account’s onchain commitment.

Challenge 2: Component-wide vs. account-wide schema commitments

The description above assumes we have a single commitment to account storage, of ALL components together.

This is in contrast to how we currently create accounts, which is via individual AccountComponents, passed in as Packages, that together make up one Account.

If we were to maintain only a single commitment, the question is: which AccountComponent is the storage slot named 0x1234::schema_commitment defined in?

  • we have a special component just for this purpose (preference)
  • it is part of the first component in the list
  • other?

The natural alternative to a per-account schema, then, is having per-component schema, i.e. 0x1234::<component>::schema_commitment.

We discussed this approach as an alternative, but now that I think of this, I don’t think the explorer would have a way of making sense of per-component commitments, because the onchain representation of the account no longer recognizes the distinction between the various AccountComponents (TODO is this correct?):

pub struct Account {
    id: AccountId,
    vault: AssetVault,
    storage: AccountStorage,
    code: AccountCode,
    nonce: Felt,
    seed: Option<Word>,
}

So even if the some of the components were “known” (with scheme available in the registry), while the others were new, such as for ExtendedWallet that holds the BasicWallet component + some custom component, the application that only sees AccountStorage onchain:

pub struct AccountStorage {
    slots: Vec<StorageSlot>,
}

cannot distinguish which StorageSlots come from which component.


Summary

Given all of the above, I think the best way forward right now is to create a dedicated AccountComponent with a single storage slot that holds per-account schema.

This would not be enforced at the protocol level, and thus be fully optional - but we could enshrine this in the client's account creation process, such that it becomes a de facto standard that the registry, explorer etc. can follow.

This special component would be created at the time of Account creation. Looking at the account creation process in the CLI, for example, we could do this as part of [process_packages](https://github.com/0xMiden/miden-client/blob/8f3e8c806187363319b9907814418984528f3c05/bin/miden-cli/src/commands/new_account.rs#L457), because there we have all the data necessary (inside Vec<Package>) to create the new component:

fn process_packages(
    packages: Vec<Package>,
    init_storage_data: &InitStorageData,
) -> Result<Vec<AccountComponent>, CliError>;

Perhaps a bigger question is whether the modularity of AccountComponent s still makes sense, now that storage slots are no longer indexed.

It would be great to understand the motivation behind the concept of AccountComponents, each with their own storage.


cc @igamigo lmk if I missed something (feel free to edit the issue directly)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions