miden_objects/note/
note_tag.rs

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
11// CONSTANTS
12// ================================================================================================
13const NETWORK_EXECUTION: u8 = 0;
14const LOCAL_EXECUTION: u8 = 1;
15
16// The 2 most significant bits are set to `0b00`.
17const NETWORK_ACCOUNT: u32 = 0;
18// The 2 most significant bits are set to `0b01`.
19const NETWORK_PUBLIC_USECASE: u32 = 0x4000_0000;
20// The 2 most significant bits are set to `0b10`.
21const LOCAL_PUBLIC_ANY: u32 = 0x8000_0000;
22// The 2 most significant bits are set to `0b11`.
23const LOCAL_ANY: u32 = 0xc000_0000;
24
25/// [super::Note]'s execution mode hints.
26///
27/// The execution hints are _not_ enforced, therefore function only as hints. For example, if a
28/// note's tag is created with the [NoteExecutionMode::Network], further validation is necessary to
29/// check the account_id is known, that the account's state is on-chain, and the account is
30/// controlled by the network.
31///
32/// The goal of the hint is to allow for a network node to quickly filter notes that are not
33/// intended for network execution, and skip the validation steps mentioned above.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[repr(u8)]
36pub enum NoteExecutionMode {
37    Network = NETWORK_EXECUTION,
38    Local = LOCAL_EXECUTION,
39}
40
41// NOTE TAG
42// ================================================================================================
43
44/// [NoteTag]`s are best effort filters for notes registered with the network.
45///
46/// Tags are light-weight values used to speed up queries. The 2 most significant bits of the tags
47/// have the following interpretation:
48///
49/// | Prefix | Name                   | [`NoteExecutionMode`] | Target                   | Allowed [`NoteType`] |
50/// | :----: | :--------------------: | :-------------------: | :----------------------: | :------------------: |
51/// | `0b00` | `NetworkAccount`       | Network               | Network Account          | [`NoteType::Public`] |
52/// | `0b01` | `NetworkUseCase`       | Network               | Use case                 | [`NoteType::Public`] |
53/// | `0b10` | `LocalPublicAny`       | Local                 | Any                      | [`NoteType::Public`] |
54/// | `0b11` | `LocalAny`             | Local                 | Any                      | Any                  |
55///
56/// Where:
57///
58/// - [`NoteExecutionMode`] is set to [`NoteExecutionMode::Network`] to hint a [`Note`](super::Note)
59///   should be consumed by the network. These notes will be further validated and if possible
60///   consumed by it.
61/// - Target describes how to further interpret the bits in the tag.
62///   - For tags with a specific target, the rest of the tag is interpreted as a partial
63///     [`AccountId`]. For network accounts these are the first 30 bits of the ID while for local
64///     account targets, the first 14 bits are used - a trade-off between privacy and uniqueness.
65///   - For use case values, the meaning of the rest of the tag is not specified by the protocol and
66///     can be used by applications built on top of the rollup.
67///
68/// The note type is the only value enforced by the protocol. The rationale is that any note
69/// intended to be consumed by the network must be public to have all the details available. The
70/// public note for local execution is intended to allow users to search for notes that can be
71/// consumed right away, without requiring an off-band communication channel.
72///
73/// **Note on Type Safety**
74///
75/// Each enum variant contains the raw encoding of the note tag, where the first two bits
76/// _should_ correspond to the variant's prefix (as defined in the table above). However, because
77/// enum variants are always public, it is possible to instantiate this enum where this invariant
78/// does not hold, e.g. `NoteTag::NetworkAccount(0b11...)`. For that reason, the enum variants
79/// should take precedence in case of such a mismatch and the inner value **should not be accessed
80/// directly**. Instead, only rely on [`NoteTag::as_u32`] to access the encoded value, which will
81/// always return the correct value.
82#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
83pub enum NoteTag {
84    /// Represents a tag for a note intended for network execution, targeted at a network account.
85    /// The note must be public.
86    NetworkAccount(u32),
87    /// Represents a tag for a note intended for network execution for a public use case. The note
88    /// must be public.
89    NetworkUseCase(u16, u16),
90    /// Represents a tag for a note intended for local execution.
91    ///
92    /// This is used for two purposes:
93    /// - A public use case.
94    /// - A note targeted at any type of account.
95    ///
96    /// In all cases, the note must be **public**.
97    LocalPublicAny(u32),
98    /// Represents a tag for a note intended for local execution.
99    ///
100    /// This is used for two purposes:
101    /// - A private use case.
102    /// - A note targeted at any type of account.
103    ///
104    /// In all cases, the note can be of any type.
105    LocalAny(u32),
106}
107
108impl NoteTag {
109    // CONSTANTS
110    // --------------------------------------------------------------------------------------------
111
112    /// The exponent of the maximum allowed use case id. In other words, 2^exponent is the maximum
113    /// allowed use case id.
114    pub(crate) const MAX_USE_CASE_ID_EXPONENT: u8 = 14;
115
116    // CONSTRUCTORS
117    // --------------------------------------------------------------------------------------------
118
119    /// Returns a new [NoteTag::NetworkAccount] or [NoteTag::LocalAny] instantiated from the
120    /// specified account ID.
121    ///
122    /// The tag is constructed as follows:
123    ///
124    /// - For local execution ([`AccountStorageMode::Private`] or [`AccountStorageMode::Public`]),
125    ///   the two most significant bits are set to `0b11`, which allows for any note type to be
126    ///   used. The following 14 bits are set to the most significant bits of the account ID, and
127    ///   the remaining 16 bits are set to 0.
128    /// - For network execution ([`AccountStorageMode::Network`]), the most significant bits are set
129    ///   to `0b00` and the remaining bits are set to the 30 most significant bits of the account
130    ///   ID.
131    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                // Shift the high bits of the account ID such that they are laid out as:
137                // [34 zero bits | remaining high bits (30 bits)].
138                let high_bits = prefix_id >> 34;
139
140                // This is equivalent to the following layout, interpreted as a u32:
141                // [2 zero bits | remaining high bits (30 bits)].
142                // The two most significant zero bits match the tag we need for network
143                // execution.
144                Self::NetworkAccount(high_bits as u32)
145            },
146            AccountStorageMode::Private | AccountStorageMode::Public => {
147                let prefix_id: u64 = account_id.prefix().into();
148
149                // Shift the high bits of the account ID such that they are laid out as:
150                // [34 zero bits | remaining high bits (30 bits)].
151                let high_bits = prefix_id >> 34;
152
153                // This is equivalent to the following layout, interpreted as a u32:
154                // [2 zero bits | remaining high bits (30 bits)].
155                let high_bits = high_bits as u32;
156
157                // Select the upper half of the u32 which then contains the 14 most significant bits
158                // of the account ID, i.e.:
159                // [2 zero bits | remaining high bits (14 bits) | 16 zero bits].
160                let high_bits = high_bits & 0xffff0000;
161
162                // Set the local execution tag in the two most significant bits.
163                Self::LocalAny(LOCAL_ANY | high_bits)
164            },
165        }
166    }
167
168    /// Returns a new [`NoteTag::NetworkUseCase`] or [`NoteTag::LocalPublicAny`]
169    /// instantiated for a custom use case which requires a public note.
170    ///
171    /// The public use_case tag requires a [NoteType::Public] note.
172    ///
173    /// The two high bits are set to the `b10` or `b01` depending on the execution hint, the next 14
174    /// bits are set to the `use_case_id`, and the low 16 bits are set to `payload`.
175    ///
176    /// # Errors
177    ///
178    /// - If `use_case_id` is larger than or equal to $2^{14}$.
179    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    /// Returns a new [`NoteTag::LocalAny`] instantiated for a custom local use case.
201    ///
202    /// The local use_case tag is the only tag type that allows for [NoteType::Private] notes.
203    ///
204    /// The two high bits are set to the `b11`, the next 14 bits are set to the `use_case_id`, and
205    /// the low 16 bits are set to `payload`.
206    ///
207    /// # Errors
208    ///
209    /// - If `use_case_id` is larger than or equal to 2^14.
210    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    // PUBLIC ACCESSORS
222    // --------------------------------------------------------------------------------------------
223
224    /// Returns true if the note is intended for execution by a specific account, i.e.
225    /// [`NoteTag::NetworkAccount`]
226    pub fn is_single_target(&self) -> bool {
227        matches!(self, NoteTag::NetworkAccount(_))
228    }
229
230    /// Returns note execution mode defined by this tag.
231    ///
232    /// If the most significant bit of the tag is 0 the note is intended for local execution;
233    /// otherwise, the note is intended for network execution.
234    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    /// Returns the inner u32 value of this tag.
242    pub fn as_u32(&self) -> u32 {
243        // Note that we always set the two most significant bits to the prefix corresponding to the
244        // enum variant. See the note on type safety on the NoteTag docs.
245
246        /// Masks out the two most significant bits, leaving all lower bits untouched.
247        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    // UTILITY METHODS
260    // --------------------------------------------------------------------------------------------
261
262    /// Returns an error if this tag is not consistent with the specified note type, and self
263    /// otherwise.
264    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        // Ensure the note is public if the note tag requires it.
270        if self.requires_public_note() && note_type != NoteType::Public {
271            Err(NoteError::PublicNoteRequired(note_type))
272        } else {
273            Ok(*self)
274        }
275    }
276
277    /// Returns `true` if the note tag requires a public note.
278    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
292// CONVERSIONS INTO NOTE TAG
293// ================================================================================================
294
295impl From<u32> for NoteTag {
296    fn from(value: u32) -> Self {
297        // Mask out the two most significant bits.
298        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                // This branch should be unreachable because `prefix` is derived from
305                // the top 2 bits of a u32, which can only be 0, 1, 2, or 3.
306                unreachable!("Invalid value encountered: {:b}", value);
307            },
308        }
309    }
310}
311
312// CONVERSIONS FROM NOTE TAG
313// ================================================================================================
314
315impl 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
327// SERIALIZATION
328// ================================================================================================
329
330impl 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// TESTS
344// ================================================================================================
345
346#[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            // for local execution[`NoteExecutionMode::Local`], all notes are allowed
426            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            // for local execution[`NoteExecutionMode::Local`], all notes are allowed
440            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            // for network execution[`NoteExecutionMode::Network`], only public notes are allowed
454            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        /// Private Account ID with the following bit pattern in the first and second byte:
470        /// 0b11001100_01010101
471        ///   ^^^^^^^^ ^^^^^^  <- 14 bits of the local tag.
472        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        // Expected private tag of variant `NoteTag::LocalAny`.
477        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        /// Public Account ID with the following bit pattern in the first and second byte:
485        /// 0b10101010_01010101
486        ///   ^^^^^^^^ ^^^^^^  <- 14 bits of the local tag.
487        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        // Expected public tag of variant `NoteTag::LocalAny`.
492        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        /// Network Account ID with the following bit pattern in the first four bytes:
500        /// 0b10101010_11001100_01110111_11001100
501        ///   ^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^  <- 30 bits of the network tag.
502        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        // Expected network tag of variant `NoteTag::NetworkAccount`.
507        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        // NETWORK
515        // ----------------------------------------------------------------------------------------
516        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        // LOCAL
540        // ----------------------------------------------------------------------------------------
541        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    /// Tests that as_u32 returns the correct prefix independent of the inner value.
610    #[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}