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}