miden_objects/note/
note_tag.rs

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
17// CONSTANTS
18// ================================================================================================
19const NETWORK_EXECUTION: u8 = 0;
20const LOCAL_EXECUTION: u8 = 1;
21
22// The 2 most significant bits are set to `0b00`.
23const NETWORK_ACCOUNT: u32 = 0;
24// The 2 most significant bits are set to `0b01`.
25const NETWORK_PUBLIC_USECASE: u32 = 0x4000_0000;
26// The 2 most significant bits are set to `0b10`.
27const LOCAL_PUBLIC_ANY: u32 = 0x8000_0000;
28// The 2 most significant bits are set to `0b11`.
29const LOCAL_ANY: u32 = 0xc000_0000;
30
31/// [super::Note]'s execution mode hints.
32///
33/// The execution hints are _not_ enforced, therefore function only as hints. For example, if a
34/// note's tag is created with the [NoteExecutionMode::Network], further validation is necessary to
35/// check the account_id is known, that the account's state is public on chain, and the account is
36/// controlled by the network.
37///
38/// The goal of the hint is to allow for a network node to quickly filter notes that are not
39/// intended for network execution, and skip the validation steps mentioned above.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[repr(u8)]
42pub enum NoteExecutionMode {
43    Network = NETWORK_EXECUTION,
44    Local = LOCAL_EXECUTION,
45}
46
47// NOTE TAG
48// ================================================================================================
49
50/// [NoteTag]`s are best effort filters for notes registered with the network.
51///
52/// Tags are light-weight values used to speed up queries. The 2 most significant bits of the tags
53/// have the following interpretation:
54///
55/// | Prefix | Name                   | [`NoteExecutionMode`] | Target                   | Allowed [`NoteType`] |
56/// | :----: | :--------------------: | :-------------------: | :----------------------: | :------------------: |
57/// | `0b00` | `NetworkAccount`       | Network               | Network Account          | [`NoteType::Public`] |
58/// | `0b01` | `NetworkUseCase`       | Network               | Use case                 | [`NoteType::Public`] |
59/// | `0b10` | `LocalPublicAny`       | Local                 | Any                      | [`NoteType::Public`] |
60/// | `0b11` | `LocalAny`             | Local                 | Any                      | Any                  |
61///
62/// Where:
63///
64/// - [`NoteExecutionMode`] is set to [`NoteExecutionMode::Network`] to hint a [`Note`](super::Note)
65///   should be consumed by the network. These notes will be further validated and if possible
66///   consumed by it.
67/// - Target describes how to further interpret the bits in the tag.
68///   - For tags with a specific target, the rest of the tag is interpreted as a partial
69///     [`AccountId`]. For network accounts these are the first 30 bits of the ID while for local
70///     account targets, the first 14 bits are used - a trade-off between privacy and uniqueness.
71///   - For use case values, the meaning of the rest of the tag is not specified by the protocol and
72///     can be used by applications built on top of the rollup.
73///
74/// The note type is the only value enforced by the protocol. The rationale is that any note
75/// intended to be consumed by the network must be public to have all the details available. The
76/// public note for local execution is intended to allow users to search for notes that can be
77/// consumed right away, without requiring an off-band communication channel.
78///
79/// **Note on Type Safety**
80///
81/// Each enum variant contains the raw encoding of the note tag, where the first two bits
82/// _should_ correspond to the variant's prefix (as defined in the table above). However, because
83/// enum variants are always public, it is possible to instantiate this enum where this invariant
84/// does not hold, e.g. `NoteTag::NetworkAccount(0b11...)`. For that reason, the enum variants
85/// should take precedence in case of such a mismatch and the inner value **should not be accessed
86/// directly**. Instead, only rely on [`NoteTag::as_u32`] to access the encoded value, which will
87/// always return the correct value.
88#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
89pub enum NoteTag {
90    /// Represents a tag for a note intended for network execution, targeted at a network account.
91    /// The note must be public.
92    NetworkAccount(u32),
93    /// Represents a tag for a note intended for network execution for a public use case. The note
94    /// must be public.
95    NetworkUseCase(u16, u16),
96    /// Represents a tag for a note intended for local execution.
97    ///
98    /// This is used for two purposes:
99    /// - A public use case.
100    /// - A note targeted at any type of account.
101    ///
102    /// In all cases, the note must be **public**.
103    LocalPublicAny(u32),
104    /// Represents a tag for a note intended for local execution.
105    ///
106    /// This is used for two purposes:
107    /// - A private use case.
108    /// - A note targeted at any type of account.
109    ///
110    /// In all cases, the note can be of any type.
111    LocalAny(u32),
112}
113
114impl NoteTag {
115    // CONSTANTS
116    // --------------------------------------------------------------------------------------------
117
118    /// The exponent of the maximum allowed use case id. In other words, 2^exponent is the maximum
119    /// allowed use case id.
120    pub(crate) const MAX_USE_CASE_ID_EXPONENT: u8 = 14;
121    /// The default note tag length for an account ID with local execution.
122    pub const DEFAULT_LOCAL_TAG_LENGTH: u8 = 14;
123    /// The default note tag length for an account ID with network execution.
124    pub const DEFAULT_NETWORK_TAG_LENGTH: u8 = 30;
125    /// The maximum number of bits that can be encoded into the tag for local accounts.
126    pub const MAX_LOCAL_TAG_LENGTH: u8 = 30;
127
128    // CONSTRUCTORS
129    // --------------------------------------------------------------------------------------------
130
131    /// Returns a new [NoteTag::NetworkAccount] or [NoteTag::LocalAny] instantiated from the
132    /// specified account ID.
133    ///
134    /// The tag is constructed as follows:
135    ///
136    /// - For local execution ([`AccountStorageMode::Private`] or [`AccountStorageMode::Public`]),
137    ///   the two most significant bits are set to `0b11`, which allows for any note type to be
138    ///   used. The following 14 bits are set to the most significant bits of the account ID, and
139    ///   the remaining 16 bits are set to 0.
140    /// - For network execution ([`AccountStorageMode::Network`]), the most significant bits are set
141    ///   to `0b00` and the remaining bits are set to the 30 most significant bits of the account
142    ///   ID.
143    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                // safe to unwrap since DEFAULT_LOCAL_TAG_LENGTH < MAX_LOCAL_TAG_LENGTH
148                Self::from_local_account_id(account_id, Self::DEFAULT_LOCAL_TAG_LENGTH).unwrap()
149            },
150        }
151    }
152
153    /// Constructs a [`NoteTag::LocalAny`] from the given `account_id` and `tag_len`.
154    ///
155    /// The tag is constructed as follows:
156    ///
157    /// - The two most significant bits are set to `0b11` to indicate a [LOCAL_ANY] tag.
158    /// - The next `tag_len` bits are set to the most significant bits of the account ID prefix.
159    /// - The remaining bits are set to zero.
160    ///
161    /// # Errors
162    ///
163    /// Returns an error if `tag_len` is larger than [`NoteTag::MAX_LOCAL_TAG_LENGTH`].
164    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        // Shift the high bits of the account ID such that they are laid out as:
175        // [34 zero bits | remaining high bits (30 bits)].
176        let high_bits = prefix_id >> 34;
177
178        // This is equivalent to the following layout, interpreted as a u32:
179        // [2 zero bits | remaining high bits (30 bits)].
180        let high_bits = high_bits as u32;
181
182        // Select the top `tag_len` bits of the account ID, i.e.:
183        // [2 zero bits | remaining high bits (tag_len bits) | (30 - tag_len) zero bits].
184        let high_bits = high_bits & (u32::MAX << (32 - 2 - tag_len));
185
186        // Set the local execution tag in the two most significant bits.
187        Ok(Self::LocalAny(LOCAL_ANY | high_bits))
188    }
189
190    /// Constructs a [`NoteTag::NetworkAccount`] from the specified `account_id`.
191    ///
192    /// The tag is constructed as follows:
193    ///
194    /// - The two most significant bits are set to `0b00` to indicate a [NETWORK_ACCOUNT] tag.
195    /// - The remaining bits are set to the 30 most significant bits of the account ID.
196    pub(crate) fn from_network_account_id(account_id: AccountId) -> Self {
197        let prefix_id: u64 = account_id.prefix().into();
198
199        // Shift the high bits of the account ID such that they are laid out as:
200        // [34 zero bits | remaining high bits (30 bits)].
201        let high_bits = prefix_id >> 34;
202
203        // This is equivalent to the following layout, interpreted as a u32:
204        // [2 zero bits | remaining high bits (30 bits)].
205        // The two most significant zero bits match the tag we need for network
206        Self::NetworkAccount(high_bits as u32)
207    }
208
209    /// Returns a new [`NoteTag::NetworkUseCase`] or [`NoteTag::LocalPublicAny`]
210    /// instantiated for a custom use case which requires a public note.
211    ///
212    /// The public use_case tag requires a [NoteType::Public] note.
213    ///
214    /// The two high bits are set to the `b10` or `b01` depending on the execution hint, the next 14
215    /// bits are set to the `use_case_id`, and the low 16 bits are set to `payload`.
216    ///
217    /// # Errors
218    ///
219    /// - If `use_case_id` is larger than or equal to $2^{14}$.
220    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    /// Returns a new [`NoteTag::LocalAny`] instantiated for a custom local use case.
242    ///
243    /// The local use_case tag is the only tag type that allows for [NoteType::Private] notes.
244    ///
245    /// The two high bits are set to the `b11`, the next 14 bits are set to the `use_case_id`, and
246    /// the low 16 bits are set to `payload`.
247    ///
248    /// # Errors
249    ///
250    /// - If `use_case_id` is larger than or equal to 2^14.
251    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    // PUBLIC ACCESSORS
263    // --------------------------------------------------------------------------------------------
264
265    /// Returns true if the note is intended for execution by a specific account, i.e.
266    /// [`NoteTag::NetworkAccount`]
267    pub fn is_single_target(&self) -> bool {
268        matches!(self, NoteTag::NetworkAccount(_))
269    }
270
271    /// Returns note execution mode defined by this tag.
272    ///
273    /// If the most significant bit of the tag is 0 the note is intended for local execution;
274    /// otherwise, the note is intended for network execution.
275    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    /// Returns the inner u32 value of this tag.
283    pub fn as_u32(&self) -> u32 {
284        // Note that we always set the two most significant bits to the prefix corresponding to the
285        // enum variant. See the note on type safety on the NoteTag docs.
286
287        /// Masks out the two most significant bits, leaving all lower bits untouched.
288        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    // UTILITY METHODS
301    // --------------------------------------------------------------------------------------------
302
303    /// Returns an error if this tag is not consistent with the specified note type, and self
304    /// otherwise.
305    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        // Ensure the note is public if the note tag requires it.
311        if self.requires_public_note() && note_type != NoteType::Public {
312            Err(NoteError::PublicNoteRequired(note_type))
313        } else {
314            Ok(*self)
315        }
316    }
317
318    /// Returns `true` if the note tag requires a public note.
319    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
333// CONVERSIONS INTO NOTE TAG
334// ================================================================================================
335
336impl From<u32> for NoteTag {
337    fn from(value: u32) -> Self {
338        // Mask out the two most significant bits.
339        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                // This branch should be unreachable because `prefix` is derived from
346                // the top 2 bits of a u32, which can only be 0, 1, 2, or 3.
347                unreachable!("Invalid value encountered: {:b}", value);
348            },
349        }
350    }
351}
352
353// CONVERSIONS FROM NOTE TAG
354// ================================================================================================
355
356impl 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
368// SERIALIZATION
369// ================================================================================================
370
371impl 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// TESTS
385// ================================================================================================
386
387#[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            // for local execution[`NoteExecutionMode::Local`], all notes are allowed
474            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            // for local execution[`NoteExecutionMode::Local`], all notes are allowed
488            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            // for network execution[`NoteExecutionMode::Network`], only public notes are allowed
502            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        /// Private Account ID with the following bit pattern in the first and second byte:
518        /// 0b11001100_01010101
519        ///   ^^^^^^^^ ^^^^^^  <- 14 bits of the local tag.
520        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        // Expected private tag of variant `NoteTag::LocalAny`.
525        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        /// Public Account ID with the following bit pattern in the first and second byte:
533        /// 0b10101010_01010101
534        ///   ^^^^^^^^ ^^^^^^  <- 14 bits of the local tag.
535        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        // Expected public tag of variant `NoteTag::LocalAny`.
540        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        /// Network Account ID with the following bit pattern in the first four bytes:
548        /// 0b10101010_11001100_01110111_11001100
549        ///   ^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^  <- 30 bits of the network tag.
550        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        // Expected network tag of variant `NoteTag::NetworkAccount`.
555        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        // NETWORK
563        // ----------------------------------------------------------------------------------------
564        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        // LOCAL
588        // ----------------------------------------------------------------------------------------
589        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    /// Tests that as_u32 returns the correct prefix independent of the inner value.
658    #[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}