Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0b46cc5
Add initial impl to test integration
sergerad Jan 5, 2026
7dfd8f6
Misc fixes
sergerad Jan 8, 2026
90e7c94
Add from_elements for AccountStorageHeader
sergerad Jan 8, 2026
abca24a
Add witness impl
sergerad Jan 8, 2026
38ac256
Split fn
sergerad Jan 8, 2026
a69bb1d
Update tests
sergerad Jan 8, 2026
58d5900
Changelog
sergerad Jan 9, 2026
6411474
Merge branch 'next' of github.com:0xPolygonMiden/miden-base into serg…
sergerad Jan 9, 2026
58f9cb3
Update changelog
sergerad Jan 9, 2026
dc7bfc3
Move pub util fn
sergerad Jan 9, 2026
1123941
Fix account_id_to_smt_index
sergerad Jan 9, 2026
cd19a48
impl TryFrom<Felt> for StorageSlotType
sergerad Jan 9, 2026
1a105b9
TransactionInputError::StorageHeaderNotFound
sergerad Jan 9, 2026
5000592
Seed comment
sergerad Jan 9, 2026
6d6b15e
from_elements -> try_from_elements
sergerad Jan 9, 2026
1c71706
Rm account id arg and add doc comments
sergerad Jan 9, 2026
397f674
Simplify account_id_to_smt_index
sergerad Jan 11, 2026
cbae4a4
add foreign account slot names to tx inputs
sergerad Jan 11, 2026
237dd4d
Add serde test
sergerad Jan 11, 2026
e13ae1d
Add read_vault_asset_witnesses impl
sergerad Jan 11, 2026
2e43c90
Move tests
sergerad Jan 12, 2026
5251431
Fix comment
sergerad Jan 12, 2026
bcb87d6
RM nested map for slot names
sergerad Jan 12, 2026
7ea16e3
Fix serde tx inputs
sergerad Jan 12, 2026
1e249ff
Add from felt test
sergerad Jan 12, 2026
631edda
Simplify tests
sergerad Jan 12, 2026
e9a466d
Add read_storage_map_witness impl
sergerad Jan 12, 2026
b263e80
For each slot
sergerad Jan 12, 2026
831f13d
Lint
sergerad Jan 12, 2026
436b155
Update changelog
sergerad Jan 12, 2026
0625fdf
Merge branch 'next' into sergerad-tx-inputs-foreign-acc-inputs
bobbinth Jan 13, 2026
99a9af7
chore: minor clean-up
bobbinth Jan 13, 2026
93c8e90
Add TransactionInputsExtractionError type
sergerad Jan 13, 2026
c00dd2c
Use vault_root in read_vault_asset_witnesses
sergerad Jan 13, 2026
1c41d3a
Simplify leaf index logic
sergerad Jan 13, 2026
123054c
Rm panics
sergerad Jan 13, 2026
3a0f472
Merge branch 'next' of github.com:0xPolygonMiden/miden-base into serg…
sergerad Jan 13, 2026
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 @@ -8,6 +8,7 @@
- [BREAKING] Allowed account components to share identical account code procedures ([#2164](https://github.com/0xMiden/miden-base/pull/2164)).
- Add `From<&ExecutedTransaction> for TransactionHeader` implementation ([#2178](https://github.com/0xMiden/miden-base/pull/2178)).
- Add `AccountId::parse()` helper function to parse both hex and bech32 formats ([#2223](https://github.com/0xMiden/miden-base/pull/2223)).
- Add `read_foreign_account_inputs()`, `read_vault_asset_witnesses()`, and `read_storage_map_witness()` for `TransactionInputs` ([#2246](https://github.com/0xMiden/miden-base/pull/2246)).

### Changes

Expand Down
167 changes: 166 additions & 1 deletion crates/miden-protocol/src/account/storage/header.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::string::ToString;
use alloc::vec::Vec;

Expand Down Expand Up @@ -150,6 +152,49 @@ impl AccountStorageHeader {
<Self as SequentialCommit>::to_elements(self)
}

/// Reconstructs an [`AccountStorageHeader`] from field elements with provided slot names.
///
/// The elements are expected to be groups of 8 elements per slot:
/// `[[0, slot_type, slot_id_suffix, slot_id_prefix], SLOT_VALUE]`
pub fn try_from_elements(
elements: &[Felt],
slot_names: &BTreeMap<StorageSlotId, StorageSlotName>,
) -> Result<Self, AccountError> {
if !elements.len().is_multiple_of(StorageSlot::NUM_ELEMENTS) {
return Err(AccountError::other(
"storage header elements length must be divisible by 8",
));
}

let mut slots = Vec::new();
for chunk in elements.chunks_exact(StorageSlot::NUM_ELEMENTS) {
// Parse slot type from second element.
let slot_type_felt = chunk[1];
let slot_type = slot_type_felt.try_into()?;

// Parse slot ID from third and fourth elements.
let slot_id_suffix = chunk[2];
let slot_id_prefix = chunk[3];
let parsed_slot_id = StorageSlotId::new(slot_id_suffix, slot_id_prefix);

// Retrieve slot name from the map.
let slot_name = slot_names.get(&parsed_slot_id).cloned().ok_or(AccountError::other(
format!("slot name not found for slot ID {}", parsed_slot_id),
))?;

// Parse slot value from last 4 elements.
let slot_value = Word::new([chunk[4], chunk[5], chunk[6], chunk[7]]);

let slot_header = StorageSlotHeader::new(slot_name, slot_type, slot_value);
slots.push(slot_header);
}

// Sort slots by ID.
slots.sort_by_key(|slot| slot.id());

Self::new(slots)
}

/// Returns the commitment to the [`AccountStorage`] this header represents.
pub fn to_commitment(&self) -> Word {
<Self as SequentialCommit>::to_commitment(self)
Expand Down Expand Up @@ -301,12 +346,15 @@ impl Deserializable for StorageSlotHeader {

#[cfg(test)]
mod tests {
use alloc::collections::BTreeMap;
use alloc::string::ToString;

use miden_core::Felt;
use miden_core::utils::{Deserializable, Serializable};

use super::AccountStorageHeader;
use crate::Word;
use crate::account::{AccountStorage, StorageSlotType};
use crate::account::{AccountStorage, StorageSlotHeader, StorageSlotName, StorageSlotType};
use crate::testing::storage::{MOCK_MAP_SLOT, MOCK_VALUE_SLOT0, MOCK_VALUE_SLOT1};

#[test]
Expand Down Expand Up @@ -344,4 +392,121 @@ mod tests {
// assert deserialized == storage header
assert_eq!(storage_header, deserialized);
}

#[test]
fn test_to_elements_from_elements_empty() {
// Construct empty header.
let empty_header = AccountStorageHeader::new(vec![]).unwrap();
let empty_elements = empty_header.to_elements();

// Call from_elements.
let empty_slot_names = BTreeMap::new();
let reconstructed_empty =
AccountStorageHeader::try_from_elements(&empty_elements, &empty_slot_names).unwrap();
assert_eq!(empty_header, reconstructed_empty);
}

#[test]
fn test_to_elements_from_elements_single_slot() {
// Construct single slot header.
let slot_name1 = StorageSlotName::new("test::value::slot1".to_string()).unwrap();
let slot1 = StorageSlotHeader::new(
slot_name1,
StorageSlotType::Value,
Word::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]),
);

let single_slot_header = AccountStorageHeader::new(vec![slot1.clone()]).unwrap();
let single_elements = single_slot_header.to_elements();

// Call from_elements.
let slot_names = BTreeMap::from([(slot1.id(), slot1.name().clone())]);
let reconstructed_single =
AccountStorageHeader::try_from_elements(&single_elements, &slot_names).unwrap();

assert_eq!(single_slot_header, reconstructed_single);
}

#[test]
fn test_to_elements_from_elements_multiple_slot() {
// Construct multi slot header.
let slot_name2 = StorageSlotName::new("test::map::slot2".to_string()).unwrap();
let slot_name3 = StorageSlotName::new("test::value::slot3".to_string()).unwrap();

let slot2 = StorageSlotHeader::new(
slot_name2,
StorageSlotType::Map,
Word::new([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]),
);
let slot3 = StorageSlotHeader::new(
slot_name3,
StorageSlotType::Value,
Word::new([Felt::new(9), Felt::new(10), Felt::new(11), Felt::new(12)]),
);

let mut slots = vec![slot2, slot3];
slots.sort_by_key(|slot| slot.id());
let multi_slot_header = AccountStorageHeader::new(slots.clone()).unwrap();
let multi_elements = multi_slot_header.to_elements();

// Call from_elements.
let slot_names = BTreeMap::from([
(slots[0].id(), slots[0].name.clone()),
(slots[1].id(), slots[1].name.clone()),
]);
let reconstructed_multi =
AccountStorageHeader::try_from_elements(&multi_elements, &slot_names).unwrap();

assert_eq!(multi_slot_header, reconstructed_multi);
}

#[test]
fn test_from_elements_errors() {
// Test with invalid length (not divisible by 8).
let invalid_elements = vec![Felt::new(1), Felt::new(2), Felt::new(3)];
let empty_slot_names = BTreeMap::new();
assert!(
AccountStorageHeader::try_from_elements(&invalid_elements, &empty_slot_names).is_err()
);

// Test with invalid slot type.
let mut invalid_type_elements = vec![crate::ZERO; 8];
invalid_type_elements[1] = Felt::new(5); // Invalid slot type.
assert!(
AccountStorageHeader::try_from_elements(&invalid_type_elements, &empty_slot_names)
.is_err()
);
}

#[test]
fn test_from_elements_with_slot_names() {
use alloc::collections::BTreeMap;

// Create original slot with known name.
let slot_name1 = StorageSlotName::new("test::value::slot1".to_string()).unwrap();
let slot1 = StorageSlotHeader::new(
slot_name1.clone(),
StorageSlotType::Value,
Word::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]),
);

// Serialize the single slot to elements
let elements = slot1.to_elements();

// Create slot names map using the slot's ID
let mut slot_names = BTreeMap::new();
slot_names.insert(slot1.id(), slot_name1.clone());

// Test from_elements with provided slot names on raw slot elements.
let reconstructed_header =
AccountStorageHeader::try_from_elements(&elements, &slot_names).unwrap();

// Verify that the original slot names are preserved.
assert_eq!(reconstructed_header.slots().count(), 1);
let reconstructed_slot = reconstructed_header.slots().next().unwrap();

assert_eq!(slot_name1.as_str(), reconstructed_slot.name().as_str());
assert_eq!(slot1.slot_type(), reconstructed_slot.slot_type());
assert_eq!(slot1.value(), reconstructed_slot.value());
}
}
16 changes: 11 additions & 5 deletions crates/miden-protocol/src/account/storage/map/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::account::StorageMapDelta;
use crate::crypto::merkle::InnerNodeInfo;
use crate::crypto::merkle::smt::{LeafIndex, SMT_DEPTH, Smt, SmtLeaf};
use crate::errors::StorageMapError;
use crate::{AccountError, Felt, Hasher};
use crate::{AccountError, Hasher};

mod partial;
pub use partial::PartialStorageMap;
Expand Down Expand Up @@ -204,16 +204,22 @@ impl StorageMap {
self.entries
}

// UTILITY FUNCTIONS
// --------------------------------------------------------------------------------------------

/// Hashes the given key to get the key of the SMT.
pub fn hash_key(raw_key: Word) -> Word {
Hasher::hash_elements(raw_key.as_elements())
}

// TODO: Replace with https://github.com/0xMiden/crypto/issues/515 once implemented.
/// Returns leaf index of a raw map key.
pub fn map_key_to_leaf_index(raw_key: Word) -> LeafIndex<SMT_DEPTH> {
Self::hash_key(raw_key).into()
}

/// Returns the leaf index of a map key.
pub fn hashed_map_key_to_leaf_index(hashed_map_key: Word) -> Felt {
// The third element in an SMT key is the index.
hashed_map_key[3]
pub fn hashed_map_key_to_leaf_index(hashed_map_key: Word) -> LeafIndex<SMT_DEPTH> {
hashed_map_key.into()
}
}

Expand Down
22 changes: 22 additions & 0 deletions crates/miden-protocol/src/account/storage/slot/slot_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ use core::hash::Hash;
use miden_core::utils::hash_string_to_word;

use crate::Felt;
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};

/// The partial hash of a [`StorageSlotName`](super::StorageSlotName).
///
Expand Down Expand Up @@ -91,6 +98,21 @@ impl Display for StorageSlotId {
}
}

impl Serializable for StorageSlotId {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.suffix.write_into(target);
self.prefix.write_into(target);
}
}

impl Deserializable for StorageSlotId {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let suffix = Felt::read_from(source)?;
let prefix = Felt::read_from(source)?;
Ok(StorageSlotId::new(suffix, prefix))
}
}

// TESTS
// ================================================================================================

Expand Down
28 changes: 28 additions & 0 deletions crates/miden-protocol/src/account/storage/slot/type.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use alloc::string::ToString;
use core::fmt::Display;

use miden_core::{ONE, ZERO};

use crate::utils::serde::{
ByteReader,
ByteWriter,
Expand Down Expand Up @@ -54,6 +56,20 @@ impl TryFrom<u8> for StorageSlotType {
}
}

impl TryFrom<Felt> for StorageSlotType {
type Error = AccountError;

fn try_from(value: Felt) -> Result<Self, Self::Error> {
if value == ZERO {
Ok(StorageSlotType::Value)
Comment on lines +59 to +64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not use impl TryFrom<u8> for StorageSlotType instead?

I think the caller should first let byte = u8::try_from(felt) and then StorageSlotType::try_from(byte). Avoids having to test the impl twice, keeping it up to date in two places, etc.

} else if value == ONE {
Ok(StorageSlotType::Map)
} else {
Err(AccountError::other("invalid storage slot type"))
}
}
}

impl Display for StorageSlotType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Expand Down Expand Up @@ -100,6 +116,7 @@ mod tests {
use miden_core::utils::{Deserializable, Serializable};

use crate::account::StorageSlotType;
use crate::{Felt, FieldElement};

#[test]
fn test_serde_account_storage_slot_type() {
Expand All @@ -112,4 +129,15 @@ mod tests {
assert_eq!(type_0, deserialized_0);
assert_eq!(type_1, deserialized_1);
}

#[test]
fn test_storage_slot_type_from_felt() {
let felt = Felt::ZERO;
let slot_type = StorageSlotType::try_from(felt).unwrap();
assert_eq!(slot_type, StorageSlotType::Value);

let felt = Felt::ONE;
let slot_type = StorageSlotType::try_from(felt).unwrap();
assert_eq!(slot_type, StorageSlotType::Map);
}
}
7 changes: 7 additions & 0 deletions crates/miden-protocol/src/block/account_tree/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use alloc::string::ToString;
use alloc::vec::Vec;

use miden_crypto::merkle::smt::LeafIndex;

use crate::Word;
use crate::account::{AccountId, AccountIdPrefix};
use crate::crypto::merkle::MerkleError;
Expand Down Expand Up @@ -46,6 +48,11 @@ pub fn smt_key_to_account_id(key: Word) -> AccountId {
.expect("account tree should only contain valid IDs")
}

/// Converts an AccountId to an SMT leaf index for use with MerkleStore operations.
pub fn account_id_to_smt_index(account_id: AccountId) -> LeafIndex<SMT_DEPTH> {
account_id_to_smt_key(account_id).into()
}

// ACCOUNT TREE
// ================================================================================================

Expand Down
36 changes: 36 additions & 0 deletions crates/miden-protocol/src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use miden_assembly::diagnostics::reporting::PrintDiagnostic;
use miden_core::mast::MastForestError;
use miden_core::{EventId, Felt};
use miden_crypto::merkle::mmr::MmrError;
use miden_crypto::merkle::smt::{SmtLeafError, SmtProofError};
use miden_crypto::utils::HexParseError;
use miden_processor::DeserializationError;
use thiserror::Error;
Expand Down Expand Up @@ -694,6 +695,41 @@ pub enum TransactionInputError {
TooManyInputNotes(usize),
}

// TRANSACTION INPUTS EXTRACTION ERROR
// ===============================================================================================

#[derive(Debug, Error)]
pub enum TransactionInputsExtractionError {
#[error("specified foreign account id matches the transaction input's account id")]
AccountNotForeign,
#[error("foreign account data not found in advice map for account {0}")]
ForeignAccountNotFound(AccountId),
#[error("foreign account code not found for account {0}")]
ForeignAccountCodeNotFound(AccountId),
#[error("storage header data not found in advice map for account {0}")]
StorageHeaderNotFound(AccountId),
#[error("failed to handle account data")]
AccountError(#[from] AccountError),
#[error("failed to handle merkle data")]
MerkleError(#[from] MerkleError),
#[error("failed to handle account tree data")]
AccountTreeError(#[from] AccountTreeError),
#[error("missing vault root from Merkle store")]
MissingVaultRoot,
#[error("missing storage map root from Merkle store")]
MissingMapRoot,
#[error("failed to construct SMT proof")]
SmtProofError(#[from] SmtProofError),
#[error("failed to construct asset witness")]
AssetError(#[from] AssetError),
#[error("failed to handle storage map data")]
StorageMapError(#[from] StorageMapError),
#[error("failed to convert elements to leaf index: {0}")]
LeafConversionError(String),
#[error("failed to construct SMT leaf")]
SmtLeafError(#[from] SmtLeafError),
}

// TRANSACTION OUTPUT ERROR
// ===============================================================================================

Expand Down
Loading