Skip to main content

miden_protocol/account/
header.rs

1use alloc::vec::Vec;
2
3use super::{Account, AccountId, Felt, PartialAccount, ZERO, hash_account};
4use crate::errors::AccountError;
5use crate::transaction::memory::{
6    ACCT_CODE_COMMITMENT_OFFSET,
7    ACCT_DATA_MEM_SIZE,
8    ACCT_ID_AND_NONCE_OFFSET,
9    ACCT_ID_PREFIX_IDX,
10    ACCT_ID_SUFFIX_IDX,
11    ACCT_NONCE_IDX,
12    ACCT_STORAGE_COMMITMENT_OFFSET,
13    ACCT_VAULT_ROOT_OFFSET,
14    MemoryOffset,
15};
16use crate::utils::serde::{Deserializable, Serializable};
17use crate::{WORD_SIZE, Word, WordError};
18
19// ACCOUNT HEADER
20// ================================================================================================
21
22/// A header of an account which contains information that succinctly describes the state of the
23/// components of the account.
24///
25/// The [AccountHeader] is composed of:
26/// - id: the account ID ([`AccountId`]) of the account.
27/// - nonce: the nonce of the account.
28/// - vault_root: a commitment to the account's vault ([super::AssetVault]).
29/// - storage_commitment: a commitment to the account's storage ([super::AccountStorage]).
30/// - code_commitment: a commitment to the account's code ([super::AccountCode]).
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct AccountHeader {
33    id: AccountId,
34    nonce: Felt,
35    vault_root: Word,
36    storage_commitment: Word,
37    code_commitment: Word,
38}
39
40impl AccountHeader {
41    // CONSTRUCTORS
42    // --------------------------------------------------------------------------------------------
43    /// Creates a new [AccountHeader].
44    pub fn new(
45        id: AccountId,
46        nonce: Felt,
47        vault_root: Word,
48        storage_commitment: Word,
49        code_commitment: Word,
50    ) -> Self {
51        Self {
52            id,
53            nonce,
54            vault_root,
55            storage_commitment,
56            code_commitment,
57        }
58    }
59
60    /// Parses the account header data returned by the VM into individual account component
61    /// commitments. Returns a tuple of account ID, vault root, storage commitment, code
62    /// commitment, and nonce.
63    pub(crate) fn try_from_elements(elements: &[Felt]) -> Result<AccountHeader, AccountError> {
64        if elements.len() != ACCT_DATA_MEM_SIZE {
65            return Err(AccountError::HeaderDataIncorrectLength {
66                actual: elements.len(),
67                expected: ACCT_DATA_MEM_SIZE,
68            });
69        }
70
71        let id = AccountId::try_from([
72            elements[ACCT_ID_AND_NONCE_OFFSET as usize + ACCT_ID_PREFIX_IDX],
73            elements[ACCT_ID_AND_NONCE_OFFSET as usize + ACCT_ID_SUFFIX_IDX],
74        ])
75        .map_err(AccountError::FinalAccountHeaderIdParsingFailed)?;
76        let nonce = elements[ACCT_ID_AND_NONCE_OFFSET as usize + ACCT_NONCE_IDX];
77        let vault_root = parse_word(elements, ACCT_VAULT_ROOT_OFFSET)
78            .expect("we should have sliced off exactly 4 bytes");
79        let storage_commitment = parse_word(elements, ACCT_STORAGE_COMMITMENT_OFFSET)
80            .expect("we should have sliced off exactly 4 bytes");
81        let code_commitment = parse_word(elements, ACCT_CODE_COMMITMENT_OFFSET)
82            .expect("we should have sliced off exactly 4 bytes");
83
84        Ok(AccountHeader::new(id, nonce, vault_root, storage_commitment, code_commitment))
85    }
86
87    // PUBLIC ACCESSORS
88    // --------------------------------------------------------------------------------------------
89
90    /// Returns the commitment of this account.
91    ///
92    /// The commitment of an account is computed as hash(id, nonce, vault_root, storage_commitment,
93    /// code_commitment). Computing the account commitment requires 2 permutations of the hash
94    /// function.
95    pub fn commitment(&self) -> Word {
96        hash_account(
97            self.id,
98            self.nonce,
99            self.vault_root,
100            self.storage_commitment,
101            self.code_commitment,
102        )
103    }
104
105    /// Returns the id of this account.
106    pub fn id(&self) -> AccountId {
107        self.id
108    }
109
110    /// Returns the nonce of this account.
111    pub fn nonce(&self) -> Felt {
112        self.nonce
113    }
114
115    /// Returns the vault root of this account.
116    pub fn vault_root(&self) -> Word {
117        self.vault_root
118    }
119
120    /// Returns the storage commitment of this account.
121    pub fn storage_commitment(&self) -> Word {
122        self.storage_commitment
123    }
124
125    /// Returns the code commitment of this account.
126    pub fn code_commitment(&self) -> Word {
127        self.code_commitment
128    }
129
130    /// Converts the account header into a vector of field elements.
131    ///
132    /// This is done by first converting the account header data into an array of Words as follows:
133    /// ```text
134    /// [
135    ///     [account_id_suffix, account_id_prefix, 0, account_nonce]
136    ///     [VAULT_ROOT]
137    ///     [STORAGE_COMMITMENT]
138    ///     [CODE_COMMITMENT]
139    /// ]
140    /// ```
141    /// And then concatenating the resulting elements into a single vector.
142    pub fn as_elements(&self) -> Vec<Felt> {
143        [
144            &[self.id.suffix(), self.id.prefix().as_felt(), ZERO, self.nonce],
145            self.vault_root.as_elements(),
146            self.storage_commitment.as_elements(),
147            self.code_commitment.as_elements(),
148        ]
149        .concat()
150    }
151}
152
153impl From<PartialAccount> for AccountHeader {
154    fn from(account: PartialAccount) -> Self {
155        (&account).into()
156    }
157}
158
159impl From<&PartialAccount> for AccountHeader {
160    fn from(account: &PartialAccount) -> Self {
161        Self {
162            id: account.id(),
163            nonce: account.nonce(),
164            vault_root: account.vault().root(),
165            storage_commitment: account.storage().commitment(),
166            code_commitment: account.code().commitment(),
167        }
168    }
169}
170
171impl From<Account> for AccountHeader {
172    fn from(account: Account) -> Self {
173        (&account).into()
174    }
175}
176
177impl From<&Account> for AccountHeader {
178    fn from(account: &Account) -> Self {
179        Self {
180            id: account.id(),
181            nonce: account.nonce(),
182            vault_root: account.vault().root(),
183            storage_commitment: account.storage().to_commitment(),
184            code_commitment: account.code().commitment(),
185        }
186    }
187}
188
189impl Serializable for AccountHeader {
190    fn write_into<W: miden_core::utils::ByteWriter>(&self, target: &mut W) {
191        self.id.write_into(target);
192        self.nonce.write_into(target);
193        self.vault_root.write_into(target);
194        self.storage_commitment.write_into(target);
195        self.code_commitment.write_into(target);
196    }
197}
198
199impl Deserializable for AccountHeader {
200    fn read_from<R: miden_core::utils::ByteReader>(
201        source: &mut R,
202    ) -> Result<Self, miden_processor::DeserializationError> {
203        let id = AccountId::read_from(source)?;
204        let nonce = Felt::read_from(source)?;
205        let vault_root = Word::read_from(source)?;
206        let storage_commitment = Word::read_from(source)?;
207        let code_commitment = Word::read_from(source)?;
208
209        Ok(AccountHeader {
210            id,
211            nonce,
212            vault_root,
213            storage_commitment,
214            code_commitment,
215        })
216    }
217}
218
219// HELPER FUNCTIONS
220// ================================================================================================
221
222/// Creates a new `Word` instance from the slice of `Felt`s using provided offset.
223fn parse_word(data: &[Felt], offset: MemoryOffset) -> Result<Word, WordError> {
224    Word::try_from(&data[offset as usize..offset as usize + WORD_SIZE])
225}
226
227// TESTS
228// ================================================================================================
229
230#[cfg(test)]
231mod tests {
232    use miden_core::Felt;
233    use miden_core::utils::{Deserializable, Serializable};
234
235    use super::AccountHeader;
236    use crate::Word;
237    use crate::account::StorageSlotContent;
238    use crate::account::tests::build_account;
239    use crate::asset::FungibleAsset;
240
241    #[test]
242    fn test_serde_account_storage() {
243        let init_nonce = Felt::new(1);
244        let asset_0 = FungibleAsset::mock(99);
245        let word = Word::from([1, 2, 3, 4u32]);
246        let storage_slot = StorageSlotContent::Value(word);
247        let account = build_account(vec![asset_0], init_nonce, vec![storage_slot]);
248
249        let account_header: AccountHeader = account.into();
250
251        let header_bytes = account_header.to_bytes();
252        let deserialized_header = AccountHeader::read_from_bytes(&header_bytes).unwrap();
253        assert_eq!(deserialized_header, account_header);
254    }
255}