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 @@ -26,6 +26,7 @@
- Added `Address` serialization and deserialization ([#1937](https://github.com/0xMiden/miden-base/issues/1937))
- Added `StorageMap::{num_entries, num_leaves}` to retrieve the number of entries in a storage map ([#1935]https://github.com/0xMiden/miden-base/pull/1935).
- Added `AssetVault::{num_assets, num_leaves, inner_nodes}` ([#1939]https://github.com/0xMiden/miden-base/pull/1939).
- [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973]https://github.com/0xMiden/miden-base/pull/1973).

### Changes

Expand Down
44 changes: 22 additions & 22 deletions crates/miden-objects/src/transaction/outputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ impl OutputNotes {
}
}

let commitment = build_output_notes_commitment(&notes);
let commitment = Self::compute_commitment(notes.iter().map(NoteHeader::from));

Ok(Self { notes, commitment })
}
Expand Down Expand Up @@ -140,6 +140,27 @@ impl OutputNotes {
pub fn iter(&self) -> impl Iterator<Item = &OutputNote> {
self.notes.iter()
}

// HELPERS
// --------------------------------------------------------------------------------------------

/// Computes a commitment to output notes.
///
/// For a non-empty list of notes, this is a sequential hash of (note_id, metadata) tuples for
/// the notes created in a transaction. For an empty list, [EMPTY_WORD] is returned.
pub(crate) fn compute_commitment(notes: impl ExactSizeIterator<Item = NoteHeader>) -> Word {
if notes.len() == 0 {
return Word::empty();
}

let mut elements: Vec<Felt> = Vec::with_capacity(notes.len() * 8);
for note_header in notes {
elements.extend_from_slice(note_header.id().as_elements());
elements.extend_from_slice(Word::from(note_header.metadata()).as_elements());
}

Hasher::hash_elements(&elements)
}
}

// SERIALIZATION
Expand Down Expand Up @@ -307,27 +328,6 @@ impl Deserializable for OutputNote {
}
}

// HELPER FUNCTIONS
// ================================================================================================

/// Build a commitment to output notes.
///
/// For a non-empty list of notes, this is a sequential hash of (note_id, metadata) tuples for the
/// notes created in a transaction. For an empty list, [EMPTY_WORD] is returned.
fn build_output_notes_commitment(notes: &[OutputNote]) -> Word {
if notes.is_empty() {
return Word::empty();
}

let mut elements: Vec<Felt> = Vec::with_capacity(notes.len() * 8);
for note in notes.iter() {
elements.extend_from_slice(note.id().as_elements());
elements.extend_from_slice(Word::from(note.metadata()).as_elements());
}

Hasher::hash_elements(&elements)
}

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

Expand Down
90 changes: 57 additions & 33 deletions crates/miden-objects/src/transaction/tx_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ use alloc::vec::Vec;
use miden_processor::DeserializationError;

use crate::Word;
use crate::note::NoteId;
use crate::note::NoteHeader;
use crate::transaction::{
AccountId,
InputNoteCommitment,
Nullifier,
OutputNote,
InputNotes,
OutputNotes,
ProvenTransaction,
TransactionId,
};
Expand All @@ -27,8 +27,8 @@ pub struct TransactionHeader {
account_id: AccountId,
initial_state_commitment: Word,
final_state_commitment: Word,
input_notes: Vec<Nullifier>,
output_notes: Vec<NoteId>,
input_notes: InputNotes<InputNoteCommitment>,
output_notes: Vec<NoteHeader>,
}

impl TransactionHeader {
Expand All @@ -37,18 +37,30 @@ impl TransactionHeader {

/// Constructs a new [`TransactionHeader`] from the provided parameters.
///
/// Note that the nullifiers of the input notes and note IDs of the output notes must be in the
/// same order as they appeared in the transaction. This is ensured when constructing this type
/// from a proven transaction, but cannot be validated during deserialization, hence additional
/// validation is necessary.
pub(crate) fn new(
id: TransactionId,
/// The [`TransactionId`] is computed from the provided parameters.
///
/// The input notes and output notes must be in the same order as they appeared in the
/// transaction that this header represents, otherwise an incorrect ID will be computed.
///
/// Note that this cannot validate that the [`AccountId`] is valid with respect to the other
/// data. This must be validated outside of this type.
pub fn new(
account_id: AccountId,
initial_state_commitment: Word,
final_state_commitment: Word,
input_notes: Vec<Nullifier>,
output_notes: Vec<NoteId>,
input_notes: InputNotes<InputNoteCommitment>,
output_notes: Vec<NoteHeader>,
) -> Self {
let input_notes_commitment = input_notes.commitment();
let output_notes_commitment = OutputNotes::compute_commitment(output_notes.iter().copied());

let id = TransactionId::new(
initial_state_commitment,
final_state_commitment,
input_notes_commitment,
output_notes_commitment,
);

Self {
id,
account_id,
Expand All @@ -59,24 +71,28 @@ impl TransactionHeader {
}
}

/// Constructs a new [`TransactionHeader`] from the provided parameters for testing purposes.
#[cfg(any(feature = "testing", test))]
/// Constructs a new [`TransactionHeader`] from the provided parameters.
///
/// # Warning
///
/// This does not validate the internal consistency of the data. Prefer [`Self::new`] whenever
/// possible.
pub fn new_unchecked(
id: TransactionId,
account_id: AccountId,
initial_state_commitment: Word,
final_state_commitment: Word,
input_notes: Vec<Nullifier>,
output_notes: Vec<NoteId>,
input_notes: InputNotes<InputNoteCommitment>,
output_notes: Vec<NoteHeader>,
) -> Self {
Self::new(
Self {
id,
account_id,
initial_state_commitment,
final_state_commitment,
input_notes,
output_notes,
)
}
}

// PUBLIC ACCESSORS
Expand Down Expand Up @@ -104,32 +120,41 @@ impl TransactionHeader {
self.final_state_commitment
}

/// Returns a reference to the nullifiers of the consumed notes.
/// Returns a reference to the consumed notes of the transaction.
///
/// The returned input note commitments have the same order as the transaction to which the
/// header belongs.
///
/// Note that the note may have been erased at the batch or block level, so it may not be
/// present there.
pub fn input_notes(&self) -> &[Nullifier] {
pub fn input_notes(&self) -> &InputNotes<InputNoteCommitment> {
&self.input_notes
}

/// Returns a reference to the notes created by the transaction.
/// Returns a reference to the ID and metadata of the output notes created by the transaction.
///
/// The returned output note data has the same order as the transaction to which the header
/// belongs.
///
/// Note that the note may have been erased at the batch or block level, so it may not be
/// present there.
pub fn output_notes(&self) -> &[NoteId] {
pub fn output_notes(&self) -> &[NoteHeader] {
&self.output_notes
}
}

impl From<&ProvenTransaction> for TransactionHeader {
/// Constructs a [`TransactionHeader`] from a [`ProvenTransaction`].
fn from(tx: &ProvenTransaction) -> Self {
TransactionHeader::new(
// SAFETY: The data in a proven transaction is guaranteed to be internally consistent and so
// we can skip the consistency checks by the `new` constructor.
TransactionHeader::new_unchecked(
tx.id(),
tx.account_id(),
tx.account_update().initial_state_commitment(),
tx.account_update().final_state_commitment(),
tx.input_notes().iter().map(InputNoteCommitment::nullifier).collect(),
tx.output_notes().iter().map(OutputNote::id).collect(),
tx.input_notes().clone(),
tx.output_notes().iter().map(NoteHeader::from).collect(),
)
}
}
Expand All @@ -139,7 +164,6 @@ impl From<&ProvenTransaction> for TransactionHeader {

impl Serializable for TransactionHeader {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.id.write_into(target);
self.account_id.write_into(target);
self.initial_state_commitment.write_into(target);
self.final_state_commitment.write_into(target);
Expand All @@ -150,20 +174,20 @@ impl Serializable for TransactionHeader {

impl Deserializable for TransactionHeader {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let id = <TransactionId>::read_from(source)?;
let account_id = <AccountId>::read_from(source)?;
let initial_state_commitment = <Word>::read_from(source)?;
let final_state_commitment = <Word>::read_from(source)?;
let input_notes = <Vec<Nullifier>>::read_from(source)?;
let output_notes = <Vec<NoteId>>::read_from(source)?;
let input_notes = <InputNotes<InputNoteCommitment>>::read_from(source)?;
let output_notes = <Vec<NoteHeader>>::read_from(source)?;

Ok(Self::new(
id,
let tx_header = Self::new(
account_id,
initial_state_commitment,
final_state_commitment,
input_notes,
output_notes,
))
);

Ok(tx_header)
}
}