Skip to main content

miden_protocol/account/code/
upgrade.rs

1use alloc::vec::Vec;
2
3use crate::account::AccountCode;
4use crate::crypto::utils::{bytes_to_elements_with_padding, padded_elements_to_bytes};
5use crate::utils::serde::{
6    ByteReader,
7    ByteWriter,
8    Deserializable,
9    DeserializationError,
10    Serializable,
11};
12use crate::{Felt, Hasher, WORD_SIZE, Word};
13
14// ACCOUNT CODE UPGRADE
15// ================================================================================================
16
17/// An upgrade of the [`AccountCode`] of an existing account.
18///
19/// # Warning
20///
21/// An upgrade does not change the account's storage, so the new code must use the same storage
22/// layout as the current code. Otherwise, the account can become unusable. The kernel does not
23/// enforce this, so the account authority must make sure that the layout stays the same.
24///
25/// The kernel only learns the commitment of the new code, so the host must obtain the new code when
26/// the kernel initializes the upgrade. It looks for the code, encoded by
27/// [`AccountCodeUpgrade::to_elements`], in the advice map under
28/// [`AccountCodeUpgrade::advice_map_key`]. Local transactions provide it e.g. via
29/// [`TransactionArgs::with_account_code_upgrade`](crate::transaction::TransactionArgs::with_account_code_upgrade),
30/// while a note script can insert it during execution.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct AccountCodeUpgrade {
33    code: AccountCode,
34}
35
36impl AccountCodeUpgrade {
37    // CONSTANTS
38    // --------------------------------------------------------------------------------------------
39
40    /// Domain separator for [`AccountCodeUpgrade::advice_map_key`].
41    ///
42    /// It keeps the key distinct from the keys of other advice map entries that hash the same
43    /// elements without a domain, such as note storage or note attachment content. See
44    /// [`AccountDelta`](crate::account::AccountDelta) for where the value is allocated from.
45    const ADVICE_MAP_KEY_DOMAIN: Felt = Felt::new_unchecked(0x02_0002);
46
47    // CONSTRUCTORS
48    // --------------------------------------------------------------------------------------------
49
50    /// Returns a new [`AccountCodeUpgrade`] that upgrades the account to `code`.
51    pub fn new(code: AccountCode) -> Self {
52        Self { code }
53    }
54
55    // PUBLIC ACCESSORS
56    // --------------------------------------------------------------------------------------------
57
58    /// Returns the advice map key under which the upgrade to the code with `new_code_commitment` is
59    /// provided.
60    ///
61    /// The code commitment itself maps to the procedure roots of the code, so the upgrade is keyed
62    /// by a domain-separated hash of the commitment instead.
63    pub fn advice_map_key(new_code_commitment: Word) -> Word {
64        Hasher::hash_elements_in_domain(
65            new_code_commitment.as_elements(),
66            Self::ADVICE_MAP_KEY_DOMAIN,
67        )
68    }
69
70    /// Returns a reference to the new account code.
71    pub fn code(&self) -> &AccountCode {
72        &self.code
73    }
74
75    /// Returns the commitment of the new account code.
76    pub fn commitment(&self) -> Word {
77        self.code.commitment()
78    }
79
80    /// Consumes self and returns the new account code.
81    pub fn into_code(self) -> AccountCode {
82        self.code
83    }
84
85    /// Returns the encoding of the new code as field elements.
86    ///
87    /// The serialized code is packed into field elements, 7 bytes per element, with a non-zero
88    /// padding marker in the last packed element, and then padded with zero elements to a whole
89    /// number of words, so that it can also be carried as words, e.g. in a note attachment.
90    pub fn to_elements(&self) -> Vec<Felt> {
91        let mut elements = bytes_to_elements_with_padding(&self.code.to_bytes());
92        elements.resize(elements.len().next_multiple_of(WORD_SIZE), Felt::ZERO);
93        elements
94    }
95
96    /// Returns the advice map entry that provides this upgrade to a transaction.
97    pub fn to_advice_map_entry(&self) -> (Word, Vec<Felt>) {
98        (Self::advice_map_key(self.commitment()), self.to_elements())
99    }
100
101    /// Decodes an [`AccountCodeUpgrade`] from the elements produced by
102    /// [`AccountCodeUpgrade::to_elements`].
103    ///
104    /// # Errors
105    ///
106    /// Returns an error if `elements` do not encode valid account code.
107    pub fn try_from_elements(elements: &[Felt]) -> Result<Self, DeserializationError> {
108        // The last packed element holds a non-zero padding marker, so any trailing zero elements
109        // are word padding.
110        let packed_len = elements
111            .iter()
112            .rposition(|element| *element != Felt::ZERO)
113            .map_or(0, |last_packed_idx| last_packed_idx + 1);
114        let bytes = padded_elements_to_bytes(&elements[..packed_len]).ok_or_else(|| {
115            DeserializationError::InvalidValue("encoded account code is not padded".into())
116        })?;
117
118        AccountCode::read_from_bytes(&bytes).map(Self::new)
119    }
120}
121
122impl Serializable for AccountCodeUpgrade {
123    fn write_into<W: ByteWriter>(&self, target: &mut W) {
124        self.code.write_into(target);
125    }
126
127    fn get_size_hint(&self) -> usize {
128        self.code.get_size_hint()
129    }
130}
131
132impl Deserializable for AccountCodeUpgrade {
133    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
134        AccountCode::read_from(source).map(Self::new)
135    }
136}
137
138// TESTS
139// ================================================================================================
140
141#[cfg(test)]
142mod tests {
143    use super::AccountCodeUpgrade;
144    use crate::WORD_SIZE;
145    use crate::account::AccountCode;
146
147    #[test]
148    fn advice_map_entry_roundtrips() -> anyhow::Result<()> {
149        let upgrade = AccountCodeUpgrade::new(AccountCode::mock());
150
151        let (key, elements) = upgrade.to_advice_map_entry();
152
153        assert_eq!(key, AccountCodeUpgrade::advice_map_key(upgrade.commitment()));
154        assert_eq!(elements.len() % WORD_SIZE, 0);
155        assert_eq!(AccountCodeUpgrade::try_from_elements(&elements)?, upgrade);
156
157        Ok(())
158    }
159}