Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- [BREAKING] `NoteScript::from_parts` and `TransactionScript::from_parts` now return a `Result` instead of panicking when the specified entrypoint is not in the provided MAST forest ([#3548](https://github.com/0xMiden/protocol/pull/3548)).
- [BREAKING] Sorted the procedures of `AccountCode` after the authentication procedure at index 0, making the account code commitment independent of the order in which components are provided ([#2961](https://github.com/0xMiden/protocol/pull/3565)).
- The transaction kernel now validates that a new account's procedures are sorted and unique ([#3567](https://github.com/0xMiden/protocol/pull/3567)).
- The transaction kernel now validates the version and reserved bit of every input note's metadata ([#3742](https://github.com/0xMiden/protocol/pull/3742)).
Comment thread
PhilippGackstatter marked this conversation as resolved.
Outdated
- [BREAKING] Renamed the fungible asset amount extraction procedures so the unsuffixed name is the validating one ([#3576](https://github.com/0xMiden/protocol/pull/3576)):
- `miden::protocol::asset::fungible_value_into_amount` -> `fungible_value_into_amount_unchecked`.
- `miden::standards::assets::fungible_asset::value_into_amount` to `value_into_amount_unchecked`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use {ASSET_SIZE} from miden::tx_kernel_core::asset
use {NOTE_MEM_SIZE, WORD_NUM_ELEMENTS} from miden::tx_kernel_core::constants
use miden::tx_kernel_core::memory

pub use {ATTACHMENT_SCHEME_NONE, MAX_ATTACHMENT_SCHEME, MAX_ATTACHMENT_TOTAL_WORDS, MAX_ATTACHMENT_WORDS, NOTE_METADATA_VERSION_1, NOTE_TYPE_PRIVATE, NOTE_TYPE_PUBLIC}
pub use {ATTACHMENT_SCHEME_NONE, MAX_ATTACHMENT_SCHEME, MAX_ATTACHMENT_TOTAL_WORDS, MAX_ATTACHMENT_WORDS, NOTE_METADATA_VERSION_1, NOTE_TYPE_PRIVATE, NOTE_TYPE_PUBLIC, validate_metadata, metadata_into_version}
from miden::protocol_utils::note

# ERRORS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use {EMPTY_SMT_ROOT, MAX_ASSETS_PER_NOTE, MAX_INPUT_NOTES_PER_TX, MAX_NOTE_STORA
use miden::tx_kernel_core::memory
use {BLOCK_DATA_SECTION_OFFSET, INPUT_VAULT_ROOT_PTR, KERNEL_PROCEDURES_PTR, PARTIAL_BLOCKCHAIN_PTR}
from miden::tx_kernel_core::memory
use miden::tx_kernel_core::note

# CONSTS
# =================================================================================================
Expand Down Expand Up @@ -631,7 +632,8 @@ proc process_note_args
# => [note_ptr]
end

#! Computes the note metadata commitment from metadata word and attachments commitment.
#! Validates the note metadata and computes its commitment from the metadata word and the
#! attachments commitment.
#!
#! Inputs: [note_ptr]
#! Outputs: [NOTE_METADATA_COMMITMENT]
Expand All @@ -640,7 +642,11 @@ end
#! - note_ptr is the memory location for the input note.
#! - NOTE_METADATA_COMMITMENT is the commitment to the note metadata computed as
#! `hash(NOTE_METADATA || NOTE_ATTACHMENTS_COMMITMENT)`.
proc compute_note_metadata_commitment
#!
#! Panics if:
#! - the metadata encodes an unknown version.
#! - the metadata has its reserved bit set.
proc process_note_metadata
# load the attachments commitment onto the stack
dup exec.memory::get_input_note_attachments_commitment
# => [NOTE_ATTACHMENTS_COMMITMENT, note_ptr]
Expand All @@ -649,6 +655,10 @@ proc compute_note_metadata_commitment
movup.4 exec.memory::get_input_note_metadata
# => [NOTE_METADATA, NOTE_ATTACHMENTS_COMMITMENT]

# the version defines how the metadata is decoded, so assert it before anything reads it
dupw exec.note::validate_metadata
# => [NOTE_METADATA, NOTE_ATTACHMENTS_COMMITMENT]

exec.poseidon2::merge
# => [NOTE_METADATA_COMMITMENT]
end
Expand Down Expand Up @@ -943,11 +953,11 @@ proc process_input_note
dup exec.add_input_note_assets_to_vault
# => [note_ptr, NULLIFIER, CAPACITY]

# note metadata commitment
# note metadata
# ---------------------------------------------------------------------------------------------

# compute NOTE_METADATA_COMMITMENT
dup exec.compute_note_metadata_commitment
# validate the metadata and compute NOTE_METADATA_COMMITMENT
dup exec.process_note_metadata
# => [NOTE_METADATA_COMMITMENT, note_ptr, NULLIFIER, CAPACITY]

# note details commitment
Expand Down
89 changes: 86 additions & 3 deletions crates/miden-protocol/asm/protocol_utils/src/note.masm
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
# ERRORS
# =================================================================================================

const ERR_NOTE_METADATA_UNSUPPORTED_VERSION = "note metadata has an unsupported version"

const ERR_NOTE_METADATA_NON_ZERO_RESERVED_BIT = "note metadata has a non-zero reserved bit"

# CONSTANTS
# =================================================================================================

# The maximum number of storage values associated with a single note.
pub const MAX_NOTE_STORAGE_ITEMS = 1024

# Note type constants. These encode the note type in the lower byte of the metadata.
# See NoteType in the Rust protocol crate for details.

#! Version 1 of the note metadata encoding.
pub const NOTE_METADATA_VERSION_1 = 1

#! The mask for the version bits in the first felt of the note metadata.
const NOTE_METADATA_VERSION_MASK = 0x3f # 0b0011_1111

#! The mask for the reserved bit in the first felt of the note metadata.
const NOTE_METADATA_RESERVED_BIT_MASK = 0x80 # 0b1000_0000

# Note type constants. These encode the note type in the lower byte of the metadata.
# See NoteType in the Rust protocol crate for details.

#! The note type of private notes.
pub const NOTE_TYPE_PRIVATE = 0

Expand All @@ -27,3 +40,73 @@ pub const MAX_ATTACHMENT_TOTAL_WORDS = 512

#! The reserved value to signal a `None` note attachment scheme.
pub const ATTACHMENT_SCHEME_NONE = 1

# PROCEDURES
# =================================================================================================

#! Extracts the version from the provided metadata.
#!
#! Inputs: [METADATA]
#! Outputs: [version]
#!
#! Where:
#! - METADATA is the metadata of a note.
#! - version is the version of the note metadata encoding.
pub proc metadata_into_version
movdn.3 drop drop drop
# => [sender_id_suffix_type_version]

u32split swap drop
# => [lo32]

u32and.NOTE_METADATA_VERSION_MASK
# => [version]
end

#! Extracts the reserved bit from the provided metadata.
#!
#! Inputs: [METADATA]
#! Outputs: [reserved_bit]
#!
#! Where:
#! - METADATA is the metadata of a note.
#! - reserved_bit is 1 if the reserved bit is set, 0 otherwise.
proc metadata_into_reserved_bit
movdn.3 drop drop drop
# => [sender_id_suffix_type_version]

u32split swap drop
# => [lo32]

u32and.NOTE_METADATA_RESERVED_BIT_MASK neq.0
# => [reserved_bit]
end

#! Validates that note metadata is well formed and consumes it.
#!
#! WARNING: This procedure should not be exposed to or called from user code (e.g. miden::protocol
#! or miden::standards) as this would make the calling code only accept note metadata version 1.
#!
#! Inputs: [METADATA]
#! Outputs: []
#!
#! Where:
#! - METADATA is the metadata of a note.
#!
#! Panics if:
#! - the metadata encodes an unknown version.
#! - the metadata has its reserved bit set.
pub proc validate_metadata
# the version defines how the rest of the metadata is decoded, so assert it first
dupw exec.metadata_into_version
# => [version, METADATA]

eq.NOTE_METADATA_VERSION_1 assert.err=ERR_NOTE_METADATA_UNSUPPORTED_VERSION
# => [METADATA]

exec.metadata_into_reserved_bit
# => [reserved_bit]

assertz.err=ERR_NOTE_METADATA_NON_ZERO_RESERVED_BIT
# => []
end
83 changes: 83 additions & 0 deletions crates/miden-testing/src/kernel_tests/tx/test_note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ use miden_protocol::asset::FungibleAsset;
use miden_protocol::crypto::dsa::falcon512_poseidon2::SecretKey;
use miden_protocol::crypto::rand::{FeltRng, RandomCoin};
use miden_protocol::errors::MasmError;
use miden_protocol::errors::protocol::{
ERR_NOTE_METADATA_NON_ZERO_RESERVED_BIT,
ERR_NOTE_METADATA_UNSUPPORTED_VERSION,
};
use miden_protocol::note::{
Note,
NoteAssets,
Expand Down Expand Up @@ -45,6 +49,7 @@ use crate::{
MockChain,
MockTransaction,
TestTransactionBuilder,
assert_execution_error,
assert_transaction_executor_error,
};

Expand Down Expand Up @@ -693,3 +698,81 @@ async fn test_find_attachment_idx(

Ok(())
}

#[rstest::rstest]
#[case::private(NoteType::Private)]
#[case::public(NoteType::Public)]
#[tokio::test]
async fn test_metadata_into_version(#[case] note_type: NoteType) -> anyhow::Result<()> {
let sender = AccountId::try_from(ACCOUNT_ID_SENDER)?;
let partial_metadata = PartialNoteMetadata::new(sender, note_type);
let metadata = NoteMetadata::new(partial_metadata, &NoteAttachments::default());
let metadata_word = metadata.to_metadata_word();

let code = format!(
"
use miden::tx_kernel_core::note

begin
push.{metadata_word}
exec.note::metadata_into_version
# => [version, pad(16)]

# truncate the stack
swap drop
end
",
);

let exec_output = CodeExecutor::with_default_host().run(&code).await?;

// The metadata encoder only ever writes version 1.
assert_eq!(exec_output.get_stack_element(0), Felt::from(1u8));

Ok(())
}

/// Tests that `validate_metadata` accepts version 1 metadata and rejects metadata with an unknown
/// version or with its reserved bit set.
#[rstest::rstest]
#[case::valid_private(0b0000_0001, None)]
#[case::valid_public(0b0100_0001, None)]
#[case::version_zero(0b0000_0000, Some(ERR_NOTE_METADATA_UNSUPPORTED_VERSION))]
#[case::unknown_version(0b0000_0010, Some(ERR_NOTE_METADATA_UNSUPPORTED_VERSION))]
#[case::reserved_bit_set(0b1000_0001, Some(ERR_NOTE_METADATA_NON_ZERO_RESERVED_BIT))]
#[tokio::test]
async fn test_validate_note_metadata(
#[case] metadata_byte: u8,
#[case] expected_err: Option<MasmError>,
) -> anyhow::Result<()> {
let sender = AccountId::try_from(ACCOUNT_ID_SENDER)?;
let partial_metadata = PartialNoteMetadata::new(sender, NoteType::Public);
let metadata = NoteMetadata::new(partial_metadata, &NoteAttachments::default());
let mut metadata_word = metadata.to_metadata_word();

// The lower byte of the sender ID suffix is zero by construction, so it can be replaced by the
// byte holding the version, note type and reserved bit.
metadata_word[0] = sender.suffix() + Felt::from(metadata_byte);

let code = format!(
"
use miden::tx_kernel_core::note

begin
push.{metadata_word}
exec.note::validate_metadata
end
",
);

let exec_result = CodeExecutor::with_default_host().run(&code).await;

match expected_err {
Some(err) => assert_execution_error!(exec_result, err),
None => {
exec_result.context("version 1 metadata should be accepted")?;
},
}

Ok(())
}
Loading