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
10const NETWORK_EXECUTION: u8 = 0;
13const LOCAL_EXECUTION: u8 = 1;
14
15const LOCAL_EXECUTION_WITH_ALL_NOTE_TYPES_ALLOWED: u32 = 0xc000_0000;
17const PUBLIC_USECASE: u32 = 0x8000_0000;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[repr(u8)]
31pub enum NoteExecutionMode {
32 Network = NETWORK_EXECUTION,
33 Local = LOCAL_EXECUTION,
34}
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
66pub struct NoteTag(u32);
67
68impl NoteTag {
69 pub(crate) const MAX_USE_CASE_ID_EXPONENT: u8 = 14;
75
76 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 let high_bits = prefix_id >> 34;
104
105 let high_bits = high_bits as u32;
108
109 let high_bits = high_bits & 0xffff0000;
113
114 Ok(Self(high_bits | LOCAL_EXECUTION_WITH_ALL_NOTE_TYPES_ALLOWED))
116 },
117 NoteExecutionMode::Network => {
118 if !account_id.is_public() {
119 Err(NoteError::NetworkExecutionRequiresPublicAccount)
120 } else {
121 let prefix_id: u64 = account_id.prefix().into();
122
123 let high_bits = prefix_id >> 34;
126
127 Ok(Self(high_bits as u32))
132 }
133 },
134 }
135 }
136
137 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, NoteExecutionMode::Network => 0x40000000, };
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 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 pub fn is_single_target(&self) -> bool {
196 let first_2_bit = self.0 >> 30;
197 first_2_bit == 0b00
198 }
199
200 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 pub fn inner(&self) -> u32 {
216 self.0
217 }
218
219 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
244impl 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
269impl 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
290impl 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#[cfg(test)]
310mod tests {
311 use assert_matches::assert_matches;
312
313 use super::{NoteExecutionMode, NoteTag};
314 use crate::{
315 NoteError,
316 account::AccountId,
317 note::NoteType,
318 testing::account_id::{
319 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
320 ACCOUNT_ID_PRIVATE_SENDER, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
321 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
322 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3, ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
323 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
324 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
325 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
326 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2,
327 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE,
328 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_2, ACCOUNT_ID_SENDER,
329 },
330 };
331
332 #[test]
333 fn test_from_account_id() {
334 let private_accounts = [
335 AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(),
336 AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap(),
337 AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap(),
338 AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap(),
339 AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap(),
340 ];
341 let public_accounts = [
342 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(),
343 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2).unwrap(),
344 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE).unwrap(),
345 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_2).unwrap(),
346 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(),
347 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).unwrap(),
348 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2).unwrap(),
349 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3).unwrap(),
350 AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET).unwrap(),
351 AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1).unwrap(),
352 ];
353
354 for account_id in private_accounts {
355 assert_matches!(
356 NoteTag::from_account_id(account_id, NoteExecutionMode::Network).unwrap_err(),
357 NoteError::NetworkExecutionRequiresPublicAccount,
358 "Tag generation must fail if network execution and private account ID are mixed"
359 )
360 }
361
362 for account_id in public_accounts {
363 let tag = NoteTag::from_account_id(account_id, NoteExecutionMode::Network)
364 .expect("tag generation must work with network execution and public account ID");
365 assert!(tag.is_single_target());
366 assert_eq!(tag.execution_mode(), NoteExecutionMode::Network);
367
368 tag.validate(NoteType::Public)
369 .expect("network execution should require notes to be public");
370 assert_matches!(
371 tag.validate(NoteType::Private),
372 Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private))
373 );
374 assert_matches!(
375 tag.validate(NoteType::Encrypted),
376 Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted))
377 );
378 }
379
380 for account_id in private_accounts {
381 let tag = NoteTag::from_account_id(account_id, NoteExecutionMode::Local)
382 .expect("tag generation must work with local execution and private account ID");
383 assert!(!tag.is_single_target());
384 assert_eq!(tag.execution_mode(), NoteExecutionMode::Local);
385
386 tag.validate(NoteType::Public)
387 .expect("local execution should support public notes");
388 tag.validate(NoteType::Private)
389 .expect("local execution should support private notes");
390 tag.validate(NoteType::Encrypted)
391 .expect("local execution should support encrypted notes");
392 }
393
394 for account_id in public_accounts {
395 let tag = NoteTag::from_account_id(account_id, NoteExecutionMode::Local)
396 .expect("Tag generation must work with local execution and public account ID");
397 assert!(!tag.is_single_target());
398 assert_eq!(tag.execution_mode(), NoteExecutionMode::Local);
399
400 tag.validate(NoteType::Public)
401 .expect("local execution should support public notes");
402 tag.validate(NoteType::Private)
403 .expect("local execution should support private notes");
404 tag.validate(NoteType::Encrypted)
405 .expect("local execution should support encrypted notes");
406 }
407 }
408
409 #[test]
410 fn test_from_account_id_values() {
411 const PRIVATE_ACCOUNT_INT: u128 = ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE
415 | 0x0055_0000_0000_0000_0000_0000_0000_0000;
416 let private_account_id = AccountId::try_from(PRIVATE_ACCOUNT_INT).unwrap();
417
418 let expected_private_local_tag = NoteTag(0b11110011_00010101_00000000_00000000);
420
421 const PUBLIC_ACCOUNT_INT: u128 = ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE
426 | 0x0055_cc77_0000_0000_0000_0000_0000_0000;
427 let public_account_id = AccountId::try_from(PUBLIC_ACCOUNT_INT).unwrap();
428
429 let expected_public_local_tag = NoteTag(0b11101010_10010101_00000000_00000000);
431
432 let expected_public_network_tag = NoteTag(0b00101010_10010101_01110011_00011101);
434
435 assert_eq!(
436 NoteTag::from_account_id(public_account_id, NoteExecutionMode::Network).unwrap(),
437 expected_public_network_tag,
438 );
439 assert_matches!(
440 NoteTag::from_account_id(private_account_id, NoteExecutionMode::Network),
441 Err(NoteError::NetworkExecutionRequiresPublicAccount)
442 );
443
444 assert_eq!(
445 NoteTag::from_account_id(private_account_id, NoteExecutionMode::Local).unwrap(),
446 expected_private_local_tag,
447 );
448
449 assert_eq!(
451 NoteTag::from_account_id(public_account_id, NoteExecutionMode::Local).unwrap(),
452 expected_public_local_tag,
453 );
454 }
455
456 #[test]
457 fn test_for_public_use_case() {
458 let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionMode::Network).unwrap();
461 assert_eq!(tag, NoteTag(0b01000000_00000000_00000000_00000000));
462
463 tag.validate(NoteType::Public).unwrap();
464
465 assert_matches!(
466 tag.validate(NoteType::Private).unwrap_err(),
467 NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private)
468 );
469 assert_matches!(
470 tag.validate(NoteType::Encrypted).unwrap_err(),
471 NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted)
472 );
473
474 let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionMode::Network).unwrap();
475 assert_eq!(tag, NoteTag(0b01000000_00000001_00000000_00000000));
476
477 let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionMode::Network).unwrap();
478 assert_eq!(tag, NoteTag(0b01000000_00000000_00000000_00000001));
479
480 let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionMode::Network).unwrap();
481 assert_eq!(tag, NoteTag(0b01100000_00000000_00000000_00000000));
482
483 let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionMode::Local).unwrap();
486 assert_eq!(tag, NoteTag(0b10000000_00000000_00000000_00000000));
487
488 tag.validate(NoteType::Public).unwrap();
489 assert_matches!(
490 tag.validate(NoteType::Private).unwrap_err(),
491 NoteError::PublicUseCaseRequiresPublicNote(NoteType::Private)
492 );
493 assert_matches!(
494 tag.validate(NoteType::Encrypted).unwrap_err(),
495 NoteError::PublicUseCaseRequiresPublicNote(NoteType::Encrypted)
496 );
497
498 let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionMode::Local).unwrap();
499 assert_eq!(tag, NoteTag(0b10000000_00000000_00000000_00000001));
500
501 let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionMode::Local).unwrap();
502 assert_eq!(tag, NoteTag(0b10000000_00000001_00000000_00000000));
503
504 let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionMode::Local).unwrap();
505 assert_eq!(tag, NoteTag(0b10100000_00000000_00000000_00000000));
506
507 assert_matches!(
508 NoteTag::for_public_use_case(1 << 15, 0b0, NoteExecutionMode::Local).unwrap_err(),
509 NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 15
510 );
511 assert_matches!(
512 NoteTag::for_public_use_case(1 << 14, 0b0, NoteExecutionMode::Local).unwrap_err(),
513 NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 14
514 );
515 }
516
517 #[test]
518 fn test_for_private_use_case() {
519 let tag = NoteTag::for_local_use_case(0b0, 0b0).unwrap();
520 assert_eq!(tag, NoteTag(0b11000000_00000000_00000000_00000000));
521
522 tag.validate(NoteType::Public)
523 .expect("local execution should support public notes");
524 tag.validate(NoteType::Private)
525 .expect("local execution should support private notes");
526 tag.validate(NoteType::Encrypted)
527 .expect("local execution should support encrypted notes");
528
529 let tag = NoteTag::for_local_use_case(0b0, 0b1).unwrap();
530 assert_eq!(tag, NoteTag(0b11000000_00000000_00000000_00000001));
531
532 let tag = NoteTag::for_local_use_case(0b1, 0b0).unwrap();
533 assert_eq!(tag, NoteTag(0b11000000_00000001_00000000_00000000));
534
535 let tag = NoteTag::for_local_use_case(1 << 13, 0b0).unwrap();
536 assert_eq!(tag, NoteTag(0b11100000_00000000_00000000_00000000));
537
538 assert_matches!(
539 NoteTag::for_local_use_case(1 << 15, 0b0).unwrap_err(),
540 NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 15
541 );
542 assert_matches!(
543 NoteTag::for_local_use_case(1 << 14, 0b0).unwrap_err(),
544 NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 14
545 );
546 }
547}