Skip to main content

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 [`AccountHeader`] of this account.
130    pub fn to_header(&self) -> AccountHeader {
131        AccountHeader::from(self)
132    }
133
134    /// Returns the commitment of this account.
135    ///
136    /// See [`AccountHeader::to_commitment`] for details on how it is computed.
137    pub fn to_commitment(&self) -> Word {
138        AccountHeader::from(self).to_commitment()
139    }
140
141    /// Returns the commitment of this account as used for the initial account state commitment in
142    /// transaction proofs.
143    ///
144    /// For existing accounts, this is exactly the same as [Account::to_commitment], however, for
145    /// new accounts this value is set to [`Word::empty`]. This is because when a transaction is
146    /// executed against a new account, public input for the initial account state is set to
147    /// [`Word::empty`] to distinguish new accounts from existing accounts. The actual
148    /// commitment of the initial account state (and the initial state itself), are provided to
149    /// the VM via the advice provider.
150    pub fn initial_commitment(&self) -> Word {
151        if self.is_new() {
152            Word::empty()
153        } else {
154            self.to_commitment()
155        }
156    }
157
158    /// Consumes self and returns the underlying parts of the partial account.
159    pub fn into_parts(
160        self,
161    ) -> (AccountId, PartialVault, PartialStorage, AccountCode, Felt, Option<Word>) {
162        (
163            self.id,
164            self.partial_vault,
165            self.partial_storage,
166            self.code,
167            self.nonce,
168            self.seed,
169        )
170    }
171}
172
173impl From<&Account> for PartialAccount {
174    /// Constructs a [`PartialAccount`] from the provided account.
175    ///
176    /// The behavior is different whether the [`Account::is_new`] or not:
177    /// - For new accounts, the storage is tracked in full. This is because transactions that create
178    ///   accounts need the full state.
179    /// - For existing accounts, the storage is tracked minimally, i.e. the minimal necessary data
180    ///   is included.
181    ///
182    /// Because new accounts always have empty vaults, in both cases, the asset vault is a minimal
183    /// representation.
184    ///
185    /// For precise control over how an account is converted to a partial account, use
186    /// [`PartialAccount::new`].
187    fn from(account: &Account) -> Self {
188        let partial_storage = if account.is_new() {
189            // This is somewhat expensive, but it allows us to do this conversion from &Account and
190            // it penalizes only the rare case (new accounts).
191            PartialStorage::new_full(account.storage.clone())
192        } else {
193            PartialStorage::new_minimal(account.storage())
194        };
195
196        Self::new(
197            account.id(),
198            account.nonce(),
199            account.code().clone(),
200            partial_storage,
201            PartialVault::new_minimal(account.vault()),
202            account.seed(),
203        )
204        .expect("account should ensure that seed is valid for account")
205    }
206}
207
208impl SequentialCommit for PartialAccount {
209    type Commitment = Word;
210
211    fn to_elements(&self) -> Vec<Felt> {
212        AccountHeader::from(self).to_elements()
213    }
214
215    fn to_commitment(&self) -> Self::Commitment {
216        AccountHeader::from(self).to_commitment()
217    }
218}
219// SERIALIZATION
220// ================================================================================================
221
222impl Serializable for PartialAccount {
223    fn write_into<W: ByteWriter>(&self, target: &mut W) {
224        target.write(self.id);
225        target.write(self.nonce);
226        target.write(&self.code);
227        target.write(&self.partial_storage);
228        target.write(&self.partial_vault);
229        target.write(self.seed);
230    }
231}
232
233impl Deserializable for PartialAccount {
234    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
235        let account_id = source.read()?;
236        let nonce = source.read()?;
237        let account_code = source.read()?;
238        let partial_storage = source.read()?;
239        let partial_vault = source.read()?;
240        let seed: Option<Word> = source.read()?;
241
242        PartialAccount::new(account_id, nonce, account_code, partial_storage, partial_vault, seed)
243            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
244    }
245}