1use core::fmt;
2
3use miden_crypto::Felt;
4
5use super::{
6 AccountId,
7 ByteReader,
8 ByteWriter,
9 Deserializable,
10 DeserializationError,
11 NoteError,
12 NoteType,
13 Serializable,
14};
15use crate::account::AccountStorageMode;
16
17const NETWORK_EXECUTION: u8 = 0;
20const LOCAL_EXECUTION: u8 = 1;
21
22const NETWORK_ACCOUNT: u32 = 0;
24const NETWORK_PUBLIC_USECASE: u32 = 0x4000_0000;
26const LOCAL_PUBLIC_ANY: u32 = 0x8000_0000;
28const LOCAL_ANY: u32 = 0xc000_0000;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[repr(u8)]
42pub enum NoteExecutionMode {
43 Network = NETWORK_EXECUTION,
44 Local = LOCAL_EXECUTION,
45}
46
47#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
89pub enum NoteTag {
90 NetworkAccount(u32),
93 NetworkUseCase(u16, u16),
96 LocalPublicAny(u32),
104 LocalAny(u32),
112}
113
114impl NoteTag {
115 pub(crate) const MAX_USE_CASE_ID_EXPONENT: u8 = 14;
121 pub const DEFAULT_LOCAL_TAG_LENGTH: u8 = 14;
123 pub const DEFAULT_NETWORK_TAG_LENGTH: u8 = 30;
125 pub const MAX_LOCAL_TAG_LENGTH: u8 = 30;
127
128 pub fn from_account_id(account_id: AccountId) -> Self {
144 match account_id.storage_mode() {
145 AccountStorageMode::Network => Self::from_network_account_id(account_id),
146 AccountStorageMode::Private | AccountStorageMode::Public => {
147 Self::from_local_account_id(account_id, Self::DEFAULT_LOCAL_TAG_LENGTH).unwrap()
149 },
150 }
151 }
152
153 pub(crate) fn from_local_account_id(
165 account_id: AccountId,
166 tag_len: u8,
167 ) -> Result<Self, NoteError> {
168 if tag_len > Self::MAX_LOCAL_TAG_LENGTH {
169 return Err(NoteError::NoteTagLengthTooLarge(tag_len));
170 }
171
172 let prefix_id: u64 = account_id.prefix().into();
173
174 let high_bits = prefix_id >> 34;
177
178 let high_bits = high_bits as u32;
181
182 let high_bits = high_bits & (u32::MAX << (32 - 2 - tag_len));
185
186 Ok(Self::LocalAny(LOCAL_ANY | high_bits))
188 }
189
190 pub(crate) fn from_network_account_id(account_id: AccountId) -> Self {
197 let prefix_id: u64 = account_id.prefix().into();
198
199 let high_bits = prefix_id >> 34;
202
203 Self::NetworkAccount(high_bits as u32)
207 }
208
209 pub fn for_public_use_case(
221 use_case_id: u16,
222 payload: u16,
223 execution: NoteExecutionMode,
224 ) -> Result<Self, NoteError> {
225 if (use_case_id >> 14) != 0 {
226 return Err(NoteError::NoteTagUseCaseTooLarge(use_case_id));
227 }
228
229 match execution {
230 NoteExecutionMode::Network => {
231 let use_case_bits = (NETWORK_PUBLIC_USECASE >> 16) as u16 | use_case_id;
232 Ok(Self::NetworkUseCase(use_case_bits, payload))
233 },
234 NoteExecutionMode::Local => {
235 let tag_u32 = LOCAL_PUBLIC_ANY | ((use_case_id as u32) << 16) | (payload as u32);
236 Ok(Self::LocalPublicAny(tag_u32))
237 },
238 }
239 }
240
241 pub fn for_local_use_case(use_case_id: u16, payload: u16) -> Result<Self, NoteError> {
252 if (use_case_id >> NoteTag::MAX_USE_CASE_ID_EXPONENT) != 0 {
253 return Err(NoteError::NoteTagUseCaseTooLarge(use_case_id));
254 }
255
256 let use_case_bits = (use_case_id as u32) << 16;
257 let payload_bits = payload as u32;
258
259 Ok(Self::LocalAny(LOCAL_ANY | use_case_bits | payload_bits))
260 }
261
262 pub fn is_single_target(&self) -> bool {
268 matches!(self, NoteTag::NetworkAccount(_))
269 }
270
271 pub fn execution_mode(&self) -> NoteExecutionMode {
276 match self {
277 NoteTag::NetworkAccount(_) | NoteTag::NetworkUseCase(..) => NoteExecutionMode::Network,
278 NoteTag::LocalAny(_) | NoteTag::LocalPublicAny(..) => NoteExecutionMode::Local,
279 }
280 }
281
282 pub fn as_u32(&self) -> u32 {
284 const LOW_BITS_MASK: u32 = 0x3fff_ffff;
289 match self {
290 NoteTag::NetworkAccount(tag) => *tag & LOW_BITS_MASK,
291 NoteTag::NetworkUseCase(use_case_bits, payload_bits) => {
292 ((*use_case_bits as u32) << 16 | *payload_bits as u32) & LOW_BITS_MASK
293 | NETWORK_PUBLIC_USECASE
294 },
295 NoteTag::LocalPublicAny(tag) => (*tag & LOW_BITS_MASK) | LOCAL_PUBLIC_ANY,
296 NoteTag::LocalAny(tag) => (*tag & LOW_BITS_MASK) | LOCAL_ANY,
297 }
298 }
299
300 pub fn validate(&self, note_type: NoteType) -> Result<Self, NoteError> {
306 if self.execution_mode() == NoteExecutionMode::Network && note_type != NoteType::Public {
307 return Err(NoteError::NetworkExecutionRequiresPublicNote(note_type));
308 }
309
310 if self.requires_public_note() && note_type != NoteType::Public {
312 Err(NoteError::PublicNoteRequired(note_type))
313 } else {
314 Ok(*self)
315 }
316 }
317
318 fn requires_public_note(&self) -> bool {
320 matches!(
321 self,
322 NoteTag::NetworkAccount(_) | NoteTag::NetworkUseCase(_, _) | NoteTag::LocalPublicAny(_)
323 )
324 }
325}
326
327impl fmt::Display for NoteTag {
328 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329 write!(f, "{}", self.as_u32())
330 }
331}
332
333impl From<u32> for NoteTag {
337 fn from(value: u32) -> Self {
338 match value & 0xc0000000 {
340 NETWORK_ACCOUNT => Self::NetworkAccount(value),
341 NETWORK_PUBLIC_USECASE => Self::NetworkUseCase((value >> 16) as u16, value as u16),
342 LOCAL_ANY => Self::LocalAny(value),
343 LOCAL_PUBLIC_ANY => Self::LocalPublicAny(value),
344 _ => {
345 unreachable!("Invalid value encountered: {:b}", value);
348 },
349 }
350 }
351}
352
353impl From<NoteTag> for u32 {
357 fn from(value: NoteTag) -> Self {
358 value.as_u32()
359 }
360}
361
362impl From<NoteTag> for Felt {
363 fn from(value: NoteTag) -> Self {
364 Felt::from(value.as_u32())
365 }
366}
367
368impl Serializable for NoteTag {
372 fn write_into<W: ByteWriter>(&self, target: &mut W) {
373 self.as_u32().write_into(target);
374 }
375}
376
377impl Deserializable for NoteTag {
378 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
379 let tag = u32::read_from(source)?;
380 Ok(Self::from(tag))
381 }
382}
383
384#[cfg(test)]
388mod tests {
389
390 use assert_matches::assert_matches;
391
392 use super::{NoteExecutionMode, NoteTag};
393 use crate::NoteError;
394 use crate::account::AccountId;
395 use crate::note::NoteType;
396 use crate::note::note_tag::{
397 LOCAL_ANY,
398 LOCAL_PUBLIC_ANY,
399 NETWORK_ACCOUNT,
400 NETWORK_PUBLIC_USECASE,
401 };
402 use crate::testing::account_id::{
403 ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET,
404 ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET,
405 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
406 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
407 ACCOUNT_ID_PRIVATE_SENDER,
408 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
409 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
410 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
411 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3,
412 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
413 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
414 ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE,
415 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
416 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
417 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2,
418 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE,
419 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2,
420 ACCOUNT_ID_SENDER,
421 };
422
423 #[test]
424 fn from_account_id() {
425 let private_accounts = [
426 AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(),
427 AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap(),
428 AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap(),
429 AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap(),
430 AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap(),
431 ];
432 let public_accounts = [
433 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(),
434 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2).unwrap(),
435 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE).unwrap(),
436 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2)
437 .unwrap(),
438 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(),
439 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).unwrap(),
440 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2).unwrap(),
441 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3).unwrap(),
442 AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET).unwrap(),
443 AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1).unwrap(),
444 ];
445 let network_accounts = [
446 AccountId::try_from(ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE).unwrap(),
447 AccountId::try_from(ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET).unwrap(),
448 AccountId::try_from(ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET).unwrap(),
449 ];
450
451 for account_id in network_accounts {
452 let tag = NoteTag::from_account_id(account_id);
453 assert!(tag.is_single_target());
454 assert_eq!(tag.execution_mode(), NoteExecutionMode::Network);
455
456 tag.validate(NoteType::Public)
457 .expect("network execution should require notes to be public");
458 assert_matches!(
459 tag.validate(NoteType::Private),
460 Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private))
461 );
462 assert_matches!(
463 tag.validate(NoteType::Encrypted),
464 Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted))
465 );
466 }
467
468 for account_id in private_accounts {
469 let tag = NoteTag::from_account_id(account_id);
470 assert!(!tag.is_single_target());
471 assert_eq!(tag.execution_mode(), NoteExecutionMode::Local);
472
473 tag.validate(NoteType::Public)
475 .expect("local execution should support public notes");
476 tag.validate(NoteType::Private)
477 .expect("local execution should support private notes");
478 tag.validate(NoteType::Encrypted)
479 .expect("local execution should support encrypted notes");
480 }
481
482 for account_id in public_accounts {
483 let tag = NoteTag::from_account_id(account_id);
484 assert!(!tag.is_single_target());
485 assert_eq!(tag.execution_mode(), NoteExecutionMode::Local);
486
487 tag.validate(NoteType::Public)
489 .expect("local execution should support public notes");
490 tag.validate(NoteType::Private)
491 .expect("local execution should support private notes");
492 tag.validate(NoteType::Encrypted)
493 .expect("local execution should support encrypted notes");
494 }
495
496 for account_id in network_accounts {
497 let tag = NoteTag::from_account_id(account_id);
498 assert!(tag.is_single_target());
499 assert_eq!(tag.execution_mode(), NoteExecutionMode::Network);
500
501 tag.validate(NoteType::Public)
503 .expect("network execution should support public notes");
504 assert_matches!(
505 tag.validate(NoteType::Private),
506 Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private))
507 );
508 assert_matches!(
509 tag.validate(NoteType::Encrypted),
510 Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted))
511 );
512 }
513 }
514
515 #[test]
516 fn from_private_account_id() {
517 const PRIVATE_ACCOUNT_INT: u128 = ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE
521 | 0x0055_0000_0000_0000_0000_0000_0000_0000;
522 let private_account_id = AccountId::try_from(PRIVATE_ACCOUNT_INT).unwrap();
523
524 let expected_private_tag = 0b11110011_00010101_00000000_00000000;
526
527 assert_eq!(NoteTag::from_account_id(private_account_id).as_u32(), expected_private_tag);
528 }
529
530 #[test]
531 fn from_public_account_id() {
532 const PUBLIC_ACCOUNT_INT: u128 = ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE
536 | 0x0055_ccaa_0000_0000_0000_0000_0000_0000;
537 let public_account_id = AccountId::try_from(PUBLIC_ACCOUNT_INT).unwrap();
538
539 let expected_public_local_tag = 0b11101010_10010101_00000000_00000000u32;
541
542 assert_eq!(NoteTag::from_account_id(public_account_id).as_u32(), expected_public_local_tag);
543 }
544
545 #[test]
546 fn from_network_account_id() {
547 const NETWORK_ACCOUNT_INT: u128 = ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE
551 | 0x00cc_77cc_0000_0000_0000_0000_0000_0000;
552 let network_account_id = AccountId::try_from(NETWORK_ACCOUNT_INT).unwrap();
553
554 let expected_network_tag = 0b00101010_10110011_00011101_11110011;
556
557 assert_eq!(NoteTag::from_account_id(network_account_id).as_u32(), expected_network_tag);
558 }
559
560 #[test]
561 fn for_public_use_case() {
562 let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionMode::Network).unwrap();
565 assert_eq!(tag.as_u32(), 0b01000000_00000000_00000000_00000000u32);
566
567 tag.validate(NoteType::Public).unwrap();
568
569 assert_matches!(
570 tag.validate(NoteType::Private).unwrap_err(),
571 NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private)
572 );
573 assert_matches!(
574 tag.validate(NoteType::Encrypted).unwrap_err(),
575 NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted)
576 );
577
578 let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionMode::Network).unwrap();
579 assert_eq!(tag.as_u32(), 0b01000000_00000001_00000000_00000000u32);
580
581 let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionMode::Network).unwrap();
582 assert_eq!(tag.as_u32(), 0b01000000_00000000_00000000_00000001u32);
583
584 let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionMode::Network).unwrap();
585 assert_eq!(tag.as_u32(), 0b01100000_00000000_00000000_00000000u32);
586
587 let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionMode::Local).unwrap();
590 assert_eq!(tag.as_u32(), 0b10000000_00000000_00000000_00000000u32);
591
592 tag.validate(NoteType::Public).unwrap();
593 assert_matches!(
594 tag.validate(NoteType::Private).unwrap_err(),
595 NoteError::PublicNoteRequired(NoteType::Private)
596 );
597 assert_matches!(
598 tag.validate(NoteType::Encrypted).unwrap_err(),
599 NoteError::PublicNoteRequired(NoteType::Encrypted)
600 );
601
602 let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionMode::Local).unwrap();
603 assert_eq!(tag.as_u32(), 0b10000000_00000000_00000000_00000001u32);
604
605 let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionMode::Local).unwrap();
606 assert_eq!(tag.as_u32(), 0b10000000_00000001_00000000_00000000u32);
607
608 let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionMode::Local).unwrap();
609 assert_eq!(tag.as_u32(), 0b10100000_00000000_00000000_00000000u32);
610
611 assert_matches!(
612 NoteTag::for_public_use_case(1 << 15, 0b0, NoteExecutionMode::Local).unwrap_err(),
613 NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 15
614 );
615 assert_matches!(
616 NoteTag::for_public_use_case(1 << 14, 0b0, NoteExecutionMode::Local).unwrap_err(),
617 NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 14
618 );
619 }
620
621 #[test]
622 fn for_private_use_case() {
623 let tag = NoteTag::for_local_use_case(0b0, 0b0).unwrap();
624 assert_eq!(
625 tag.as_u32() >> 30,
626 LOCAL_ANY >> 30,
627 "local use case prefix should be local any"
628 );
629 assert_eq!(tag.as_u32(), 0b11000000_00000000_00000000_00000000u32);
630
631 tag.validate(NoteType::Public)
632 .expect("local execution should support public notes");
633 tag.validate(NoteType::Private)
634 .expect("local execution should support private notes");
635 tag.validate(NoteType::Encrypted)
636 .expect("local execution should support encrypted notes");
637
638 let tag = NoteTag::for_local_use_case(0b0, 0b1).unwrap();
639 assert_eq!(tag.as_u32(), 0b11000000_00000000_00000000_00000001u32);
640
641 let tag = NoteTag::for_local_use_case(0b1, 0b0).unwrap();
642 assert_eq!(tag.as_u32(), 0b11000000_00000001_00000000_00000000u32);
643
644 let tag = NoteTag::for_local_use_case(1 << 13, 0b0).unwrap();
645 assert_eq!(tag.as_u32(), 0b11100000_00000000_00000000_00000000u32);
646
647 assert_matches!(
648 NoteTag::for_local_use_case(1 << 15, 0b0).unwrap_err(),
649 NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 15
650 );
651 assert_matches!(
652 NoteTag::for_local_use_case(1 << 14, 0b0).unwrap_err(),
653 NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 14
654 );
655 }
656
657 #[test]
659 fn note_tag_as_u32() {
660 const HIGH_BITS_MASK: u32 = 0xc000_0000;
661
662 assert_eq!(NoteTag::NetworkAccount(u32::MAX).as_u32() & HIGH_BITS_MASK, NETWORK_ACCOUNT);
663 assert_eq!(
664 NoteTag::NetworkUseCase(u16::MAX, u16::MAX).as_u32() & HIGH_BITS_MASK,
665 NETWORK_PUBLIC_USECASE
666 );
667 assert_eq!(NoteTag::LocalPublicAny(u32::MAX).as_u32() & HIGH_BITS_MASK, LOCAL_PUBLIC_ANY);
668 assert_eq!(NoteTag::LocalAny(0).as_u32() & HIGH_BITS_MASK, LOCAL_ANY);
669 }
670}