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::NetworkExecutionRequiresOnChainAccount)
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 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 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 let expected_off_chain_local_tag = NoteTag(0b11110011_00010101_00000000_00000000);
419
420 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 let expected_on_chain_local_tag = NoteTag(0b11101010_10010101_00000000_00000000);
430
431 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 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 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 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}