miden_protocol/note/
note_tag.rs1use core::fmt;
2
3use miden_crypto::Felt;
4
5use super::{
6 AccountId,
7 ByteReader,
8 ByteWriter,
9 Deserializable,
10 DeserializationError,
11 NoteError,
12 Serializable,
13};
14#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
60pub struct NoteTag(u32);
61
62impl NoteTag {
63 pub const DEFAULT_ACCOUNT_TARGET_TAG_LENGTH: u8 = 14;
68 pub const MAX_ACCOUNT_TARGET_TAG_LENGTH: u8 = 32;
70
71 pub const fn new(tag: u32) -> Self {
76 Self(tag)
77 }
78
79 pub fn with_account_target(account_id: AccountId) -> Self {
84 Self::with_custom_account_target(account_id, Self::DEFAULT_ACCOUNT_TARGET_TAG_LENGTH)
85 .expect("default account target tag length must be valid")
86 }
87
88 pub fn with_custom_account_target(
97 account_id: AccountId,
98 tag_len: u8,
99 ) -> Result<Self, NoteError> {
100 if tag_len > Self::MAX_ACCOUNT_TARGET_TAG_LENGTH {
101 return Err(NoteError::NoteTagLengthTooLarge(tag_len));
102 }
103
104 let prefix = account_id.prefix().as_u64();
105 let high_bits = (prefix >> 32) as u32;
107 let mask = u32::MAX.checked_shl(u32::BITS - tag_len as u32).unwrap_or(0);
109 let tag = high_bits & mask;
110 Ok(Self(tag))
111 }
112
113 pub fn as_u32(&self) -> u32 {
118 self.0
119 }
120}
121
122impl fmt::Display for NoteTag {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 write!(f, "{}", self.as_u32())
125 }
126}
127
128impl From<u32> for NoteTag {
132 fn from(tag: u32) -> Self {
133 Self::new(tag)
134 }
135}
136
137impl From<NoteTag> for u32 {
141 fn from(tag: NoteTag) -> Self {
142 tag.as_u32()
143 }
144}
145
146impl From<NoteTag> for Felt {
147 fn from(tag: NoteTag) -> Self {
148 Felt::from(tag.as_u32())
149 }
150}
151
152impl Serializable for NoteTag {
156 fn write_into<W: ByteWriter>(&self, target: &mut W) {
157 self.as_u32().write_into(target);
158 }
159
160 fn get_size_hint(&self) -> usize {
161 core::mem::size_of::<u32>()
162 }
163}
164
165impl Deserializable for NoteTag {
166 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
167 let tag = u32::read_from(source)?;
168 Ok(Self::new(tag))
169 }
170}
171
172#[cfg(test)]
176mod tests {
177
178 use super::NoteTag;
179 use crate::account::{AccountId, AccountStorageMode};
180 use crate::testing::account_id::{
181 ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET,
182 ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET,
183 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
184 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
185 ACCOUNT_ID_PRIVATE_SENDER,
186 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
187 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
188 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
189 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3,
190 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
191 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
192 ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE,
193 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
194 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
195 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2,
196 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE,
197 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2,
198 ACCOUNT_ID_SENDER,
199 AccountIdBuilder,
200 };
201
202 #[test]
203 fn from_account_id() {
204 let private_accounts = [
205 AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(),
206 AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap(),
207 AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap(),
208 AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap(),
209 AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap(),
210 AccountIdBuilder::new()
211 .storage_mode(AccountStorageMode::Private)
212 .build_with_seed([2; 32]),
213 ];
214 let public_accounts = [
215 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(),
216 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2).unwrap(),
217 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE).unwrap(),
218 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2)
219 .unwrap(),
220 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(),
221 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).unwrap(),
222 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2).unwrap(),
223 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3).unwrap(),
224 AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET).unwrap(),
225 AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1).unwrap(),
226 AccountIdBuilder::new()
227 .storage_mode(AccountStorageMode::Public)
228 .build_with_seed([3; 32]),
229 ];
230 let network_accounts = [
231 AccountId::try_from(ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE).unwrap(),
232 AccountId::try_from(ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET).unwrap(),
233 AccountId::try_from(ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET).unwrap(),
234 AccountIdBuilder::new()
235 .storage_mode(AccountStorageMode::Network)
236 .build_with_seed([4; 32]),
237 ];
238
239 for account_id in private_accounts
240 .iter()
241 .chain(public_accounts.iter())
242 .chain(network_accounts.iter())
243 {
244 let tag = NoteTag::with_account_target(*account_id);
245 assert_eq!(tag.as_u32() << 16, 0, "16 least significant bits should be zero");
246 let expected = ((account_id.prefix().as_u64() >> 32) as u32) >> 16;
247 let actual = tag.as_u32() >> 16;
248
249 assert_eq!(actual, expected, "14 most significant bits should match");
250 }
251 }
252
253 #[test]
254 fn from_custom_account_target() -> anyhow::Result<()> {
255 let account_id = AccountId::try_from(ACCOUNT_ID_SENDER)?;
256
257 let tag = NoteTag::with_custom_account_target(
258 account_id,
259 NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH,
260 )?;
261
262 assert_eq!(
263 (account_id.prefix().as_u64() >> 32) as u32,
264 tag.as_u32(),
265 "32 most significant bits should match"
266 );
267
268 Ok(())
269 }
270}