Skip to main content

miden_protocol/account/
partial.rs

1use alloc::string::ToString;
2
3use miden_core::utils::{Deserializable, Serializable};
4use miden_core::{Felt, ZERO};
5
6use super::{Account, AccountCode, AccountId, PartialStorage};
7use crate::Word;
8use crate::account::{hash_account, validate_account_seed};
9use crate::asset::PartialVault;
10use crate::errors::AccountError;
11use crate::utils::serde::DeserializationError;
12
13/// A partial representation of an account.
14///
15/// A partial account is used as inputs to the transaction kernel and contains only the essential
16/// data needed for verification and transaction processing without requiring the full account
17/// state.
18///
19/// For new accounts, the partial storage must be the full initial account storage.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct PartialAccount {
22    /// The ID for the partial account
23    id: AccountId,
24    /// Partial representation of the account's vault, containing the vault root and necessary
25    /// proof information for asset verification
26    partial_vault: PartialVault,
27    /// Partial representation of the account's storage, containing the storage commitment and
28    /// proofs for specific storage slots that need to be accessed
29    partial_storage: PartialStorage,
30    /// Account code
31    code: AccountCode,
32    /// The current transaction nonce of the account
33    nonce: Felt,
34    /// The seed of the account ID, if any.
35    seed: Option<Word>,
36}
37
38impl PartialAccount {
39    // CONSTRUCTORS
40    // --------------------------------------------------------------------------------------------
41
42    /// Creates a new [`PartialAccount`] with the provided account parts and seed.
43    ///
44    /// # Errors
45    ///
46    /// Returns an error if:
47    /// - an account seed is provided but the account's nonce indicates the account already exists.
48    /// - an account seed is not provided but the account's nonce indicates the account is new.
49    /// - an account seed is provided but the account ID derived from it is invalid or does not
50    ///   match the provided ID.
51    pub fn new(
52        id: AccountId,
53        nonce: Felt,
54        code: AccountCode,
55        partial_storage: PartialStorage,
56        partial_vault: PartialVault,
57        seed: Option<Word>,
58    ) -> Result<Self, AccountError> {
59        validate_account_seed(id, code.commitment(), partial_storage.commitment(), seed, nonce)?;
60
61        let account = Self {
62            id,
63            nonce,
64            code,
65            partial_storage,
66            partial_vault,
67            seed,
68        };
69
70        Ok(account)
71    }
72
73    // ACCESSORS
74    // --------------------------------------------------------------------------------------------
75
76    /// Returns the account's unique identifier.
77    pub fn id(&self) -> AccountId {
78        self.id
79    }
80
81    /// Returns the account's current nonce value.
82    pub fn nonce(&self) -> Felt {
83        self.nonce
84    }
85
86    /// Returns a reference to the account code.
87    pub fn code(&self) -> &AccountCode {
88        &self.code
89    }
90
91    /// Returns a reference to the partial storage representation of the account.
92    pub fn storage(&self) -> &PartialStorage {
93        &self.partial_storage
94    }
95
96    /// Returns a reference to the partial vault representation of the account.
97    pub fn vault(&self) -> &PartialVault {
98        &self.partial_vault
99    }
100
101    /// Returns the seed of the account's ID if the account is new.
102    ///
103    /// That is, if [`PartialAccount::is_new`] returns `true`, the seed will be `Some`.
104    pub fn seed(&self) -> Option<Word> {
105        self.seed
106    }
107
108    /// Returns `true` if the account is new, `false` otherwise.
109    ///
110    /// An account is considered new if the account's nonce is zero and it hasn't been registered on
111    /// chain yet.
112    pub fn is_new(&self) -> bool {
113        self.nonce == ZERO
114    }
115
116    /// Returns the commitment of this account.
117    ///
118    /// The commitment of an account is computed as:
119    ///
120    /// ```text
121    /// hash(id, nonce, vault_root, storage_commitment, code_commitment).
122    /// ```
123    pub fn commitment(&self) -> Word {
124        hash_account(
125            self.id,
126            self.nonce,
127            self.vault().root(),
128            self.storage().commitment(),
129            self.code().commitment(),
130        )
131    }
132
133    /// Returns the commitment of this account as used for the initial account state commitment in
134    /// transaction proofs.
135    ///
136    /// For existing accounts, this is exactly the same as [Account::commitment()], however, for new
137    /// accounts this value is set to [`Word::empty`]. This is because when a transaction is
138    /// executed against a new account, public input for the initial account state is set to
139    /// [`Word::empty`] to distinguish new accounts from existing accounts. The actual
140    /// commitment of the initial account state (and the initial state itself), are provided to
141    /// the VM via the advice provider.
142    pub fn initial_commitment(&self) -> Word {
143        if self.is_new() {
144            Word::empty()
145        } else {
146            self.commitment()
147        }
148    }
149
150    /// Returns `true` if the full state of the account is public on chain, and `false` otherwise.
151    pub fn has_public_state(&self) -> bool {
152        self.id.has_public_state()
153    }
154
155    /// Consumes self and returns the underlying parts of the partial account.
156    pub fn into_parts(
157        self,
158    ) -> (AccountId, PartialVault, PartialStorage, AccountCode, Felt, Option<Word>) {
159        (
160            self.id,
161            self.partial_vault,
162            self.partial_storage,
163            self.code,
164            self.nonce,
165            self.seed,
166        )
167    }
168}
169
170impl From<&Account> for PartialAccount {
171    /// Constructs a [`PartialAccount`] from the provided account.
172    ///
173    /// The behavior is different whether the [`Account::is_new`] or not:
174    /// - For new accounts, the storage is tracked in full. This is because transactions that create
175    ///   accounts need the full state.
176    /// - For existing accounts, the storage is tracked minimally, i.e. the minimal necessary data
177    ///   is included.
178    ///
179    /// Because new accounts always have empty vaults, in both cases, the asset vault is a minimal
180    /// representation.
181    ///
182    /// For precise control over how an account is converted to a partial account, use
183    /// [`PartialAccount::new`].
184    fn from(account: &Account) -> Self {
185        let partial_storage = if account.is_new() {
186            // This is somewhat expensive, but it allows us to do this conversion from &Account and
187            // it penalizes only the rare case (new accounts).
188            PartialStorage::new_full(account.storage.clone())
189        } else {
190            PartialStorage::new_minimal(account.storage())
191        };
192
193        Self::new(
194            account.id(),
195            account.nonce(),
196            account.code().clone(),
197            partial_storage,
198            PartialVault::new_minimal(account.vault()),
199            account.seed(),
200        )
201        .expect("account should ensure that seed is valid for account")
202    }
203}
204
205impl Serializable for PartialAccount {
206    fn write_into<W: miden_core::utils::ByteWriter>(&self, target: &mut W) {
207        target.write(self.id);
208        target.write(self.nonce);
209        target.write(&self.code);
210        target.write(&self.partial_storage);
211        target.write(&self.partial_vault);
212        target.write(self.seed);
213    }
214}
215
216impl Deserializable for PartialAccount {
217    fn read_from<R: miden_core::utils::ByteReader>(
218        source: &mut R,
219    ) -> Result<Self, miden_processor::DeserializationError> {
220        let account_id = source.read()?;
221        let nonce = source.read()?;
222        let account_code = source.read()?;
223        let partial_storage = source.read()?;
224        let partial_vault = source.read()?;
225        let seed: Option<Word> = source.read()?;
226
227        PartialAccount::new(account_id, nonce, account_code, partial_storage, partial_vault, seed)
228            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
229    }
230}