diff --git a/miden-lib/asm/miden/sat/internal/prologue.masm b/miden-lib/asm/miden/sat/internal/prologue.masm index b40afb26f..79a2b393b 100644 --- a/miden-lib/asm/miden/sat/internal/prologue.masm +++ b/miden-lib/asm/miden/sat/internal/prologue.masm @@ -656,7 +656,7 @@ end #! Process the consumed notes data provided via the advice provider. This involves reading the data #! from the advice provider and storing it at the appropriate memory addresses. As each note is #! consumed its hash and nullifier is computed. The transaction nullifier commitment is computed -#! via a sequential hash of all (nullifier, script_root) pairs for all consumed notes. +#! via a sequential hash of all (nullifier, ZERO) pairs for all consumed notes. #! #! Stack: [] #! Advice stack: [num_cn, @@ -679,78 +679,99 @@ end proc.process_consumed_notes_data # load the consumed notes data onto the advice stack exec.layout::get_nullifier_com adv.push_mapval dropw + # => [...] # read the number of consumed notes from the advice provider adv_push.1 + # => [num_notes, ...] # store the number of consumed notes dup exec.layout::set_total_num_consumed_notes + # => [num_notes, ...] - # assert the number of consumed notes is within limits - dup exec.constants::get_max_num_consumed_notes lte assert + # assert the number of consumed notes is within limits; since max number of consumed notes is + # expected to be smaller than 2^32, we can use a more efficient u32 comparison + dup exec.constants::get_max_num_consumed_notes u32assert2 u32lte assert + # => [num_notes, ...] # loop over consumed notes and read data # --------------------------------------------------------------------------------------------- - # initialize counter for consumed notes + # initialize counter of already processed notes push.0 + # => [num_processed_notes = 0, num_notes, ...] # check if the number of consumed notes is greater then 0. Conditional for the while loop. dup.1 dup.1 neq + # => [has_more_notes, num_processed_notes, num_notes, ...] # loop and read note data from the advice provider while.true dup exec.process_consumed_note + # => [num_processed_notes, num_notes, ...] - # increment consumed note counter and check if we should loop again + # increment processed note counter and check if we should loop again add.1 dup.1 dup.1 neq + # => [has_more_notes, num_processed_notes + 1, num_notes, ...] end # drop counter drop + # => [num_notes, ...] # compute nullifier commitment # --------------------------------------------------------------------------------------------- - # initiate counter for nullifiers + # initiate counter of notes processed for nullifier hashing push.0 + # => [num_processed_notes = 0, num_notes, ...] # initiate stack for sequential hash to compute nullifier commitment padw padw padw + # => [R1, R0, CAP, num_processed_notes, num_notes, ...] # check if the number of consumed notes is greater then 0. Conditional for the while loop. dup.13 dup.13 neq + # => [has_more_notes, R1, R0, CAP, num_processed_notes, num_notes, ...] - # loop and sequentially hash hperm(nullifier, script_root) over all consumed notes + # loop and sequentially hash hperm(nullifier, ZERO) over all consumed notes while.true # clear hasher rate dropw dropw + # => [CAP, num_processed_notes, num_notes, ...] # get consumed note nullifier dup.4 exec.layout::get_consumed_note_nullifier + # => [NULLIFIER, CAP, num_processed_notes, num_notes, ...] - # get consumed note script root - dup.8 exec.layout::get_consumed_note_ptr exec.layout::get_consumed_note_script_root + # pad the stack + padw + # => [ZERO, NULLIFIER, CAP, num_processed_notes, num_notes, ...] - # compute hperm(nullifier, script_root) + # compute hperm(nullifier, ZERO) hperm + # => [PERM, PERM, CAP, num_processed_notes, num_notes, ...] - # increment nullifier counter and check if we should loop again + # increment processed note counter and check if we should loop again movup.12 add.1 dup movdn.13 dup.14 neq + # => [has_more_notes, PERM, PERM, CAP, num_processed_notes + 1, num_notes, ...] end # extract nullifier hash dropw swapw dropw + # => [NULLIFIER_COM, num_processed_notes + 1, num_notes, ...] # assert nullifier hash is what we would expect exec.layout::get_nullifier_com assert_eqw + # => [num_processed_notes + 1, num_notes, ...] # clear stack drop drop + # => [...] # set the current consumed note pointer to the first consumed note push.0 exec.layout::get_consumed_note_ptr exec.layout::set_current_consumed_note_ptr - # => [] + # => [...] end # TRANSACTION SCRIPT diff --git a/miden-tx/src/verifier/mod.rs b/miden-tx/src/verifier/mod.rs index f3eae322f..d993fd556 100644 --- a/miden-tx/src/verifier/mod.rs +++ b/miden-tx/src/verifier/mod.rs @@ -4,8 +4,8 @@ use miden_lib::outputs::{ CREATED_NOTES_COMMITMENT_WORD_IDX, FINAL_ACCOUNT_HASH_WORD_IDX, TX_SCRIPT_ROOT_WORD_IDX, }; use miden_objects::{ - notes::NoteEnvelope, - transaction::{ConsumedNoteInfo, ProvenTransaction}, + notes::{NoteEnvelope, Nullifier}, + transaction::ProvenTransaction, Felt, Word, WORD_SIZE, ZERO, }; use miden_verifier::verify; @@ -58,12 +58,13 @@ impl TransactionVerifier { // HELPERS // -------------------------------------------------------------------------------------------- + /// Returns the consumed notes commitment. - fn compute_consumed_notes_hash(consumed_notes: &[ConsumedNoteInfo]) -> Digest { + fn compute_consumed_notes_hash(consumed_notes: &[Nullifier]) -> Digest { let mut elements: Vec = Vec::with_capacity(consumed_notes.len() * 8); - for note in consumed_notes.iter() { - elements.extend_from_slice(note.nullifier().as_elements()); - elements.extend_from_slice(note.script_root().as_elements()); + for nullifier in consumed_notes.iter() { + elements.extend_from_slice(nullifier.inner().as_elements()); + elements.extend_from_slice(&Word::default()); } Hasher::hash_elements(&elements) } diff --git a/mock/src/mock/chain.rs b/mock/src/mock/chain.rs index b90424a9e..d0ed1e281 100644 --- a/mock/src/mock/chain.rs +++ b/mock/src/mock/chain.rs @@ -508,7 +508,7 @@ impl MockChain { return Err(MockError::DuplicatedNote); } - self.check_nullifier_unknown(note.nullifier()); + self.check_nullifier_unknown(note.nullifier().inner()); self.pending_objects.notes.push(note); Ok(()) } diff --git a/objects/src/notes/mod.rs b/objects/src/notes/mod.rs index 31e927949..73ebd96bd 100644 --- a/objects/src/notes/mod.rs +++ b/objects/src/notes/mod.rs @@ -15,6 +15,9 @@ pub use inputs::NoteInputs; mod metadata; pub use metadata::NoteMetadata; +mod nullifier; +pub use nullifier::Nullifier; + mod origin; use miden_crypto::utils::{ByteReader, ByteWriter, Deserializable, Serializable}; pub use origin::{NoteInclusionProof, NoteOrigin}; @@ -177,23 +180,8 @@ impl Note { } /// Returns the nullifier for this note. - /// - /// The nullifier is computed as hash(serial_num, script_hash, input_hash, vault_hash). - /// This achieves the following properties: - /// - Every note can be reduced to a single unique nullifier. - /// - We cannot derive a note's hash from its nullifier. - /// - To compute the nullifier we must know all components of the note: serial_num, - /// script_hash, input_hash and vault hash. - pub fn nullifier(&self) -> Digest { - // The total number of elements to be hashed is 16. We can absorb them in - // exactly two permutations - let target_num_elements = 4 * WORD_SIZE; - let mut elements: Vec = Vec::with_capacity(target_num_elements); - elements.extend_from_slice(&self.serial_num); - elements.extend_from_slice(self.script.hash().as_elements()); - elements.extend_from_slice(self.inputs.hash().as_elements()); - elements.extend_from_slice(self.vault.hash().as_elements()); - Hasher::hash_elements(&elements) + pub fn nullifier(&self) -> Nullifier { + self.into() } } diff --git a/objects/src/notes/nullifier.rs b/objects/src/notes/nullifier.rs new file mode 100644 index 000000000..30a4493b3 --- /dev/null +++ b/objects/src/notes/nullifier.rs @@ -0,0 +1,110 @@ +use super::{Digest, Felt, Hasher, Note, Word, WORD_SIZE, ZERO}; +use crate::utils::serde::{ + ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, +}; + +// NULLIFIER +// ================================================================================================ + +/// A note's nullifier. +/// +/// A note's nullifier is computed as hash(serial_num, script_hash, input_hash, vault_hash). +/// +/// This achieves the following properties: +/// - Every note can be reduced to a single unique nullifier. +/// - We cannot derive a note's hash from its nullifier, or a note's nullifier from its hash. +/// - To compute the nullifier we must know all components of the note: serial_num, script_hash, +/// input_hash and vault_hash. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Nullifier(Digest); + +impl Nullifier { + /// Returns a new note [Nullifier] instantiated from the provided digest. + pub fn new( + script_hash: Digest, + inputs_hash: Digest, + vault_hash: Digest, + serial_num: Word, + ) -> Self { + let mut elements = [ZERO; 4 * WORD_SIZE]; + elements[..4].copy_from_slice(&serial_num); + elements[4..8].copy_from_slice(script_hash.as_elements()); + elements[8..12].copy_from_slice(inputs_hash.as_elements()); + elements[12..].copy_from_slice(vault_hash.as_elements()); + Self(Hasher::hash_elements(&elements)) + } + + /// Returns the elements of this nullifier. + pub fn as_elements(&self) -> &[Felt] { + self.0.as_elements() + } + + /// Returns the digest defining this nullifier. + pub fn inner(&self) -> Digest { + self.0 + } +} + +// CONVERSIONS INTO NULLIFIER +// ================================================================================================ + +impl From<&Note> for Nullifier { + fn from(note: &Note) -> Self { + Self::new(note.script.hash(), note.inputs.hash(), note.vault.hash(), note.serial_num) + } +} + +impl From for Nullifier { + fn from(value: Word) -> Self { + Self(value.into()) + } +} + +impl From for Nullifier { + fn from(value: Digest) -> Self { + Self(value) + } +} + +// CONVERSIONS FROM NULLIFIER +// ================================================================================================ + +impl From for Word { + fn from(nullifier: Nullifier) -> Self { + nullifier.0.into() + } +} + +impl From for [u8; 32] { + fn from(nullifier: Nullifier) -> Self { + nullifier.0.into() + } +} + +impl From<&Nullifier> for Word { + fn from(nullifier: &Nullifier) -> Self { + nullifier.0.into() + } +} + +impl From<&Nullifier> for [u8; 32] { + fn from(nullifier: &Nullifier) -> Self { + nullifier.0.into() + } +} + +// SERIALIZATION +// ================================================================================================ + +impl Serializable for Nullifier { + fn write_into(&self, target: &mut W) { + target.write_bytes(&self.0.to_bytes()); + } +} + +impl Deserializable for Nullifier { + fn read_from(source: &mut R) -> Result { + let nullifier = Digest::read_from(source)?; + Ok(Self(nullifier)) + } +} diff --git a/objects/src/notes/script.rs b/objects/src/notes/script.rs index 76e56e75b..34c8bea71 100644 --- a/objects/src/notes/script.rs +++ b/objects/src/notes/script.rs @@ -1,7 +1,8 @@ use super::{Assembler, AssemblyContext, CodeBlock, Digest, NoteError, ProgramAst}; -use crate::utils::serde::{ByteReader, ByteWriter, Deserializable, Serializable}; +use crate::utils::serde::{ + ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, +}; use assembly::ast::AstSerdeOptions; -use vm_processor::DeserializationError; // CONSTANTS // ================================================================================================ diff --git a/objects/src/transaction/consumed_notes.rs b/objects/src/transaction/consumed_notes.rs index 55fb035cf..5645c464e 100644 --- a/objects/src/transaction/consumed_notes.rs +++ b/objects/src/transaction/consumed_notes.rs @@ -2,8 +2,8 @@ use miden_crypto::utils::{ByteReader, ByteWriter, Deserializable, Serializable}; use vm_processor::DeserializationError; use super::{ - utils::generate_consumed_notes_commitment, AdviceInputsBuilder, Digest, Felt, RecordedNote, - ToAdviceInputs, Vec, Word, + utils::generate_consumed_notes_commitment, AdviceInputsBuilder, Digest, Felt, Nullifier, + RecordedNote, ToAdviceInputs, Vec, Word, }; // CONSUMED NOTES @@ -96,9 +96,13 @@ impl ToAdviceInputs for ConsumedNotes { } } -impl From for Vec { +impl From for Vec { fn from(consumed_notes: ConsumedNotes) -> Self { - consumed_notes.notes.into_iter().map(|note| note.into()).collect::>() + consumed_notes + .notes + .into_iter() + .map(|note| note.note().nullifier()) + .collect::>() } } @@ -114,96 +118,6 @@ impl FromIterator for ConsumedNotes { } } -// CONSUMED NOTE INFO -// ================================================================================================ - -/// Holds information about a note that was consumed by a transaction. -/// Contains: -/// - nullifier: nullifier of the note that was consumed -/// - script_root: script root of the note that was consumed -#[derive(Clone, Copy, Debug)] -pub struct ConsumedNoteInfo { - nullifier: Digest, - script_root: Digest, -} - -impl ConsumedNoteInfo { - /// Creates a new ConsumedNoteInfo object. - pub fn new(nullifier: Digest, script_root: Digest) -> Self { - Self { - nullifier, - script_root, - } - } - - /// Returns the nullifier of the note that was consumed. - pub fn nullifier(&self) -> Digest { - self.nullifier - } - - /// Returns the script root of the note that was consumed. - pub fn script_root(&self) -> Digest { - self.script_root - } -} - -impl From for [Felt; 8] { - fn from(note_info: ConsumedNoteInfo) -> Self { - (¬e_info).into() - } -} - -impl From for [Word; 2] { - fn from(note_info: ConsumedNoteInfo) -> Self { - (¬e_info).into() - } -} - -impl From for [u8; 64] { - fn from(note_info: ConsumedNoteInfo) -> Self { - (¬e_info).into() - } -} - -impl From for ConsumedNoteInfo { - fn from(recorded_note: RecordedNote) -> Self { - (&recorded_note).into() - } -} - -impl From<&ConsumedNoteInfo> for [Felt; 8] { - fn from(note_info: &ConsumedNoteInfo) -> Self { - let mut elements: [Felt; 8] = Default::default(); - elements[..4].copy_from_slice(note_info.nullifier.as_elements()); - elements[4..].copy_from_slice(note_info.script_root.as_elements()); - elements - } -} - -impl From<&ConsumedNoteInfo> for [Word; 2] { - fn from(note_info: &ConsumedNoteInfo) -> Self { - let mut elements: [Word; 2] = Default::default(); - elements[0].copy_from_slice(note_info.nullifier.as_elements()); - elements[1].copy_from_slice(note_info.script_root.as_elements()); - elements - } -} - -impl From<&ConsumedNoteInfo> for [u8; 64] { - fn from(note_info: &ConsumedNoteInfo) -> Self { - let mut elements: [u8; 64] = [0; 64]; - elements[..32].copy_from_slice(¬e_info.nullifier.as_bytes()); - elements[32..].copy_from_slice(¬e_info.script_root.as_bytes()); - elements - } -} - -impl From<&RecordedNote> for ConsumedNoteInfo { - fn from(recorded_note: &RecordedNote) -> Self { - Self::new(recorded_note.note().nullifier(), recorded_note.note().script().hash()) - } -} - // SERIALIZATION // ================================================================================================ // @@ -224,22 +138,3 @@ impl Deserializable for ConsumedNotes { Ok(Self::new(notes)) } } - -impl Serializable for ConsumedNoteInfo { - fn write_into(&self, target: &mut W) { - target.write_bytes(&self.nullifier.to_bytes()); - target.write_bytes(&self.script_root.to_bytes()); - } -} - -impl Deserializable for ConsumedNoteInfo { - fn read_from(source: &mut R) -> Result { - let nullifier = Digest::read_from(source)?; - let script_root = Digest::read_from(source)?; - - Ok(Self { - nullifier, - script_root, - }) - } -} diff --git a/objects/src/transaction/mod.rs b/objects/src/transaction/mod.rs index e8874b000..7ba29bcfa 100644 --- a/objects/src/transaction/mod.rs +++ b/objects/src/transaction/mod.rs @@ -1,6 +1,6 @@ use super::{ accounts::{Account, AccountId}, - notes::{Note, NoteEnvelope, RecordedNote}, + notes::{Note, NoteEnvelope, Nullifier, RecordedNote}, utils::collections::Vec, AdviceInputs, AdviceInputsBuilder, BlockHeader, ChainMmr, Digest, Felt, Hasher, PreparedTransactionError, StarkField, ToAdviceInputs, TransactionWitnessError, Word, WORD_SIZE, @@ -23,7 +23,7 @@ mod tx_witness; mod utils; pub use account_stub::FinalAccountStub; -pub use consumed_notes::{ConsumedNoteInfo, ConsumedNotes}; +pub use consumed_notes::ConsumedNotes; pub use created_notes::CreatedNotes; pub use event::Event; pub use executed_tx::ExecutedTransaction; diff --git a/objects/src/transaction/proven_tx.rs b/objects/src/transaction/proven_tx.rs index 8773020ac..129e95a6e 100644 --- a/objects/src/transaction/proven_tx.rs +++ b/objects/src/transaction/proven_tx.rs @@ -1,4 +1,4 @@ -use super::{AccountId, ConsumedNoteInfo, Digest, NoteEnvelope, TransactionId, Vec}; +use super::{AccountId, Digest, NoteEnvelope, Nullifier, TransactionId, Vec}; use miden_crypto::utils::{ByteReader, ByteWriter, Deserializable, Serializable}; use miden_verifier::ExecutionProof; use vm_processor::DeserializationError; @@ -9,7 +9,7 @@ use vm_processor::DeserializationError; /// - account_id: the account that the transaction was executed against. /// - initial_account_hash: the hash of the account before the transaction was executed. /// - final_account_hash: the hash of the account after the transaction was executed. -/// - consumed_notes: a list of consumed notes. +/// - consumed_notes: a list of consumed notes defined by their nullifiers. /// - created_notes: a list of created notes. /// - tx_script_root: the script root of the transaction. /// - block_ref: the block hash of the last known block at the time the transaction was executed. @@ -19,7 +19,7 @@ pub struct ProvenTransaction { account_id: AccountId, initial_account_hash: Digest, final_account_hash: Digest, - consumed_notes: Vec, + consumed_notes: Vec, created_notes: Vec, tx_script_root: Option, block_ref: Digest, @@ -33,7 +33,7 @@ impl ProvenTransaction { account_id: AccountId, initial_account_hash: Digest, final_account_hash: Digest, - consumed_notes: Vec, + consumed_notes: Vec, created_notes: Vec, tx_script_root: Option, block_ref: Digest, @@ -74,8 +74,8 @@ impl ProvenTransaction { self.final_account_hash } - /// Returns the consumed notes. - pub fn consumed_notes(&self) -> &[ConsumedNoteInfo] { + /// Returns the nullifiers of consumed notes. + pub fn consumed_notes(&self) -> &[Nullifier] { &self.consumed_notes } @@ -124,7 +124,7 @@ impl Deserializable for ProvenTransaction { let final_account_hash = Digest::read_from(source)?; let count = source.read_u64()?; - let consumed_notes = ConsumedNoteInfo::read_batch_from(source, count as usize)?; + let consumed_notes = Nullifier::read_batch_from(source, count as usize)?; let count = source.read_u64()?; let created_notes = NoteEnvelope::read_batch_from(source, count as usize)?; diff --git a/objects/src/transaction/transaction_id.rs b/objects/src/transaction/transaction_id.rs index d6968f298..f0c056e93 100644 --- a/objects/src/transaction/transaction_id.rs +++ b/objects/src/transaction/transaction_id.rs @@ -55,9 +55,9 @@ impl From<&ProvenTransaction> for TransactionId { // TODO: move input/output note hash computations into a more central location let input_notes_hash = { let mut elements: Vec = Vec::with_capacity(tx.consumed_notes().len() * 8); - for note in tx.consumed_notes().iter() { - elements.extend_from_slice(note.nullifier().as_elements()); - elements.extend_from_slice(note.script_root().as_elements()); + for nullifier in tx.consumed_notes().iter() { + elements.extend_from_slice(nullifier.as_elements()); + elements.extend_from_slice(&Word::default()); } Hasher::hash_elements(&elements) }; diff --git a/objects/src/transaction/tx_witness.rs b/objects/src/transaction/tx_witness.rs index e6974a0e5..cf7822e7a 100644 --- a/objects/src/transaction/tx_witness.rs +++ b/objects/src/transaction/tx_witness.rs @@ -1,6 +1,6 @@ use super::{ - AccountId, AdviceInputs, ConsumedNoteInfo, Digest, Felt, Hasher, Program, StackInputs, - StarkField, TransactionWitnessError, Vec, Word, WORD_SIZE, + AccountId, AdviceInputs, Digest, Felt, Hasher, Nullifier, Program, StackInputs, StarkField, + TransactionWitnessError, Vec, Word, WORD_SIZE, }; /// A [TransactionWitness] is the minimum required data required to execute and prove a Miden rollup @@ -68,12 +68,12 @@ impl TransactionWitness { &self.consumed_notes_hash } - /// Returns a vector of [ConsumedNoteInfo] for all of the consumed notes in the transaction. + /// Returns a vector of [Nullifier] for all consumed notes in the transaction. /// /// # Errors /// - If the consumed notes data is not found in the advice map. /// - If the consumed notes data is not well formed. - pub fn consumed_notes_info(&self) -> Result, TransactionWitnessError> { + pub fn consumed_notes_info(&self) -> Result, TransactionWitnessError> { // fetch consumed notes data from the advice map let notes_data = self .advice_witness @@ -94,10 +94,10 @@ impl TransactionWitness { } // compute the nullifier and extract script root and number of assets - let (nullifier, script_root, num_assets) = extract_note_data(¬es_data[note_ptr..]); + let (nullifier, num_assets) = extract_note_data(¬es_data[note_ptr..]); // push the [ConsumedNoteInfo] to the vector - consumed_notes_info.push(ConsumedNoteInfo::new(nullifier, script_root)); + consumed_notes_info.push(nullifier.into()); // round up the number of assets to the next multiple of 2 to account for asset padding let num_assets = (num_assets + 1) & !1; @@ -111,7 +111,11 @@ impl TransactionWitness { Hasher::hash_elements( &consumed_notes_info .iter() - .flat_map(|info| <[Felt; 8]>::from(*info)) + .flat_map(|info| { + let mut elements = Word::from(info).to_vec(); + elements.extend_from_slice(&Word::default()); + elements + }) .collect::>() ) ); @@ -164,8 +168,7 @@ impl TransactionWitness { // HELPERS // ================================================================================================ -/// Extracts and returns the nullifier, script root and number of assets from the provided note -/// data. +/// Extracts and returns the nullifier and the number of assets from the provided note data. /// /// Expects the note data to be organized as follows: /// [CN_SN, CN_SR, CN_IR, CN_VR, CN_M] @@ -175,14 +178,12 @@ impl TransactionWitness { /// - CN_IR is the inputs root of the consumed note. /// - CN_VR is the vault root of the consumed note. /// - CN1_M is the metadata of the consumed note. -fn extract_note_data(note_data: &[Felt]) -> (Digest, Digest, u64) { +fn extract_note_data(note_data: &[Felt]) -> (Digest, u64) { // compute the nullifier let nullifier = Hasher::hash_elements(¬e_data[..4 * WORD_SIZE]); - // extract the script root and number of assets - let script_root: Word = - note_data[WORD_SIZE..2 * WORD_SIZE].try_into().expect("word is well formed"); + // extract the number of assets let num_assets = note_data[4 * WORD_SIZE].as_int(); - (nullifier, script_root.into(), num_assets) + (nullifier, num_assets) } diff --git a/objects/src/transaction/utils.rs b/objects/src/transaction/utils.rs index 368dd3c3f..f64f47e37 100644 --- a/objects/src/transaction/utils.rs +++ b/objects/src/transaction/utils.rs @@ -90,13 +90,14 @@ pub fn generate_advice_provider_inputs( } /// Returns the consumed notes commitment. -/// This is a sequential hash of all (nullifier, script_root) pairs for the notes consumed in the +/// +/// This is a sequential hash of all (nullifier, ZERO) pairs for the notes consumed in the /// transaction. pub fn generate_consumed_notes_commitment(recorded_notes: &[RecordedNote]) -> Digest { let mut elements: Vec = Vec::with_capacity(recorded_notes.len() * 8); for recorded_note in recorded_notes.iter() { elements.extend_from_slice(recorded_note.note().nullifier().as_elements()); - elements.extend_from_slice(recorded_note.note().script().hash().as_elements()); + elements.extend_from_slice(&Word::default()); } Hasher::hash_elements(&elements) }