Skip to main content

miden_protocol/account/
partial.rs

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