miden_objects/note/
note_tag.rs

1use core::{fmt, num::TryFromIntError};
2
3use miden_crypto::Felt;
4
5use super::{
6    AccountId, ByteReader, ByteWriter, Deserializable, DeserializationError, NoteError, NoteType,
7    Serializable,
8};
9
10// CONSTANTS
11// ================================================================================================
12const NETWORK_EXECUTION: u8 = 0;
13const LOCAL_EXECUTION: u8 = 1;
14
15// The 2 most significant bits are set to `0b11`.
16const LOCAL_EXECUTION_WITH_ALL_NOTE_TYPES_ALLOWED: u32 = 0xc000_0000;
17// The 2 most significant bits are set to `0b10`.
18const PUBLIC_USECASE: u32 = 0x8000_0000;
19
20/// [super::Note]'s execution mode hints.
21///
22/// The execution hints are _not_ enforced, therefore function only as hints. For example, if a
23/// note's tag is created with the [NoteExecutionMode::Network], further validation is necessary to
24/// check the account_id is known, that the account's state is on-chain, and the account is
25/// controlled by the network.
26///
27/// The goal of the hint is to allow for a network node to quickly filter notes that are not
28/// intended for network execution, and skip the validation steps mentioned above.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[repr(u8)]
31pub enum NoteExecutionMode {
32    Network = NETWORK_EXECUTION,
33    Local = LOCAL_EXECUTION,
34}
35
36// NOTE TAG
37// ================================================================================================
38
39/// [NoteTag]`s are best effort filters for notes registered with the network.
40///
41/// Tags are light-weight values used to speed up queries. The 2 most significant bits of the tags
42/// have the following interpretation:
43///
44/// | Prefix | Execution mode | Target   | Allowed [NoteType] |
45/// | ------ | :------------: | :------: | :----------------: |
46/// | `0b00` | Network        | Specific | [NoteType::Public] |
47/// | `0b01` | Network        | Use case | [NoteType::Public] |
48/// | `0b10` | Local          | Any      | [NoteType::Public] |
49/// | `0b11` | Local          | Any      | Any                |
50///
51/// Where:
52///
53/// - [`NoteExecutionMode`] is set to [`NoteExecutionMode::Network`] to hint a [`Note`](super::Note)
54///   should be consumed by the network. These notes will be further validated and if possible
55///   consumed by it.
56/// - Target describes how to further interpret the bits in the tag. For tags with a specific
57///   target, the rest of the tag is interpreted as a partial [`AccountId`]. For use case values,
58///   the meaning of the rest of the tag is not specified by the protocol and can be used by
59///   applications built on top of the rollup.
60///
61/// The note type is the only value enforced by the protocol. The rationale is that any note
62/// intended to be consumed by the network must be public to have all the details available. The
63/// public note for local execution is intended to allow users to search for notes that can be
64/// consumed right away, without requiring an off-band communication channel.
65#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
66pub struct NoteTag(u32);
67
68impl NoteTag {
69    // CONSTANTS
70    // --------------------------------------------------------------------------------------------
71
72    /// The exponent of the maximum allowed use case id. In other words, 2^exponent is the maximum
73    /// allowed use case id.
74    pub(crate) const MAX_USE_CASE_ID_EXPONENT: u8 = 14;
75
76    // CONSTRUCTORS
77    // --------------------------------------------------------------------------------------------
78
79    /// Returns a new [NoteTag] instantiated from the specified account ID and execution mode.
80    ///
81    /// The tag is constructed as follows:
82    ///
83    /// - For local execution, the two most significant bits are set to `0b11`, which allows for any
84    ///   note type to be used. The following 14 bits are set to the most significant bits of the
85    ///   account ID, and the remaining 16 bits are set to 0.
86    /// - For network execution, the most significant bits are set to `0b00` and the remaining bits
87    ///   are set to the 30 most significant bits of the account ID.
88    ///
89    /// # Errors
90    ///
91    /// This will return an error if the account_id is not for a public account and the execution
92    /// hint is set to [NoteExecutionMode::Network].
93    pub fn from_account_id(
94        account_id: AccountId,
95        execution: NoteExecutionMode,
96    ) -> Result<Self, NoteError> {
97        match execution {
98            NoteExecutionMode::Local => {
99                let prefix_id: u64 = account_id.prefix().into();
100
101                // Shift the high bits of the account ID such that they are layed out as:
102                // [34 zero bits | remaining high bits (30 bits)].
103                let high_bits = prefix_id >> 34;
104
105                // This is equivalent to the following layout, interpreted as a u32:
106                // [2 zero bits | remaining high bits (30 bits)].
107                let high_bits = high_bits as u32;
108
109                // Select the upper half of the u32 which then contains the 14 most significant bits
110                // of the account ID, i.e.:
111                // [2 zero bits | remaining high bits (14 bits) | 16 zero bits].
112                let high_bits = high_bits & 0xffff0000;
113
114                // Set the local execution tag in the two most significant bits.
115                Ok(Self(high_bits | LOCAL_EXECUTION_WITH_ALL_NOTE_TYPES_ALLOWED))
116            },
117            NoteExecutionMode::Network => {
118                if !account_id.is_public() {
119                    Err(NoteError::NetworkExecutionRequiresOnChainAccount)
120                } else {
121                    let prefix_id: u64 = account_id.prefix().into();
122
123                    // Shift the high bits of the account ID such that they are layed out as:
124                    // [34 zero bits | remaining high bits (30 bits)].
125                    let high_bits = prefix_id >> 34;
126
127                    // This is equivalent to the following layout, interpreted as a u32:
128                    // [2 zero bits | remaining high bits (30 bits)].
129                    // The two most significant zero bits match the tag we need for network
130                    // execution.
131                    Ok(Self(high_bits as u32))
132                }
133            },
134        }
135    }
136
137    /// Returns a new [NoteTag] instantiated for a custom use case which requires a public note.
138    ///
139    /// The public use_case tag requires a [NoteType::Public] note.
140    ///
141    /// The two high bits are set to the `b10` or `b01` depending on the execution hint, the next 14
142    /// bits are set to the `use_case_id`, and the low 16 bits are set to `payload`.
143    ///
144    /// # Errors
145    ///
146    /// - If `use_case_id` is larger than or equal to $2^{14}$.
147    pub fn for_public_use_case(
148        use_case_id: u16,
149        payload: u16,
150        execution: NoteExecutionMode,
151    ) -> Result<Self, NoteError> {
152        if (use_case_id >> 14) != 0 {
153            return Err(NoteError::NoteTagUseCaseTooLarge(use_case_id));
154        }
155
156        let execution_bits = match execution {
157            NoteExecutionMode::Local => PUBLIC_USECASE, // high bits set to `0b10`
158            NoteExecutionMode::Network => 0x40000000,   // high bits set to `0b01`
159        };
160
161        let use_case_bits = (use_case_id as u32) << 16;
162        let payload_bits = payload as u32;
163
164        Ok(Self(execution_bits | use_case_bits | payload_bits))
165    }
166
167    /// Returns a new [NoteTag] instantiated for a custom local use case.
168    ///
169    /// The local use_case tag is the only tag type that allows for [NoteType::Private] notes.
170    ///
171    /// The two high bits are set to the `b11`, the next 14 bits are set to the `use_case_id`, and
172    /// the low 16 bits are set to `payload`.
173    ///
174    /// # Errors
175    ///
176    /// - If `use_case_id` is larger than or equal to 2^14.
177    pub fn for_local_use_case(use_case_id: u16, payload: u16) -> Result<Self, NoteError> {
178        if (use_case_id >> NoteTag::MAX_USE_CASE_ID_EXPONENT) != 0 {
179            return Err(NoteError::NoteTagUseCaseTooLarge(use_case_id));
180        }
181
182        let execution_bits = LOCAL_EXECUTION_WITH_ALL_NOTE_TYPES_ALLOWED;
183        let use_case_bits = (use_case_id as u32) << 16;
184        let payload_bits = payload as u32;
185
186        Ok(Self(execution_bits | use_case_bits | payload_bits))
187    }
188
189    // PUBLIC ACCESSORS
190    // --------------------------------------------------------------------------------------------
191
192    /// Returns true if the note is intended for execution by a specific account.
193    ///
194    /// A note is intended for execution by a single account if the first two bits are zeros
195    pub fn is_single_target(&self) -> bool {
196        let first_2_bit = self.0 >> 30;
197        first_2_bit == 0b00
198    }
199
200    /// Returns note execution mode defined by this tag.
201    ///
202    /// If the most significant bit of the tag is 0 the note is intended for local execution;
203    /// otherwise, the note is intended for network execution.
204    pub fn execution_mode(&self) -> NoteExecutionMode {
205        let first_bit = self.0 >> 31;
206
207        if first_bit == (LOCAL_EXECUTION as u32) {
208            NoteExecutionMode::Local
209        } else {
210            NoteExecutionMode::Network
211        }
212    }
213
214    /// Returns the inner u32 value of this tag.
215    pub fn inner(&self) -> u32 {
216        self.0
217    }
218
219    // UTILITY METHODS
220    // --------------------------------------------------------------------------------------------
221
222    /// Returns an error if this tag is not consistent with the specified note type, and self
223    /// otherwise.
224    pub fn validate(&self, note_type: NoteType) -> Result<Self, NoteError> {
225        if self.execution_mode() == NoteExecutionMode::Network && note_type != NoteType::Public {
226            return Err(NoteError::NetworkExecutionRequiresPublicNote(note_type));
227        }
228
229        let is_public_use_case = (self.0 & 0xc0000000) == PUBLIC_USECASE;
230        if is_public_use_case && note_type != NoteType::Public {
231            Err(NoteError::PublicUseCaseRequiresPublicNote(note_type))
232        } else {
233            Ok(*self)
234        }
235    }
236}
237
238impl fmt::Display for NoteTag {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        write!(f, "{}", self.0)
241    }
242}
243
244// CONVERSIONS INTO NOTE TAG
245// ================================================================================================
246
247impl From<u32> for NoteTag {
248    fn from(value: u32) -> Self {
249        Self(value)
250    }
251}
252
253impl TryFrom<u64> for NoteTag {
254    type Error = TryFromIntError;
255
256    fn try_from(value: u64) -> Result<Self, Self::Error> {
257        Ok(Self(value.try_into()?))
258    }
259}
260
261impl TryFrom<Felt> for NoteTag {
262    type Error = TryFromIntError;
263
264    fn try_from(value: Felt) -> Result<Self, Self::Error> {
265        Ok(Self(value.as_int().try_into()?))
266    }
267}
268
269// CONVERSIONS FROM NOTE TAG
270// ================================================================================================
271
272impl From<NoteTag> for u32 {
273    fn from(value: NoteTag) -> Self {
274        value.0
275    }
276}
277
278impl From<NoteTag> for u64 {
279    fn from(value: NoteTag) -> Self {
280        value.0 as u64
281    }
282}
283
284impl From<NoteTag> for Felt {
285    fn from(value: NoteTag) -> Self {
286        Felt::from(value.0)
287    }
288}
289
290// SERIALIZATION
291// ================================================================================================
292
293impl Serializable for NoteTag {
294    fn write_into<W: ByteWriter>(&self, target: &mut W) {
295        self.0.write_into(target);
296    }
297}
298
299impl Deserializable for NoteTag {
300    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
301        let tag = u32::read_from(source)?;
302        Ok(Self(tag))
303    }
304}
305
306// TESTS
307// ================================================================================================
308
309#[cfg(test)]
310mod tests {
311    use assert_matches::assert_matches;
312
313    use super::{NoteExecutionMode, NoteTag};
314    use crate::{
315        account::AccountId,
316        note::NoteType,
317        testing::account_id::{
318            ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN,
319            ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2,
320            ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_3, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN,
321            ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN_1,
322            ACCOUNT_ID_OFF_CHAIN_SENDER, ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN,
323            ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN_2,
324            ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN,
325            ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN,
326            ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2, ACCOUNT_ID_SENDER,
327        },
328        NoteError,
329    };
330
331    #[test]
332    fn test_from_account_id() {
333        let off_chain_accounts = [
334            AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(),
335            AccountId::try_from(ACCOUNT_ID_OFF_CHAIN_SENDER).unwrap(),
336            AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN).unwrap(),
337            AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN).unwrap(),
338            AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN).unwrap(),
339        ];
340        let on_chain_accounts = [
341            AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN).unwrap(),
342            AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN_2).unwrap(),
343            AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN).unwrap(),
344            AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2).unwrap(),
345            AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(),
346            AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1).unwrap(),
347            AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2).unwrap(),
348            AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_3).unwrap(),
349            AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(),
350            AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN_1).unwrap(),
351        ];
352
353        for off_chain in off_chain_accounts {
354            assert_matches!(
355                NoteTag::from_account_id(off_chain, NoteExecutionMode::Network).unwrap_err(),
356                NoteError::NetworkExecutionRequiresOnChainAccount,
357                "Tag generation must fail if network execution and off-chain account ID are mixed"
358            )
359        }
360
361        for on_chain in on_chain_accounts {
362            let tag = NoteTag::from_account_id(on_chain, NoteExecutionMode::Network)
363                .expect("tag generation must work with network execution and on-chain account ID");
364            assert!(tag.is_single_target());
365            assert_eq!(tag.execution_mode(), NoteExecutionMode::Network);
366
367            tag.validate(NoteType::Public)
368                .expect("network execution should require notes to be public");
369            assert_matches!(
370                tag.validate(NoteType::Private),
371                Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private))
372            );
373            assert_matches!(
374                tag.validate(NoteType::Encrypted),
375                Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted))
376            );
377        }
378
379        for off_chain in off_chain_accounts {
380            let tag = NoteTag::from_account_id(off_chain, NoteExecutionMode::Local)
381                .expect("tag generation must work with local execution and off-chain account ID");
382            assert!(!tag.is_single_target());
383            assert_eq!(tag.execution_mode(), NoteExecutionMode::Local);
384
385            tag.validate(NoteType::Public)
386                .expect("local execution should support public notes");
387            tag.validate(NoteType::Private)
388                .expect("local execution should support private notes");
389            tag.validate(NoteType::Encrypted)
390                .expect("local execution should support encrypted notes");
391        }
392
393        for on_chain in on_chain_accounts {
394            let tag = NoteTag::from_account_id(on_chain, NoteExecutionMode::Local)
395                .expect("Tag generation must work with local execution and on-chain account ID");
396            assert!(!tag.is_single_target());
397            assert_eq!(tag.execution_mode(), NoteExecutionMode::Local);
398
399            tag.validate(NoteType::Public)
400                .expect("local execution should support public notes");
401            tag.validate(NoteType::Private)
402                .expect("local execution should support private notes");
403            tag.validate(NoteType::Encrypted)
404                .expect("local execution should support encrypted notes");
405        }
406    }
407
408    #[test]
409    fn test_from_account_id_values() {
410        // Off-Chain Account ID with the following bit pattern in the first and second byte:
411        // 0b11001100_01010101
412        //   ^^^^^^^^ ^^^^^^  <- these are the 14 bits used in the tag.
413        const OFF_CHAIN_INT: u128 = ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN
414            | 0x0055_0000_0000_0000_0000_0000_0000_0000;
415        let off_chain = AccountId::try_from(OFF_CHAIN_INT).unwrap();
416
417        // Expected off-chain tag with LOCAL_EXECUTION_WITH_ALL_NOTE_TYPES_ALLOWED.
418        let expected_off_chain_local_tag = NoteTag(0b11110011_00010101_00000000_00000000);
419
420        // On-Chain Account ID with the following bit pattern in the first and second byte:
421        // 0b10101010_01010101_11001100_01110111
422        //   ^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^  <- 30 bits of the network tag.
423        //   ^^^^^^^^ ^^^^^^  <- 14 bits of the local tag.
424        let on_chain_int = ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN
425            | 0x0055_cc77_0000_0000_0000_0000_0000_0000;
426        let on_chain = AccountId::try_from(on_chain_int).unwrap();
427
428        // Expected on-chain tag with LOCAL_EXECUTION_WITH_ALL_NOTE_TYPES_ALLOWED.
429        let expected_on_chain_local_tag = NoteTag(0b11101010_10010101_00000000_00000000);
430
431        // Expected on-chain tag with leading 00 tag bits for network execution.
432        let expected_on_chain_network_tag = NoteTag(0b00101010_10010101_01110011_00011101);
433
434        assert_eq!(
435            NoteTag::from_account_id(on_chain, NoteExecutionMode::Network).unwrap(),
436            expected_on_chain_network_tag,
437        );
438        assert_matches!(
439            NoteTag::from_account_id(off_chain, NoteExecutionMode::Network),
440            Err(NoteError::NetworkExecutionRequiresOnChainAccount)
441        );
442
443        assert_eq!(
444            NoteTag::from_account_id(off_chain, NoteExecutionMode::Local).unwrap(),
445            expected_off_chain_local_tag,
446        );
447
448        // We expect the 16th most significant bit to be cut off.
449        assert_eq!(
450            NoteTag::from_account_id(on_chain, NoteExecutionMode::Local).unwrap(),
451            expected_on_chain_local_tag,
452        );
453    }
454
455    #[test]
456    fn test_for_public_use_case() {
457        // NETWORK
458        // ----------------------------------------------------------------------------------------
459        let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionMode::Network).unwrap();
460        assert_eq!(tag, NoteTag(0b01000000_00000000_00000000_00000000));
461
462        tag.validate(NoteType::Public).unwrap();
463
464        assert_matches!(
465            tag.validate(NoteType::Private).unwrap_err(),
466            NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private)
467        );
468        assert_matches!(
469            tag.validate(NoteType::Encrypted).unwrap_err(),
470            NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted)
471        );
472
473        let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionMode::Network).unwrap();
474        assert_eq!(tag, NoteTag(0b01000000_00000001_00000000_00000000));
475
476        let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionMode::Network).unwrap();
477        assert_eq!(tag, NoteTag(0b01000000_00000000_00000000_00000001));
478
479        let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionMode::Network).unwrap();
480        assert_eq!(tag, NoteTag(0b01100000_00000000_00000000_00000000));
481
482        // LOCAL
483        // ----------------------------------------------------------------------------------------
484        let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionMode::Local).unwrap();
485        assert_eq!(tag, NoteTag(0b10000000_00000000_00000000_00000000));
486
487        tag.validate(NoteType::Public).unwrap();
488        assert_matches!(
489            tag.validate(NoteType::Private).unwrap_err(),
490            NoteError::PublicUseCaseRequiresPublicNote(NoteType::Private)
491        );
492        assert_matches!(
493            tag.validate(NoteType::Encrypted).unwrap_err(),
494            NoteError::PublicUseCaseRequiresPublicNote(NoteType::Encrypted)
495        );
496
497        let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionMode::Local).unwrap();
498        assert_eq!(tag, NoteTag(0b10000000_00000000_00000000_00000001));
499
500        let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionMode::Local).unwrap();
501        assert_eq!(tag, NoteTag(0b10000000_00000001_00000000_00000000));
502
503        let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionMode::Local).unwrap();
504        assert_eq!(tag, NoteTag(0b10100000_00000000_00000000_00000000));
505
506        assert_matches!(
507          NoteTag::for_public_use_case(1 << 15, 0b0, NoteExecutionMode::Local).unwrap_err(),
508          NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 15
509        );
510        assert_matches!(
511          NoteTag::for_public_use_case(1 << 14, 0b0, NoteExecutionMode::Local).unwrap_err(),
512          NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 14
513        );
514    }
515
516    #[test]
517    fn test_for_private_use_case() {
518        let tag = NoteTag::for_local_use_case(0b0, 0b0).unwrap();
519        assert_eq!(tag, NoteTag(0b11000000_00000000_00000000_00000000));
520
521        tag.validate(NoteType::Public)
522            .expect("local execution should support public notes");
523        tag.validate(NoteType::Private)
524            .expect("local execution should support private notes");
525        tag.validate(NoteType::Encrypted)
526            .expect("local execution should support encrypted notes");
527
528        let tag = NoteTag::for_local_use_case(0b0, 0b1).unwrap();
529        assert_eq!(tag, NoteTag(0b11000000_00000000_00000000_00000001));
530
531        let tag = NoteTag::for_local_use_case(0b1, 0b0).unwrap();
532        assert_eq!(tag, NoteTag(0b11000000_00000001_00000000_00000000));
533
534        let tag = NoteTag::for_local_use_case(1 << 13, 0b0).unwrap();
535        assert_eq!(tag, NoteTag(0b11100000_00000000_00000000_00000000));
536
537        assert_matches!(
538          NoteTag::for_local_use_case(1 << 15, 0b0).unwrap_err(),
539          NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 15
540        );
541        assert_matches!(
542          NoteTag::for_local_use_case(1 << 14, 0b0).unwrap_err(),
543          NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 14
544        );
545    }
546}