Skip to main content

miden_protocol/block/account_tree/
account_id_key.rs

1use miden_crypto::merkle::smt::LeafIndex;
2
3use super::AccountId;
4use crate::Word;
5use crate::account::AccountIdPrefix;
6use crate::crypto::merkle::smt::SMT_DEPTH;
7use crate::errors::AccountIdError;
8
9/// The account ID encoded as a key for use in AccountTree and advice maps in
10/// `TransactionAdviceInputs`.
11///
12/// Canonical word layout:
13///
14/// [0, 0, suffix, prefix]
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct AccountIdKey(AccountId);
17
18impl AccountIdKey {
19    // Indices in the word layout where the prefix and suffix are stored.
20    const KEY_SUFFIX_IDX: usize = 2;
21    const KEY_PREFIX_IDX: usize = 3;
22
23    /// Create from AccountId
24    pub fn new(id: AccountId) -> Self {
25        Self(id)
26    }
27
28    /// Returns the underlying AccountId
29    pub fn account_id(&self) -> AccountId {
30        self.0
31    }
32
33    // SMT WORD REPRESENTATION
34    //---------------------------------------------------------------------------------------------------
35
36    /// Returns `[0, 0, suffix, prefix]`
37    pub fn as_word(&self) -> Word {
38        let mut key = Word::empty();
39
40        key[Self::KEY_SUFFIX_IDX] = self.0.suffix();
41        key[Self::KEY_PREFIX_IDX] = self.0.prefix().as_felt();
42
43        key
44    }
45
46    /// Construct from SMT word representation.
47    ///
48    /// Validates structure before converting.
49    pub fn try_from_word(word: Word) -> Result<AccountId, AccountIdError> {
50        AccountId::try_from_elements(word[Self::KEY_SUFFIX_IDX], word[Self::KEY_PREFIX_IDX])
51    }
52
53    /// Returns the SMT key for an account ID prefix, with only the prefix field set.
54    pub(crate) fn id_prefix_to_smt_key(prefix: AccountIdPrefix) -> Word {
55        let mut key = Word::empty();
56        key[Self::KEY_PREFIX_IDX] = prefix.as_felt();
57        key
58    }
59
60    // LEAF INDEX
61    //---------------------------------------------------------------------------------------------------
62
63    /// Converts to SMT leaf index used by AccountTree
64    pub fn to_leaf_index(&self) -> LeafIndex<SMT_DEPTH> {
65        LeafIndex::from(self.as_word())
66    }
67}
68
69impl From<AccountId> for AccountIdKey {
70    fn from(id: AccountId) -> Self {
71        Self(id)
72    }
73}
74
75// TESTS
76//---------------------------------------------------------------------------------------------------
77
78#[cfg(test)]
79mod tests {
80
81    use miden_core::ZERO;
82
83    use super::{AccountId, *};
84    use crate::account::AccountType;
85    #[test]
86    fn test_as_word_layout() {
87        let id = AccountId::builder()
88            .account_type(AccountType::Private)
89            .build_with_seed([1u8; 32]);
90        let key = AccountIdKey::from(id);
91        let word = key.as_word();
92
93        assert_eq!(word[0], ZERO);
94        assert_eq!(word[1], ZERO);
95        assert_eq!(word[2], id.suffix());
96        assert_eq!(word[3], id.prefix().as_felt());
97    }
98
99    #[test]
100    fn test_roundtrip_word_conversion() {
101        let id = AccountId::builder()
102            .account_type(AccountType::Private)
103            .build_with_seed([1u8; 32]);
104
105        let key = AccountIdKey::from(id);
106        let recovered =
107            AccountIdKey::try_from_word(key.as_word()).expect("valid account id conversion");
108
109        assert_eq!(id, recovered);
110    }
111
112    #[test]
113    fn test_leaf_index_consistency() {
114        let id = AccountId::builder()
115            .account_type(AccountType::Private)
116            .build_with_seed([1u8; 32]);
117        let key = AccountIdKey::from(id);
118
119        let idx1 = key.to_leaf_index();
120        let idx2 = key.to_leaf_index();
121
122        assert_eq!(idx1, idx2);
123    }
124
125    #[test]
126    fn test_from_conversion() {
127        let id = AccountId::builder()
128            .account_type(AccountType::Private)
129            .build_with_seed([1u8; 32]);
130        let key: AccountIdKey = id.into();
131
132        assert_eq!(key.account_id(), id);
133    }
134
135    #[test]
136    fn test_multiple_roundtrips() {
137        for _ in 0..100 {
138            let id = AccountId::builder()
139                .account_type(AccountType::Private)
140                .build_with_seed([1u8; 32]);
141            let key = AccountIdKey::from(id);
142
143            let recovered =
144                AccountIdKey::try_from_word(key.as_word()).expect("valid account id conversion");
145
146            assert_eq!(id, recovered);
147        }
148    }
149}