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
161impl Deserializable for NoteTag {
162 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
163 let tag = u32::read_from(source)?;
164 Ok(Self::new(tag))
165 }
166}
167
168#[cfg(test)]
172mod tests {
173
174 use super::NoteTag;
175 use crate::account::{AccountId, AccountStorageMode};
176 use crate::testing::account_id::{
177 ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET,
178 ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET,
179 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
180 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
181 ACCOUNT_ID_PRIVATE_SENDER,
182 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
183 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
184 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
185 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3,
186 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
187 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
188 ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE,
189 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
190 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
191 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2,
192 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE,
193 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2,
194 ACCOUNT_ID_SENDER,
195 AccountIdBuilder,
196 };
197
198 #[test]
199 fn from_account_id() {
200 let private_accounts = [
201 AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(),
202 AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap(),
203 AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap(),
204 AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap(),
205 AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap(),
206 AccountIdBuilder::new()
207 .storage_mode(AccountStorageMode::Private)
208 .build_with_seed([2; 32]),
209 ];
210 let public_accounts = [
211 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(),
212 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2).unwrap(),
213 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE).unwrap(),
214 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2)
215 .unwrap(),
216 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(),
217 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).unwrap(),
218 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2).unwrap(),
219 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3).unwrap(),
220 AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET).unwrap(),
221 AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1).unwrap(),
222 AccountIdBuilder::new()
223 .storage_mode(AccountStorageMode::Public)
224 .build_with_seed([3; 32]),
225 ];
226 let network_accounts = [
227 AccountId::try_from(ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE).unwrap(),
228 AccountId::try_from(ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET).unwrap(),
229 AccountId::try_from(ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET).unwrap(),
230 AccountIdBuilder::new()
231 .storage_mode(AccountStorageMode::Network)
232 .build_with_seed([4; 32]),
233 ];
234
235 for account_id in private_accounts
236 .iter()
237 .chain(public_accounts.iter())
238 .chain(network_accounts.iter())
239 {
240 let tag = NoteTag::with_account_target(*account_id);
241 assert_eq!(tag.as_u32() << 16, 0, "16 least significant bits should be zero");
242 let expected = ((account_id.prefix().as_u64() >> 32) as u32) >> 16;
243 let actual = tag.as_u32() >> 16;
244
245 assert_eq!(actual, expected, "14 most significant bits should match");
246 }
247 }
248
249 #[test]
250 fn from_custom_account_target() -> anyhow::Result<()> {
251 let account_id = AccountId::try_from(ACCOUNT_ID_SENDER)?;
252
253 let tag = NoteTag::with_custom_account_target(
254 account_id,
255 NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH,
256 )?;
257
258 assert_eq!(
259 (account_id.prefix().as_u64() >> 32) as u32,
260 tag.as_u32(),
261 "32 most significant bits should match"
262 );
263
264 Ok(())
265 }
266}