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