1use core::fmt;
2
3use miden_crypto::Felt;
4
5use super::{
6 AccountId, ByteReader, ByteWriter, Deserializable, DeserializationError, NoteError, NoteType,
7 Serializable,
8};
9use crate::account::AccountStorageMode;
10
11const NETWORK_EXECUTION: u8 = 0;
14const LOCAL_EXECUTION: u8 = 1;
15
16const NETWORK_ACCOUNT: u32 = 0;
18const NETWORK_PUBLIC_USECASE: u32 = 0x4000_0000;
20const LOCAL_PUBLIC_ANY: u32 = 0x8000_0000;
22const LOCAL_ANY: u32 = 0xc000_0000;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[repr(u8)]
36pub enum NoteExecutionMode {
37 Network = NETWORK_EXECUTION,
38 Local = LOCAL_EXECUTION,
39}
40
41#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
83pub enum NoteTag {
84 NetworkAccount(u32),
87 NetworkUseCase(u16, u16),
90 LocalPublicAny(u32),
98 LocalAny(u32),
106}
107
108impl NoteTag {
109 pub(crate) const MAX_USE_CASE_ID_EXPONENT: u8 = 14;
115
116 pub fn from_account_id(account_id: AccountId) -> Self {
132 match account_id.storage_mode() {
133 AccountStorageMode::Network => {
134 let prefix_id: u64 = account_id.prefix().into();
135
136 let high_bits = prefix_id >> 34;
139
140 Self::NetworkAccount(high_bits as u32)
145 },
146 AccountStorageMode::Private | AccountStorageMode::Public => {
147 let prefix_id: u64 = account_id.prefix().into();
148
149 let high_bits = prefix_id >> 34;
152
153 let high_bits = high_bits as u32;
156
157 let high_bits = high_bits & 0xffff0000;
161
162 Self::LocalAny(LOCAL_ANY | high_bits)
164 },
165 }
166 }
167
168 pub fn for_public_use_case(
180 use_case_id: u16,
181 payload: u16,
182 execution: NoteExecutionMode,
183 ) -> Result<Self, NoteError> {
184 if (use_case_id >> 14) != 0 {
185 return Err(NoteError::NoteTagUseCaseTooLarge(use_case_id));
186 }
187
188 match execution {
189 NoteExecutionMode::Network => {
190 let use_case_bits = (NETWORK_PUBLIC_USECASE >> 16) as u16 | use_case_id;
191 Ok(Self::NetworkUseCase(use_case_bits, payload))
192 },
193 NoteExecutionMode::Local => {
194 let tag_u32 = LOCAL_PUBLIC_ANY | ((use_case_id as u32) << 16) | (payload as u32);
195 Ok(Self::LocalPublicAny(tag_u32))
196 },
197 }
198 }
199
200 pub fn for_local_use_case(use_case_id: u16, payload: u16) -> Result<Self, NoteError> {
211 if (use_case_id >> NoteTag::MAX_USE_CASE_ID_EXPONENT) != 0 {
212 return Err(NoteError::NoteTagUseCaseTooLarge(use_case_id));
213 }
214
215 let use_case_bits = (use_case_id as u32) << 16;
216 let payload_bits = payload as u32;
217
218 Ok(Self::LocalAny(LOCAL_ANY | use_case_bits | payload_bits))
219 }
220
221 pub fn is_single_target(&self) -> bool {
227 matches!(self, NoteTag::NetworkAccount(_))
228 }
229
230 pub fn execution_mode(&self) -> NoteExecutionMode {
235 match self {
236 NoteTag::NetworkAccount(_) | NoteTag::NetworkUseCase(..) => NoteExecutionMode::Network,
237 NoteTag::LocalAny(_) | NoteTag::LocalPublicAny(..) => NoteExecutionMode::Local,
238 }
239 }
240
241 pub fn as_u32(&self) -> u32 {
243 const LOW_BITS_MASK: u32 = 0x3fff_ffff;
248 match self {
249 NoteTag::NetworkAccount(tag) => *tag & LOW_BITS_MASK,
250 NoteTag::NetworkUseCase(use_case_bits, payload_bits) => {
251 ((*use_case_bits as u32) << 16 | *payload_bits as u32) & LOW_BITS_MASK
252 | NETWORK_PUBLIC_USECASE
253 },
254 NoteTag::LocalPublicAny(tag) => (*tag & LOW_BITS_MASK) | LOCAL_PUBLIC_ANY,
255 NoteTag::LocalAny(tag) => (*tag & LOW_BITS_MASK) | LOCAL_ANY,
256 }
257 }
258
259 pub fn validate(&self, note_type: NoteType) -> Result<Self, NoteError> {
265 if self.execution_mode() == NoteExecutionMode::Network && note_type != NoteType::Public {
266 return Err(NoteError::NetworkExecutionRequiresPublicNote(note_type));
267 }
268
269 if self.requires_public_note() && note_type != NoteType::Public {
271 Err(NoteError::PublicNoteRequired(note_type))
272 } else {
273 Ok(*self)
274 }
275 }
276
277 fn requires_public_note(&self) -> bool {
279 matches!(
280 self,
281 NoteTag::NetworkAccount(_) | NoteTag::NetworkUseCase(_, _) | NoteTag::LocalPublicAny(_)
282 )
283 }
284}
285
286impl fmt::Display for NoteTag {
287 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288 write!(f, "{}", self.as_u32())
289 }
290}
291
292impl From<u32> for NoteTag {
296 fn from(value: u32) -> Self {
297 match value & 0xc0000000 {
299 NETWORK_ACCOUNT => Self::NetworkAccount(value),
300 NETWORK_PUBLIC_USECASE => Self::NetworkUseCase((value >> 16) as u16, value as u16),
301 LOCAL_ANY => Self::LocalAny(value),
302 LOCAL_PUBLIC_ANY => Self::LocalPublicAny(value),
303 _ => {
304 unreachable!("Invalid value encountered: {:b}", value);
307 },
308 }
309 }
310}
311
312impl From<NoteTag> for u32 {
316 fn from(value: NoteTag) -> Self {
317 value.as_u32()
318 }
319}
320
321impl From<NoteTag> for Felt {
322 fn from(value: NoteTag) -> Self {
323 Felt::from(value.as_u32())
324 }
325}
326
327impl Serializable for NoteTag {
331 fn write_into<W: ByteWriter>(&self, target: &mut W) {
332 self.as_u32().write_into(target);
333 }
334}
335
336impl Deserializable for NoteTag {
337 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
338 let tag = u32::read_from(source)?;
339 Ok(Self::from(tag))
340 }
341}
342
343#[cfg(test)]
347mod tests {
348
349 use assert_matches::assert_matches;
350
351 use super::{NoteExecutionMode, NoteTag};
352 use crate::{
353 NoteError,
354 account::AccountId,
355 note::{
356 NoteType,
357 note_tag::{LOCAL_ANY, LOCAL_PUBLIC_ANY, NETWORK_ACCOUNT, NETWORK_PUBLIC_USECASE},
358 },
359 testing::account_id::{
360 ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET, ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET,
361 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
362 ACCOUNT_ID_PRIVATE_SENDER, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
363 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
364 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3, ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
365 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
366 ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE,
367 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
368 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
369 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2,
370 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE,
371 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2, ACCOUNT_ID_SENDER,
372 },
373 };
374
375 #[test]
376 fn from_account_id() {
377 let private_accounts = [
378 AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(),
379 AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap(),
380 AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap(),
381 AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap(),
382 AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap(),
383 ];
384 let public_accounts = [
385 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(),
386 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2).unwrap(),
387 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE).unwrap(),
388 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2)
389 .unwrap(),
390 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(),
391 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).unwrap(),
392 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2).unwrap(),
393 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3).unwrap(),
394 AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET).unwrap(),
395 AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1).unwrap(),
396 ];
397 let network_accounts = [
398 AccountId::try_from(ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE).unwrap(),
399 AccountId::try_from(ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET).unwrap(),
400 AccountId::try_from(ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET).unwrap(),
401 ];
402
403 for account_id in network_accounts {
404 let tag = NoteTag::from_account_id(account_id);
405 assert!(tag.is_single_target());
406 assert_eq!(tag.execution_mode(), NoteExecutionMode::Network);
407
408 tag.validate(NoteType::Public)
409 .expect("network execution should require notes to be public");
410 assert_matches!(
411 tag.validate(NoteType::Private),
412 Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private))
413 );
414 assert_matches!(
415 tag.validate(NoteType::Encrypted),
416 Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted))
417 );
418 }
419
420 for account_id in private_accounts {
421 let tag = NoteTag::from_account_id(account_id);
422 assert!(!tag.is_single_target());
423 assert_eq!(tag.execution_mode(), NoteExecutionMode::Local);
424
425 tag.validate(NoteType::Public)
427 .expect("local execution should support public notes");
428 tag.validate(NoteType::Private)
429 .expect("local execution should support private notes");
430 tag.validate(NoteType::Encrypted)
431 .expect("local execution should support encrypted notes");
432 }
433
434 for account_id in public_accounts {
435 let tag = NoteTag::from_account_id(account_id);
436 assert!(!tag.is_single_target());
437 assert_eq!(tag.execution_mode(), NoteExecutionMode::Local);
438
439 tag.validate(NoteType::Public)
441 .expect("local execution should support public notes");
442 tag.validate(NoteType::Private)
443 .expect("local execution should support private notes");
444 tag.validate(NoteType::Encrypted)
445 .expect("local execution should support encrypted notes");
446 }
447
448 for account_id in network_accounts {
449 let tag = NoteTag::from_account_id(account_id);
450 assert!(tag.is_single_target());
451 assert_eq!(tag.execution_mode(), NoteExecutionMode::Network);
452
453 tag.validate(NoteType::Public)
455 .expect("network execution should support public notes");
456 assert_matches!(
457 tag.validate(NoteType::Private),
458 Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private))
459 );
460 assert_matches!(
461 tag.validate(NoteType::Encrypted),
462 Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted))
463 );
464 }
465 }
466
467 #[test]
468 fn from_private_account_id() {
469 const PRIVATE_ACCOUNT_INT: u128 = ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE
473 | 0x0055_0000_0000_0000_0000_0000_0000_0000;
474 let private_account_id = AccountId::try_from(PRIVATE_ACCOUNT_INT).unwrap();
475
476 let expected_private_tag = 0b11110011_00010101_00000000_00000000;
478
479 assert_eq!(NoteTag::from_account_id(private_account_id).as_u32(), expected_private_tag);
480 }
481
482 #[test]
483 fn from_public_account_id() {
484 const PUBLIC_ACCOUNT_INT: u128 = ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE
488 | 0x0055_ccaa_0000_0000_0000_0000_0000_0000;
489 let public_account_id = AccountId::try_from(PUBLIC_ACCOUNT_INT).unwrap();
490
491 let expected_public_local_tag = 0b11101010_10010101_00000000_00000000u32;
493
494 assert_eq!(NoteTag::from_account_id(public_account_id).as_u32(), expected_public_local_tag);
495 }
496
497 #[test]
498 fn from_network_account_id() {
499 const NETWORK_ACCOUNT_INT: u128 = ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE
503 | 0x00cc_77cc_0000_0000_0000_0000_0000_0000;
504 let network_account_id = AccountId::try_from(NETWORK_ACCOUNT_INT).unwrap();
505
506 let expected_network_tag = 0b00101010_10110011_00011101_11110011;
508
509 assert_eq!(NoteTag::from_account_id(network_account_id).as_u32(), expected_network_tag);
510 }
511
512 #[test]
513 fn for_public_use_case() {
514 let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionMode::Network).unwrap();
517 assert_eq!(tag.as_u32(), 0b01000000_00000000_00000000_00000000u32);
518
519 tag.validate(NoteType::Public).unwrap();
520
521 assert_matches!(
522 tag.validate(NoteType::Private).unwrap_err(),
523 NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private)
524 );
525 assert_matches!(
526 tag.validate(NoteType::Encrypted).unwrap_err(),
527 NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted)
528 );
529
530 let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionMode::Network).unwrap();
531 assert_eq!(tag.as_u32(), 0b01000000_00000001_00000000_00000000u32);
532
533 let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionMode::Network).unwrap();
534 assert_eq!(tag.as_u32(), 0b01000000_00000000_00000000_00000001u32);
535
536 let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionMode::Network).unwrap();
537 assert_eq!(tag.as_u32(), 0b01100000_00000000_00000000_00000000u32);
538
539 let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionMode::Local).unwrap();
542 assert_eq!(tag.as_u32(), 0b10000000_00000000_00000000_00000000u32);
543
544 tag.validate(NoteType::Public).unwrap();
545 assert_matches!(
546 tag.validate(NoteType::Private).unwrap_err(),
547 NoteError::PublicNoteRequired(NoteType::Private)
548 );
549 assert_matches!(
550 tag.validate(NoteType::Encrypted).unwrap_err(),
551 NoteError::PublicNoteRequired(NoteType::Encrypted)
552 );
553
554 let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionMode::Local).unwrap();
555 assert_eq!(tag.as_u32(), 0b10000000_00000000_00000000_00000001u32);
556
557 let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionMode::Local).unwrap();
558 assert_eq!(tag.as_u32(), 0b10000000_00000001_00000000_00000000u32);
559
560 let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionMode::Local).unwrap();
561 assert_eq!(tag.as_u32(), 0b10100000_00000000_00000000_00000000u32);
562
563 assert_matches!(
564 NoteTag::for_public_use_case(1 << 15, 0b0, NoteExecutionMode::Local).unwrap_err(),
565 NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 15
566 );
567 assert_matches!(
568 NoteTag::for_public_use_case(1 << 14, 0b0, NoteExecutionMode::Local).unwrap_err(),
569 NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 14
570 );
571 }
572
573 #[test]
574 fn for_private_use_case() {
575 let tag = NoteTag::for_local_use_case(0b0, 0b0).unwrap();
576 assert_eq!(
577 tag.as_u32() >> 30,
578 LOCAL_ANY >> 30,
579 "local use case prefix should be local any"
580 );
581 assert_eq!(tag.as_u32(), 0b11000000_00000000_00000000_00000000u32);
582
583 tag.validate(NoteType::Public)
584 .expect("local execution should support public notes");
585 tag.validate(NoteType::Private)
586 .expect("local execution should support private notes");
587 tag.validate(NoteType::Encrypted)
588 .expect("local execution should support encrypted notes");
589
590 let tag = NoteTag::for_local_use_case(0b0, 0b1).unwrap();
591 assert_eq!(tag.as_u32(), 0b11000000_00000000_00000000_00000001u32);
592
593 let tag = NoteTag::for_local_use_case(0b1, 0b0).unwrap();
594 assert_eq!(tag.as_u32(), 0b11000000_00000001_00000000_00000000u32);
595
596 let tag = NoteTag::for_local_use_case(1 << 13, 0b0).unwrap();
597 assert_eq!(tag.as_u32(), 0b11100000_00000000_00000000_00000000u32);
598
599 assert_matches!(
600 NoteTag::for_local_use_case(1 << 15, 0b0).unwrap_err(),
601 NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 15
602 );
603 assert_matches!(
604 NoteTag::for_local_use_case(1 << 14, 0b0).unwrap_err(),
605 NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 14
606 );
607 }
608
609 #[test]
611 fn note_tag_as_u32() {
612 const HIGH_BITS_MASK: u32 = 0xc000_0000;
613
614 assert_eq!(NoteTag::NetworkAccount(u32::MAX).as_u32() & HIGH_BITS_MASK, NETWORK_ACCOUNT);
615 assert_eq!(
616 NoteTag::NetworkUseCase(u16::MAX, u16::MAX).as_u32() & HIGH_BITS_MASK,
617 NETWORK_PUBLIC_USECASE
618 );
619 assert_eq!(NoteTag::LocalPublicAny(u32::MAX).as_u32() & HIGH_BITS_MASK, LOCAL_PUBLIC_ANY);
620 assert_eq!(NoteTag::LocalAny(0).as_u32() & HIGH_BITS_MASK, LOCAL_ANY);
621 }
622}