From f47566300ac5c6c036ecc28bfb9024a0200f4b7f Mon Sep 17 00:00:00 2001 From: Augusto Hack Date: Fri, 7 Jun 2024 20:56:39 +0200 Subject: [PATCH] feat: replaced ToNullifier with ToInputNoteCommitment (#732) --- CHANGELOG.md | 1 + miden-lib/src/transaction/inputs.rs | 10 +- miden-tx/src/compiler/mod.rs | 6 +- miden-tx/src/kernel_tests/test_prologue.rs | 2 +- miden-tx/src/prover/mod.rs | 7 +- miden-tx/src/verifier/mod.rs | 2 +- objects/src/transaction/executed_tx.rs | 8 +- objects/src/transaction/inputs.rs | 130 +++++++++++---------- objects/src/transaction/mod.rs | 6 +- objects/src/transaction/prepared_tx.rs | 6 +- objects/src/transaction/proven_tx.rs | 82 +++++++++++-- objects/src/transaction/transaction_id.rs | 4 +- objects/src/transaction/tx_witness.rs | 5 +- 13 files changed, 170 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42d2a917a..923e48eec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * [BREAKING] Added support for delegated authenticated notes (#724). * [BREAKING] Changed rng to mutable reference in note creation functions (#733). * Added transaction IDs to the `Block` struct (#734). +* [BREAKING] Replaced `ToNullifier` trait with `ToInputNoteCommitments`, which includes the `note_id` for delayed note authentication (#732). ## 0.3.0 (2024-05-14) diff --git a/miden-lib/src/transaction/inputs.rs b/miden-lib/src/transaction/inputs.rs index 57477f5eb..8c2244f63 100644 --- a/miden-lib/src/transaction/inputs.rs +++ b/miden-lib/src/transaction/inputs.rs @@ -27,7 +27,7 @@ impl ToTransactionKernelInputs for PreparedTransaction { let stack_inputs = TransactionKernel::build_input_stack( account.id(), account.init_hash(), - self.input_notes().nullifier_commitment(), + self.input_notes().commitment(), self.block_header().hash(), ); @@ -44,7 +44,7 @@ impl ToTransactionKernelInputs for ExecutedTransaction { let stack_inputs = TransactionKernel::build_input_stack( account.id(), account.init_hash(), - self.input_notes().nullifier_commitment(), + self.input_notes().commitment(), self.block_header().hash(), ); @@ -62,7 +62,7 @@ impl ToTransactionKernelInputs for TransactionWitness { let stack_inputs = TransactionKernel::build_input_stack( account.id(), account.init_hash(), - self.input_notes().nullifier_commitment(), + self.input_notes().commitment(), self.block_header().hash(), ); @@ -271,7 +271,7 @@ fn add_account_to_advice_inputs( /// - And all notes details together under the nullifier commitment. /// fn add_input_notes_to_advice_inputs( - notes: &InputNotes, + notes: &InputNotes, tx_args: &TransactionArgs, inputs: &mut AdviceInputs, ) { @@ -337,5 +337,5 @@ fn add_input_notes_to_advice_inputs( } // NOTE: keep map in sync with the `process_input_notes_data` kernel procedure - inputs.extend_map([(notes.nullifier_commitment(), note_data)]); + inputs.extend_map([(notes.commitment(), note_data)]); } diff --git a/miden-tx/src/compiler/mod.rs b/miden-tx/src/compiler/mod.rs index 2438b8dfa..c99f12d9a 100644 --- a/miden-tx/src/compiler/mod.rs +++ b/miden-tx/src/compiler/mod.rs @@ -2,7 +2,7 @@ use alloc::{collections::BTreeMap, vec::Vec}; use miden_objects::{ assembly::{Assembler, AssemblyContext, ModuleAst, ProgramAst}, - transaction::{InputNotes, TransactionScript}, + transaction::{InputNote, InputNotes, TransactionScript}, Felt, NoteError, TransactionScriptError, Word, }; @@ -150,7 +150,7 @@ impl TransactionCompiler { pub fn compile_transaction( &self, account_id: AccountId, - notes: &InputNotes, + notes: &InputNotes, tx_script: Option<&ProgramAst>, ) -> Result { // Fetch the account interface from the `account_procedures` map. Return an error if the @@ -219,7 +219,7 @@ impl TransactionCompiler { fn compile_notes( &self, target_account_interface: &[Digest], - notes: &InputNotes, + notes: &InputNotes, assembly_context: &mut AssemblyContext, ) -> Result, TransactionCompilerError> { let mut note_programs = Vec::new(); diff --git a/miden-tx/src/kernel_tests/test_prologue.rs b/miden-tx/src/kernel_tests/test_prologue.rs index 25c371fdc..8994cd5a7 100644 --- a/miden-tx/src/kernel_tests/test_prologue.rs +++ b/miden-tx/src/kernel_tests/test_prologue.rs @@ -113,7 +113,7 @@ fn global_input_memory_assertions(process: &Process, inputs: &Prepared assert_eq!( read_root_mem_value(process, INPUT_NOTES_COMMITMENT_PTR), - inputs.input_notes().nullifier_commitment().as_elements(), + inputs.input_notes().commitment().as_elements(), "The nullifier commitment should be stored at the INPUT_NOTES_COMMITMENT_PTR" ); diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index 9265d51f6..030f1ac6c 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -3,10 +3,7 @@ use alloc::vec::Vec; use miden_lib::transaction::{ToTransactionKernelInputs, TransactionKernel}; use miden_objects::{ accounts::delta::AccountUpdateDetails, - notes::Nullifier, - transaction::{ - InputNotes, OutputNote, ProvenTransaction, ProvenTransactionBuilder, TransactionWitness, - }, + transaction::{OutputNote, ProvenTransaction, ProvenTransactionBuilder, TransactionWitness}, }; use miden_prover::prove; pub use miden_prover::ProvingOptions; @@ -45,7 +42,7 @@ impl TransactionProver { ) -> Result { let tx_witness: TransactionWitness = transaction.into(); - let input_notes: InputNotes = tx_witness.tx_inputs().input_notes().into(); + let input_notes = tx_witness.tx_inputs().input_notes(); let account_id = tx_witness.account().id(); let block_hash = tx_witness.block_header().hash(); diff --git a/miden-tx/src/verifier/mod.rs b/miden-tx/src/verifier/mod.rs index 111abff2d..af69ecbe9 100644 --- a/miden-tx/src/verifier/mod.rs +++ b/miden-tx/src/verifier/mod.rs @@ -35,7 +35,7 @@ impl TransactionVerifier { let stack_inputs = TransactionKernel::build_input_stack( transaction.account_id(), transaction.account_update().init_state_hash(), - transaction.input_notes().nullifier_commitment(), + transaction.input_notes().commitment(), transaction.block_ref(), ); let stack_outputs = TransactionKernel::build_output_stack( diff --git a/objects/src/transaction/executed_tx.rs b/objects/src/transaction/executed_tx.rs index b20e29ace..38da4c20c 100644 --- a/objects/src/transaction/executed_tx.rs +++ b/objects/src/transaction/executed_tx.rs @@ -1,9 +1,9 @@ use core::cell::OnceCell; use super::{ - Account, AccountDelta, AccountId, AccountStub, AdviceInputs, BlockHeader, InputNotes, - OutputNotes, Program, TransactionArgs, TransactionId, TransactionInputs, TransactionOutputs, - TransactionWitness, + Account, AccountDelta, AccountId, AccountStub, AdviceInputs, BlockHeader, InputNote, + InputNotes, OutputNotes, Program, TransactionArgs, TransactionId, TransactionInputs, + TransactionOutputs, TransactionWitness, }; // EXECUTED TRANSACTION @@ -89,7 +89,7 @@ impl ExecutedTransaction { } /// Returns the notes consumed in this transaction. - pub fn input_notes(&self) -> &InputNotes { + pub fn input_notes(&self) -> &InputNotes { self.tx_inputs.input_notes() } diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index 5e48ab632..b2e110be5 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -1,4 +1,4 @@ -use alloc::{collections::BTreeSet, string::ToString, vec::Vec}; +use alloc::{collections::BTreeSet, vec::Vec}; use core::fmt::Debug; use super::{BlockHeader, ChainMmr, Digest, Felt, Hasher, Word}; @@ -19,7 +19,7 @@ pub struct TransactionInputs { account_seed: Option, block_header: BlockHeader, block_chain: ChainMmr, - input_notes: InputNotes, + input_notes: InputNotes, } impl TransactionInputs { @@ -36,7 +36,7 @@ impl TransactionInputs { account_seed: Option, block_header: BlockHeader, block_chain: ChainMmr, - input_notes: InputNotes, + input_notes: InputNotes, ) -> Result { // validate the seed validate_account_seed(&account, account_seed)?; @@ -113,7 +113,7 @@ impl TransactionInputs { } /// Returns the notes to be consumed in the transaction. - pub fn input_notes(&self) -> &InputNotes { + pub fn input_notes(&self) -> &InputNotes { &self.input_notes } @@ -121,7 +121,9 @@ impl TransactionInputs { // -------------------------------------------------------------------------------------------- /// Consumes these transaction inputs and returns their underlying components. - pub fn into_parts(self) -> (Account, Option, BlockHeader, ChainMmr, InputNotes) { + pub fn into_parts( + self, + ) -> (Account, Option, BlockHeader, ChainMmr, InputNotes) { ( self.account, self.account_seed, @@ -132,60 +134,35 @@ impl TransactionInputs { } } -// TO NULLIFIER TRAIT +// TO INPUT NOTE COMMITMENT // ================================================================================================ -/// Defines how a note object can be reduced to a nullifier. +/// Specifies the data used by the transaction kernel to commit to a note. /// -/// This trait is implemented on both [InputNote] and [Nullifier] so that we can treat them -/// generically as [InputNotes]. -pub trait ToNullifier: - Debug + Clone + PartialEq + Eq + Serializable + Deserializable + Sized -{ +/// The commitment is composed of: +/// +/// - nullifier, which prevents double spend and provides unlinkability. +/// - an optional note_id, which allows for delayed note authentication. +pub trait ToInputNoteCommitments { fn nullifier(&self) -> Nullifier; -} - -impl ToNullifier for InputNote { - fn nullifier(&self) -> Nullifier { - self.note().nullifier() - } -} - -impl ToNullifier for Nullifier { - fn nullifier(&self) -> Nullifier { - *self - } -} - -impl From for InputNotes { - fn from(value: InputNotes) -> Self { - Self { - notes: value.notes.iter().map(|note| note.nullifier()).collect(), - nullifier_commitment: build_nullifier_commitment(&value.notes), - } - } -} - -impl From<&InputNotes> for InputNotes { - fn from(value: &InputNotes) -> Self { - Self { - notes: value.notes.iter().map(|note| note.nullifier()).collect(), - nullifier_commitment: build_nullifier_commitment(&value.notes), - } - } + fn note_id(&self) -> Option; } // INPUT NOTES // ================================================================================================ /// Input notes for a transaction, empty if the transaction does not consume notes. +/// +/// This structure is generic over `T`, so it can be used to create the input notes for transaction +/// execution, which require the note's details to run the transaction kernel, and the input notes +/// for proof verification, which require only the commitment data. #[derive(Debug, Clone)] -pub struct InputNotes { +pub struct InputNotes { notes: Vec, - nullifier_commitment: Digest, + commitment: Digest, } -impl InputNotes { +impl InputNotes { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- /// Returns new [InputNotes] instantiated from the provided vector of notes. @@ -209,9 +186,9 @@ impl InputNotes { } } - let nullifier_commitment = build_nullifier_commitment(¬es); + let commitment = build_input_note_commitment(¬es); - Ok(Self { notes, nullifier_commitment }) + Ok(Self { notes, commitment }) } // PUBLIC ACCESSORS @@ -221,11 +198,11 @@ impl InputNotes { /// /// For non empty lists the commitment is defined as: /// - /// > hash(nullifier_0 || ZERO || nullifier_1 || ZERO || .. || nullifier_n || ZERO) + /// > hash(nullifier_0 || noteid0_or_zero || nullifier_1 || noteid1_or_zero || .. || nullifier_n || noteidn_or_zero) /// /// Otherwise defined as ZERO for empty lists. - pub fn nullifier_commitment(&self) -> Digest { - self.nullifier_commitment + pub fn commitment(&self) -> Digest { + self.commitment } /// Returns total number of input notes. @@ -260,7 +237,7 @@ impl InputNotes { } } -impl IntoIterator for InputNotes { +impl IntoIterator for InputNotes { type Item = T; type IntoIter = alloc::vec::IntoIter; @@ -269,19 +246,28 @@ impl IntoIterator for InputNotes { } } -impl PartialEq for InputNotes { +impl<'a, T> IntoIterator for &'a InputNotes { + type Item = &'a T; + type IntoIter = alloc::slice::Iter<'a, T>; + + fn into_iter(self) -> alloc::slice::Iter<'a, T> { + self.notes.iter() + } +} + +impl PartialEq for InputNotes { fn eq(&self, other: &Self) -> bool { self.notes == other.notes } } -impl Eq for InputNotes {} +impl Eq for InputNotes {} -impl Default for InputNotes { +impl Default for InputNotes { fn default() -> Self { Self { notes: Vec::new(), - nullifier_commitment: build_nullifier_commitment::(&[]), + commitment: build_input_note_commitment::(&[]), } } } @@ -289,7 +275,7 @@ impl Default for InputNotes { // SERIALIZATION // ------------------------------------------------------------------------------------------------ -impl Serializable for InputNotes { +impl Serializable for InputNotes { fn write_into(&self, target: &mut W) { // assert is OK here because we enforce max number of notes in the constructor assert!(self.notes.len() <= u16::MAX.into()); @@ -298,18 +284,18 @@ impl Serializable for InputNotes { } } -impl Deserializable for InputNotes { +impl Deserializable for InputNotes { fn read_from(source: &mut R) -> Result { let num_notes = source.read_u16()?; let notes = source.read_many::(num_notes.into())?; - Self::new(notes).map_err(|err| DeserializationError::InvalidValue(err.to_string())) + Self::new(notes).map_err(|err| DeserializationError::InvalidValue(format!("{}", err))) } } // HELPER FUNCTIONS // ------------------------------------------------------------------------------------------------ -fn build_nullifier_commitment(notes: &[T]) -> Digest { +fn build_input_note_commitment(notes: &[T]) -> Digest { // Note: This implementation must be kept in sync with the kernel's `process_input_notes_data` if notes.is_empty() { return Digest::default(); @@ -318,7 +304,8 @@ fn build_nullifier_commitment(notes: &[T]) -> Digest { let mut elements: Vec = Vec::with_capacity(notes.len() * 2); for note in notes { elements.extend_from_slice(note.nullifier().as_elements()); - elements.extend_from_slice(&Word::default()); + elements + .extend_from_slice(¬e.note_id().map_or(Word::default(), |note_id| note_id.into())); } Hasher::hash_elements(&elements) } @@ -392,6 +379,29 @@ fn is_in_block(note: &Note, proof: &NoteInclusionProof, block_header: &BlockHead proof.note_path().verify(note_index, note_hash, &block_header.note_root()) } +impl ToInputNoteCommitments for InputNote { + fn nullifier(&self) -> Nullifier { + self.note().nullifier() + } + + fn note_id(&self) -> Option { + match self { + InputNote::Authenticated { .. } => None, + InputNote::Unauthenticated { note } => Some(note.id()), + } + } +} + +impl ToInputNoteCommitments for &InputNote { + fn nullifier(&self) -> Nullifier { + (*self).nullifier() + } + + fn note_id(&self) -> Option { + (*self).note_id() + } +} + // SERIALIZATION // ------------------------------------------------------------------------------------------------ diff --git a/objects/src/transaction/mod.rs b/objects/src/transaction/mod.rs index 6fe09cdac..a25c14589 100644 --- a/objects/src/transaction/mod.rs +++ b/objects/src/transaction/mod.rs @@ -17,10 +17,12 @@ mod tx_witness; pub use chain_mmr::ChainMmr; pub use executed_tx::ExecutedTransaction; -pub use inputs::{InputNote, InputNotes, ToNullifier, TransactionInputs}; +pub use inputs::{InputNote, InputNotes, ToInputNoteCommitments, TransactionInputs}; pub use outputs::{OutputNote, OutputNotes, TransactionOutputs}; pub use prepared_tx::PreparedTransaction; -pub use proven_tx::{ProvenTransaction, ProvenTransactionBuilder, TxAccountUpdate}; +pub use proven_tx::{ + InputNoteCommitment, ProvenTransaction, ProvenTransactionBuilder, TxAccountUpdate, +}; pub use transaction_id::TransactionId; pub use tx_args::{TransactionArgs, TransactionScript}; pub use tx_witness::TransactionWitness; diff --git a/objects/src/transaction/prepared_tx.rs b/objects/src/transaction/prepared_tx.rs index 31e798d66..80d20b9ac 100644 --- a/objects/src/transaction/prepared_tx.rs +++ b/objects/src/transaction/prepared_tx.rs @@ -1,4 +1,6 @@ -use super::{Account, BlockHeader, InputNotes, Program, TransactionArgs, TransactionInputs}; +use super::{ + Account, BlockHeader, InputNote, InputNotes, Program, TransactionArgs, TransactionInputs, +}; // PREPARED TRANSACTION // ================================================================================================ @@ -44,7 +46,7 @@ impl PreparedTransaction { } /// Returns the notes to be consumed in this transaction. - pub fn input_notes(&self) -> &InputNotes { + pub fn input_notes(&self) -> &InputNotes { self.tx_inputs.input_notes() } diff --git a/objects/src/transaction/proven_tx.rs b/objects/src/transaction/proven_tx.rs index 080eb9011..62803df04 100644 --- a/objects/src/transaction/proven_tx.rs +++ b/objects/src/transaction/proven_tx.rs @@ -2,9 +2,13 @@ use alloc::{string::ToString, vec::Vec}; use miden_verifier::ExecutionProof; -use super::{AccountId, Digest, InputNotes, Nullifier, OutputNote, OutputNotes, TransactionId}; +use super::ToInputNoteCommitments; use crate::{ accounts::delta::AccountUpdateDetails, + notes::NoteId, + transaction::{ + AccountId, Digest, InputNotes, Nullifier, OutputNote, OutputNotes, TransactionId, + }, utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, ProvenTransactionError, }; @@ -22,8 +26,8 @@ pub struct ProvenTransaction { /// Account update data. account_update: TxAccountUpdate, - /// A list of nullifiers for all notes consumed by the transaction. - input_notes: InputNotes, + /// Commited details of all notes consumed by the transaction. + input_notes: InputNotes, /// Notes created by the transaction. For private notes, this will contain only note headers, /// while for public notes this will also contain full note details. @@ -53,7 +57,7 @@ impl ProvenTransaction { } /// Returns a reference to the notes consumed by the transaction. - pub fn input_notes(&self) -> &InputNotes { + pub fn input_notes(&self) -> &InputNotes { &self.input_notes } @@ -135,7 +139,7 @@ impl Deserializable for ProvenTransaction { fn read_from(source: &mut R) -> Result { let account_update = TxAccountUpdate::read_from(source)?; - let input_notes = InputNotes::::read_from(source)?; + let input_notes = >::read_from(source)?; let output_notes = OutputNotes::read_from(source)?; let block_ref = Digest::read_from(source)?; @@ -144,7 +148,7 @@ impl Deserializable for ProvenTransaction { let id = TransactionId::new( account_update.init_state_hash(), account_update.final_state_hash(), - input_notes.nullifier_commitment(), + input_notes.commitment(), output_notes.commitment(), ); @@ -181,8 +185,8 @@ pub struct ProvenTransactionBuilder { /// State changes to the account due to the transaction. account_update_details: AccountUpdateDetails, - /// List of [Nullifier]s of all consumed notes by the transaction. - input_notes: Vec, + /// List of [InputNoteCommitment]s of all consumed notes by the transaction. + input_notes: Vec, /// List of [OutputNote]s of all notes created by the transaction. output_notes: Vec, @@ -228,11 +232,13 @@ impl ProvenTransactionBuilder { } /// Add notes consumed by the transaction. - pub fn add_input_notes(mut self, notes: T) -> Self + pub fn add_input_notes(mut self, notes: T) -> Self where - T: IntoIterator, + T: IntoIterator, + I: ToInputNoteCommitments, { - self.input_notes.extend(notes); + self.input_notes + .extend(notes.into_iter().map(|note| InputNoteCommitment::from_commitable(¬e))); self } @@ -259,7 +265,7 @@ impl ProvenTransactionBuilder { let id = TransactionId::new( self.initial_account_hash, self.final_account_hash, - input_notes.nullifier_commitment(), + input_notes.commitment(), output_notes.commitment(), ); let account_update = TxAccountUpdate::new( @@ -370,6 +376,58 @@ impl Deserializable for TxAccountUpdate { } } +// INPUT NOTE COMMITMENT +// ================================================================================================ + +/// The commitment of a consumed notes. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InputNoteCommitment { + nullifier: Nullifier, + note_id: Option, +} + +impl ToInputNoteCommitments for InputNoteCommitment { + fn nullifier(&self) -> Nullifier { + self.nullifier + } + + fn note_id(&self) -> Option { + self.note_id + } +} + +impl InputNoteCommitment { + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Converts a value of type `T` into a [InputNoteCommitment]. + fn from_commitable(value: &T) -> Self { + Self { + nullifier: value.nullifier(), + note_id: value.note_id(), + } + } +} + +// SERIALIZATION +// ------------------------------------------------------------------------------------------------ + +impl Serializable for InputNoteCommitment { + fn write_into(&self, target: &mut W) { + self.nullifier.write_into(target); + self.note_id.write_into(target); + } +} + +impl Deserializable for InputNoteCommitment { + fn read_from(source: &mut R) -> Result { + let nullifier = Nullifier::read_from(source)?; + let note_id = >::read_from(source)?; + + Ok(Self { nullifier, note_id }) + } +} + // TESTS // ================================================================================================ diff --git a/objects/src/transaction/transaction_id.rs b/objects/src/transaction/transaction_id.rs index c5c011664..c294a0697 100644 --- a/objects/src/transaction/transaction_id.rs +++ b/objects/src/transaction/transaction_id.rs @@ -78,7 +78,7 @@ impl From<&ProvenTransaction> for TransactionId { Self::new( tx.account_update().init_state_hash(), tx.account_update().final_state_hash(), - tx.input_notes().nullifier_commitment(), + tx.input_notes().commitment(), tx.output_notes().commitment(), ) } @@ -86,7 +86,7 @@ impl From<&ProvenTransaction> for TransactionId { impl From<&ExecutedTransaction> for TransactionId { fn from(tx: &ExecutedTransaction) -> Self { - let input_notes_hash = tx.input_notes().nullifier_commitment(); + let input_notes_hash = tx.input_notes().commitment(); let output_notes_hash = tx.output_notes().commitment(); Self::new( tx.initial_account().init_hash(), diff --git a/objects/src/transaction/tx_witness.rs b/objects/src/transaction/tx_witness.rs index c97024573..dfde87d8e 100644 --- a/objects/src/transaction/tx_witness.rs +++ b/objects/src/transaction/tx_witness.rs @@ -1,5 +1,6 @@ use super::{ - Account, AdviceInputs, BlockHeader, InputNotes, Program, TransactionArgs, TransactionInputs, + Account, AdviceInputs, BlockHeader, InputNote, InputNotes, Program, TransactionArgs, + TransactionInputs, }; // TRANSACTION WITNESS @@ -61,7 +62,7 @@ impl TransactionWitness { } /// Returns the notes consumed in this transaction. - pub fn input_notes(&self) -> &InputNotes { + pub fn input_notes(&self) -> &InputNotes { self.tx_inputs.input_notes() }