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