Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -30,6 +30,7 @@
- Fixed the multisig, guarded, non-fungible, and AggLayer faucet factories not enabling asset callbacks for faucets configured with a transfer policy ([#3547](https://github.com/0xMiden/protocol/pull/3547)).
- Documented that `authority::assert_authorized` is a no-op under `Authority::AuthControlled` ([#3500](https://github.com/0xMiden/protocol/pull/3500)).
- Fixed `FungibleFaucet::receive_and_burn` treating a non-fungible asset issued by the same account as a fungible burn, which reduced `token_supply` without burning any fungible supply; the asset is now validated with the new `miden::standards::assets::fungible_asset::validate` procedure ([#3553](https://github.com/0xMiden/protocol/pull/3553)).
- The canonical encoding's reserved account-header and storage-slot elements are now asserted to be zero at account creation, in both the transaction kernel and the Rust `try_from_elements` parsers ([#3599](https://github.com/0xMiden/protocol/issues/3599)).
- Fixed the authentication procedure not ending up at index 0 of an account's code when its MAST root was already exported by another component ([#3566](https://github.com/0xMiden/protocol/pull/3566)).
- [BREAKING] Foreign procedure invocation now requires the provided procedure root to be part of the foreign account's code, so a caller can no longer execute arbitrary code under a foreign account's identity ([#3575](https://github.com/0xMiden/protocol/pull/3575)).
- Fixed `PrivateOutputNote` construction and deserialization accepting attachment data that is not committed by the note header ([#3556](https://github.com/0xMiden/protocol/pull/3579)).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ const ERR_ACCOUNT_STORAGE_SLOTS_MUST_BE_SORTED_AND_UNIQUE =
const ERR_ACCOUNT_STORAGE_SLOT_TYPE_IS_INVALID =
"account storage slot has an unsupported type"

const ERR_ACCOUNT_STORAGE_SLOT_RESERVED_ELEMENT_MUST_BE_ZERO =
"reserved element of an account storage slot must be zero"

const ERR_ACCOUNT_PROCEDURES_MUST_BE_SORTED_AND_UNIQUE =
"account procedures following the authentication procedure must be unique and sorted in ascending order"

Expand Down Expand Up @@ -948,8 +951,9 @@ pub proc validate_seed
# => []
end

#! Validates that slot IDs are sorted in ascending order, that slot IDs are unique, and that every
#! slot's type belongs to the set of supported types (value or map).
#! Validates that slot IDs are sorted in ascending order, that slot IDs are unique, that every
#! slot's type belongs to the set of supported types (value or map), and that the reserved element
#! of every slot record is zero.
#!
#! Validation should only ever happen on the storage of the native account.
#!
Expand All @@ -960,12 +964,13 @@ end
#! - each slot's ID is not strictly less than the next slot's ID.
#! - this ensures sorting and uniqueness among slot IDs.
#! - any slot has a type that is not a supported storage slot type.
#! - any slot's reserved element is not zero.
pub proc validate_storage
exec.memory::get_native_num_storage_slots
# => [num_slots]

# iterate over all slots from the last one down to slot 0, validating each slot's type and
# comparing each slot's ID against the previous slot's ID
# reserved element and comparing each slot's ID against the previous slot's ID
dup neq.0
# => [should_loop, num_slots]

Expand All @@ -984,6 +989,11 @@ pub proc validate_storage
assert.err=ERR_ACCOUNT_STORAGE_SLOT_TYPE_IS_INVALID
# => [curr_slot_idx]

# assert the reserved element of the slot record is zero
dup exec.get_native_slot_reserved eq.0
assert.err=ERR_ACCOUNT_STORAGE_SLOT_RESERVED_ELEMENT_MUST_BE_ZERO
# => [curr_slot_idx]

# compare the slot's ID against the previous slot's ID, unless this is slot 0
dup neq.0
# => [has_prev_slot, curr_slot_idx]
Expand Down Expand Up @@ -1581,6 +1591,31 @@ pub proc get_native_slot_id
# => [slot_id_suffix, slot_id_prefix]
end

#! Gets the reserved element of the storage slot at the provided index for the native account.
#!
#! WARNING: The index must be in bounds.
#!
#! Inputs: [index]
#! Outputs: [reserved]
#!
#! Where:
#! - index is the index of the slot.
#! - reserved is the reserved element of the slot record, which must be zero per the canonical
#! encoding.
proc get_native_slot_reserved
# convert the index into a memory offset
mul.ACCOUNT_STORAGE_SLOT_DATA_LENGTH
# => [offset]

exec.memory::get_native_account_active_storage_slots_ptr
add
# => [slot_ptr]

# the reserved element is the first element of the slot record
mem_load
# => [reserved]
end

#! Gets the slot ID of the storage slot pointed at by slot_ptr.
#!
#! WARNING: The slot_ptr must be valid.
Expand Down
15 changes: 15 additions & 0 deletions crates/miden-protocol/asm/kernels/transaction-core/src/memory.masm
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ pub const ACCOUNT_DATA_LENGTH = 8192
# The offsets at which the account data is stored relative to the start of the account data segment.
pub const ACCT_NONCE_OFFSET = 0
pub const ACCT_ID_AND_NONCE_OFFSET = 0
# Reserved element of the account header word; must always be zero per the canonical encoding.
const ACCT_RESERVED_OFFSET = 1
pub const ACCT_ID_SUFFIX_OFFSET = 2
pub const ACCT_ID_PREFIX_OFFSET = 3
const ACCT_VAULT_ROOT_OFFSET = 4
Expand Down Expand Up @@ -1104,6 +1106,19 @@ pub proc get_native_account_nonce
mem_load
end

#! Returns the reserved element of the native account's header word.
#!
#! Inputs: []
#! Outputs: [reserved]
#!
#! Where:
#! - reserved is the reserved element of the account header word, which must always be zero per the
#! canonical encoding.
pub proc get_native_account_reserved
push.NATIVE_ACCOUNT_DATA_PTR add.ACCT_RESERVED_OFFSET
mem_load
end

#! Sets the nonce of the native account.
#!
#! Inputs: [nonce]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ const ERR_PROLOGUE_INPUT_NOTES_COMMITMENT_MISMATCH =

const ERR_PROLOGUE_NEW_ACCOUNT_NONCE_MUST_BE_ZERO = "new account must have a zero nonce"

const ERR_PROLOGUE_NEW_ACCOUNT_RESERVED_HEADER_ELEMENT_MUST_BE_ZERO =
"reserved element of the new account header word must be zero"

const ERR_PROLOGUE_NUMBER_OF_NOTE_STORAGE_ITEMS_EXCEEDED_LIMIT =
"number of note storage items exceeded the maximum limit of 1024"

Expand Down Expand Up @@ -309,6 +312,11 @@ proc validate_new_account
exec.memory::get_account_nonce eq.0 assert.err=ERR_PROLOGUE_NEW_ACCOUNT_NONCE_MUST_BE_ZERO
# => []

# Assert the reserved element of the account header word is 0
exec.memory::get_native_account_reserved eq.0
assert.err=ERR_PROLOGUE_NEW_ACCOUNT_RESERVED_HEADER_ELEMENT_MUST_BE_ZERO
# => []

# Assert the initial vault is empty
# ---------------------------------------------------------------------------------------------
# get the account vault root
Expand Down
26 changes: 26 additions & 0 deletions crates/miden-protocol/src/account/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::transaction::memory::{
ACCT_ID_PREFIX_IDX,
ACCT_ID_SUFFIX_IDX,
ACCT_NONCE_IDX,
ACCT_RESERVED_IDX,
ACCT_STORAGE_COMMITMENT_OFFSET,
ACCT_VAULT_ROOT_OFFSET,
MemoryOffset,
Expand Down Expand Up @@ -81,6 +82,12 @@ impl AccountHeader {
)
.map_err(AccountError::FinalAccountHeaderIdParsingFailed)?;
let nonce = elements[ACCT_ID_AND_NONCE_OFFSET as usize + ACCT_NONCE_IDX];

let reserved = elements[ACCT_ID_AND_NONCE_OFFSET as usize + ACCT_RESERVED_IDX];
if reserved != Felt::ZERO {
return Err(AccountError::HeaderReservedElementNotZero(reserved));
}

let vault_root = parse_word(elements, ACCT_VAULT_ROOT_OFFSET)
.expect("we should have sliced off exactly 4 bytes");
let storage_commitment = parse_word(elements, ACCT_STORAGE_COMMITMENT_OFFSET)
Expand Down Expand Up @@ -243,13 +250,16 @@ fn parse_word(data: &[Felt], offset: MemoryOffset) -> Result<Word, WordError> {

#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use miden_core::Felt;

use super::AccountHeader;
use crate::Word;
use crate::account::StorageSlotContent;
use crate::account::tests::build_account;
use crate::asset::FungibleAsset;
use crate::errors::AccountError;
use crate::transaction::memory::{ACCT_ID_AND_NONCE_OFFSET, ACCT_RESERVED_IDX};
use crate::utils::serde::{Deserializable, Serializable};

#[test]
Expand All @@ -266,4 +276,20 @@ mod tests {
let deserialized_header = AccountHeader::read_from_bytes(&header_bytes).unwrap();
assert_eq!(deserialized_header, account_header);
}

#[test]
fn test_try_from_elements_rejects_non_zero_reserved() {
let account = build_account(
vec![FungibleAsset::mock(99)],
Felt::from(1_u32),
vec![StorageSlotContent::Value(Word::from([1, 2, 3, 4u32]))],
);
let account_header: AccountHeader = account.into();

let mut elements = account_header.to_elements();
elements[ACCT_ID_AND_NONCE_OFFSET as usize + ACCT_RESERVED_IDX] = Felt::ONE;

let err = AccountHeader::try_from_elements(&elements).unwrap_err();
assert_matches!(err, AccountError::HeaderReservedElementNotZero(value) if value == Felt::ONE);
}
}
14 changes: 14 additions & 0 deletions crates/miden-protocol/src/account/storage/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ impl AccountStorageHeader {

let mut slots = Vec::new();
for chunk in elements.chunks_exact(StorageSlot::NUM_ELEMENTS) {
// The first element of each slot record is reserved and must be zero.
if chunk[0] != Felt::ZERO {
return Err(AccountError::StorageSlotReservedElementNotZero(chunk[0]));
}

// Parse slot type from second element.
let slot_type_felt = chunk[1];
let slot_type = slot_type_felt.try_into()?;
Expand Down Expand Up @@ -351,11 +356,13 @@ mod tests {
use alloc::collections::BTreeMap;
use alloc::string::ToString;

use assert_matches::assert_matches;
use miden_core::Felt;

use super::AccountStorageHeader;
use crate::Word;
use crate::account::{AccountStorage, StorageSlotHeader, StorageSlotName, StorageSlotType};
use crate::errors::AccountError;
use crate::testing::storage::{MOCK_MAP_SLOT, MOCK_VALUE_SLOT0, MOCK_VALUE_SLOT1};
use crate::utils::serde::{Deserializable, Serializable};

Expand Down Expand Up @@ -488,6 +495,13 @@ mod tests {
AccountStorageHeader::try_from_elements(&invalid_type_elements, &empty_slot_names)
.is_err()
);

// Test with a non-zero reserved element.
let mut reserved_elements = vec![crate::ZERO; 8];
reserved_elements[0] = Felt::ONE; // Reserved element must be zero.
let err = AccountStorageHeader::try_from_elements(&reserved_elements, &empty_slot_names)
.unwrap_err();
assert_matches!(err, AccountError::StorageSlotReservedElementNotZero(value) if value == Felt::ONE);
}

#[test]
Expand Down
4 changes: 4 additions & 0 deletions crates/miden-protocol/src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ pub enum AccountError {
FinalAccountHeaderIdParsingFailed(#[source] AccountIdError),
#[error("account header data has length {actual} but it must be of length {expected}")]
HeaderDataIncorrectLength { actual: usize, expected: usize },
#[error("reserved element of the account header must be zero but was {0}")]
HeaderReservedElementNotZero(Felt),
#[error("final nonce {new} is not strictly greater than current account nonce {current}")]
NonceMustIncrease { current: Felt, new: Felt },
#[error(
Expand Down Expand Up @@ -179,6 +181,8 @@ pub enum AccountError {
StorageSlotIdNotFound { slot_id: StorageSlotId },
#[error("storage slots must be sorted by slot ID")]
UnsortedStorageSlots,
#[error("reserved element of a storage slot must be zero but was {0}")]
StorageSlotReservedElementNotZero(Felt),
#[error("number of storage slots is {0} but max possible number is {max}", max = AccountStorage::MAX_NUM_STORAGE_SLOTS)]
StorageTooManySlots(u64),
#[error(
Expand Down
4 changes: 4 additions & 0 deletions crates/miden-protocol/src/transaction/kernel/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ pub const NATIVE_ACCT_ID_AND_NONCE_PTR: MemoryAddress =
/// The index of the account nonce within the account ID and nonce data.
pub const ACCT_NONCE_IDX: DataIndex = 0;

/// The index of the reserved element within the account ID and nonce data. This element must always
/// be zero per the canonical encoding.
pub const ACCT_RESERVED_IDX: DataIndex = 1;

/// The index of the account ID within the account ID and nonce data.
pub const ACCT_ID_SUFFIX_IDX: DataIndex = 2;
pub const ACCT_ID_PREFIX_IDX: DataIndex = 3;
Expand Down
Loading