From e42f42a9f4d5e58cddf6cb0313b00cc3c11e0dd2 Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 31 Jul 2023 10:19:11 +0200 Subject: [PATCH] feat: introduce TransactionVerifier --- miden-tx/Cargo.toml | 1 + miden-tx/src/compiler/mod.rs | 149 +++++++++++++++++---------- miden-tx/src/lib.rs | 2 + miden-tx/src/prover/mod.rs | 4 +- miden-tx/src/tests.rs | 26 ++--- miden-tx/src/verifier/mod.rs | 40 +++++++ objects/src/transaction/proven_tx.rs | 8 -- 7 files changed, 146 insertions(+), 84 deletions(-) create mode 100644 miden-tx/src/verifier/mod.rs diff --git a/miden-tx/Cargo.toml b/miden-tx/Cargo.toml index 1a4bbec52..ded21cc26 100644 --- a/miden-tx/Cargo.toml +++ b/miden-tx/Cargo.toml @@ -24,6 +24,7 @@ miden-core = { package = "miden-core", git = "https://github.com/0xPolygonMiden/ miden-objects = { package = "miden-objects", path = "../objects", default-features = false } miden-lib = { package = "miden-lib", path = "../miden-lib" } miden-stdlib = { package = "miden-stdlib", git = "https://github.com/0xPolygonMiden/miden-vm.git", branch = "next", default-features = false } +miden-verifier = { package = "miden-verifier", git = "https://github.com/0xPolygonMiden/miden-vm.git", branch = "next", default-features = false } [dev-dependencies] miden-objects = { package = "miden-objects", path = "../objects", default-features = false } diff --git a/miden-tx/src/compiler/mod.rs b/miden-tx/src/compiler/mod.rs index 32ad1bd0b..2e074fbda 100644 --- a/miden-tx/src/compiler/mod.rs +++ b/miden-tx/src/compiler/mod.rs @@ -3,6 +3,7 @@ use super::{ Digest, MidenLib, ModuleAst, Note, NoteScript, Operation, Program, ProgramAst, SatKernel, StdLibrary, TransactionCompilerError, }; +use miden_core::ProgramInfo; #[cfg(test)] mod tests; @@ -169,24 +170,22 @@ impl TransactionComplier { let mut assembly_context = AssemblyContext::new(AssemblyContextType::Program); // Create note tree and note [CodeBlock]s - let (note_tree_root, note_roots) = - self.create_note_program_tree(&target_account_interface, notes, &mut assembly_context)?; + let (note_tree_root, note_roots) = self.compile_and_build_note_program_tree( + &target_account_interface, + notes, + &mut assembly_context, + )?; // Create the transaction program - let (tx_script_code_block, tx_script_hash) = - self.create_tx_program(tx_script, &mut assembly_context, target_account_interface)?; + let (tx_script_code_block, tx_script_hash) = self.create_tx_script_program( + tx_script, + &mut assembly_context, + target_account_interface, + )?; - // Merge transaction script code block and epilogue code block - let tx_script_and_epilogue = CodeBlock::new_join([ - CodeBlock::new_call(tx_script_code_block.hash()), - self.epilogue.clone(), - ]); - - // Merge prologue and note script tree - let prologue_and_notes = CodeBlock::new_join([self.prologue.clone(), note_tree_root]); - - // Merge prologue, note tree, tx script and epilogue - let program_root = CodeBlock::new_join([prologue_and_notes, tx_script_and_epilogue]); + // build transaction program + let program_root = + self.build_transaction_program(note_tree_root, tx_script_code_block.hash()); // Create [CodeBlockTable] from [AssemblyContext] let mut cb_table = self @@ -202,26 +201,40 @@ impl TransactionComplier { // insert transaction script into [CodeBlockTable] cb_table.insert(tx_script_code_block); - // Create transaction program + // Create transaction program with kernel let program = Program::with_kernel(program_root, self.assembler.kernel().clone(), cb_table); // Create compiled transaction Ok((program, tx_script_hash)) } + /// Returns a [ProgramInfo] which contains the hash of the transaction program associated with + /// the provided consumed notes hashes and transaction script hash and the kernel. + pub fn build_program_info( + &self, + notes: Vec, + tx_script_hash: Option, + ) -> ProgramInfo { + let tx_script_hash = + tx_script_hash.unwrap_or(CodeBlock::new_span(vec![Operation::Noop]).hash()); + let note_tree_root = self.build_note_program_tree(notes); + let transaction_program = self.build_transaction_program(note_tree_root, tx_script_hash); + ProgramInfo::new(transaction_program.hash(), self.assembler.kernel().clone()) + } + // HELPER METHODS // -------------------------------------------------------------------------------------------- /// Returns a [CodeBlock] which contains the note program tree root and a [Vec] which /// contains the [CodeBlock]s associated with the notes. - fn create_note_program_tree( + fn compile_and_build_note_program_tree( &mut self, target_account_interface: &[Digest], notes: &[Note], assembly_context: &mut AssemblyContext, ) -> Result<(CodeBlock, Vec), TransactionCompilerError> { // Create vectors to store note programs and note roots - let mut note_programs = Vec::new(); + let mut note_hashes = Vec::new(); let mut note_roots = Vec::new(); // Create and verify note programs. Note programs are verified against the target account. @@ -235,52 +248,21 @@ impl TransactionComplier { TransactionCompilerError::NoteIncompatibleWithAccountInterface(note_root.hash()) }, )?; - note_programs.push(CodeBlock::new_join([ - self.note_setup.clone(), - CodeBlock::new_call(note_root.hash()), - ])); + note_hashes.push(note_root.hash()); note_roots.push(note_root); } - // Push note processing teardown onto the note programs vector - note_programs.push(self.note_processing_teardown.clone()); - - // Merge the note programs into a tree using join blocks - while note_programs.len() != 1 { - // TODO: We should optimize this in the future - however maybe not required as this - // part will be handled by a pcall-like operation in the future. - // Pad note programs to an even number using a [Operation::Noop] span block - if note_programs.len() % 2 != 0 { - note_programs.push(CodeBlock::new_span(vec![Operation::Noop])); - } - - // convert vector into an iterator - let mut note_programs_iter = note_programs.into_iter(); - - // create a temporary vector to hold the merged CodeBlocks - let mut note_programs_temp = Vec::new(); + // build the note program tree + let note_tree_root = self.build_note_program_tree(note_hashes); - // Consume two code blocks at a time and merge them into a single code block - while let (Some(left_code_block), Some(right_code_block)) = - (note_programs_iter.next(), note_programs_iter.next()) - { - note_programs_temp.push(CodeBlock::new_join([left_code_block, right_code_block])); - } - - note_programs = note_programs_temp; - } - - Ok(( - note_programs.into_iter().next().expect("a single root code block exists"), - note_roots, - )) + Ok((note_tree_root, note_roots)) } /// Returns a ([CodeBlock], Option) tuple where the first element is the compiled /// transaction script program and the second element is the hash of the transaction script /// program. If no transaction script is provided, the first element is a [CodeBlock] containing /// a single [Operation::Noop] and the second element is `None`. - fn create_tx_program( + fn create_tx_script_program( &mut self, tx_script: Option, assembly_context: &mut AssemblyContext, @@ -322,6 +304,65 @@ impl TransactionComplier { NoteTarget::Procedures(procs) => Ok(procs), } } + + // HELPERS + // -------------------------------------------------------------------------------------------- + /// Returns a [CodeBlock] which represents the transaction program. + fn build_transaction_program( + &self, + note_program_tree: CodeBlock, + tx_script_hash: Digest, + ) -> CodeBlock { + // Merge transaction script code block and epilogue code block + let tx_script_and_epilogue = + CodeBlock::new_join([CodeBlock::new_call(tx_script_hash), self.epilogue.clone()]); + + // Merge prologue and note script tree + let prologue_and_notes = CodeBlock::new_join([self.prologue.clone(), note_program_tree]); + + // Merge prologue, note tree, tx script and epilogue + CodeBlock::new_join([prologue_and_notes, tx_script_and_epilogue]) + } + + /// Returns a [CodeBlock] which represents the note program tree. + fn build_note_program_tree(&self, note_hashes: Vec) -> CodeBlock { + let mut note_programs = note_hashes + .into_iter() + .map(|note_hash| { + CodeBlock::new_join([self.note_setup.clone(), CodeBlock::new_call(note_hash)]) + }) + .collect::>(); + + // Push note processing teardown onto the note programs vector + note_programs.push(self.note_processing_teardown.clone()); + + // Merge the note programs into a tree using join blocks + while note_programs.len() != 1 { + // TODO: We should optimize this in the future - however maybe not required as this + // part will be handled by a pcall-like operation in the future. + // Pad note programs to an even number using a [Operation::Noop] span block + if note_programs.len() % 2 != 0 { + note_programs.push(CodeBlock::new_span(vec![Operation::Noop])); + } + + // convert vector into an iterator + let mut note_programs_iter = note_programs.into_iter(); + + // create a temporary vector to hold the merged CodeBlocks + let mut note_programs_temp = Vec::new(); + + // Consume two code blocks at a time and merge them into a single code block + while let (Some(left_code_block), Some(right_code_block)) = + (note_programs_iter.next(), note_programs_iter.next()) + { + note_programs_temp.push(CodeBlock::new_join([left_code_block, right_code_block])); + } + + note_programs = note_programs_temp; + } + + note_programs.into_iter().next().expect("a single root code block exists") + } } impl Default for TransactionComplier { diff --git a/miden-tx/src/lib.rs b/miden-tx/src/lib.rs index c02459b47..473cc8933 100644 --- a/miden-tx/src/lib.rs +++ b/miden-tx/src/lib.rs @@ -26,6 +26,8 @@ use error::{ pub use executor::TransactionExecutor; mod prover; pub use prover::TransactionProver; +mod verifier; +pub use verifier::TransactionVerifier; #[cfg(test)] mod tests; diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index 9a078e37f..78770c05c 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -51,7 +51,7 @@ impl TransactionProver { let created_notes = CreatedNotes::try_from_vm_result(&outputs, &stack, &map, &store) .map_err(TransactionProverError::TransactionResultError)?; - let (account, block_header, _chain, consumed_notes, tx_program, tx_script_root) = + let (account, block_header, _chain, consumed_notes, _tx_program, tx_script_root) = transaction.into_parts(); Ok(ProvenTransaction::new( @@ -61,7 +61,6 @@ impl TransactionProver { consumed_notes.into(), created_notes.into(), tx_script_root, - tx_program.hash(), block_header.hash(), proof, )) @@ -112,7 +111,6 @@ impl TransactionProver { consumed_notes_info, created_notes.into(), tx_script_root, - tx_program.hash(), block_hash, proof, )) diff --git a/miden-tx/src/tests.rs b/miden-tx/src/tests.rs index ea614f876..9cf8cac43 100644 --- a/miden-tx/src/tests.rs +++ b/miden-tx/src/tests.rs @@ -1,13 +1,12 @@ use super::{ Account, AccountId, BlockHeader, ChainMmr, DataStore, DataStoreError, Note, NoteOrigin, - TransactionExecutor, TransactionProver, + TransactionExecutor, TransactionProver, TransactionVerifier, }; use assembly::{ ast::{ModuleAst, ProgramAst}, Assembler, }; use crypto::{StarkField, ONE}; -use miden_core::ProgramInfo; use miden_objects::{ mock::{ mock_inputs, prepare_word, CHILD_ROOT_PARENT_LEAF_INDEX, CHILD_SMT_DEPTH, @@ -249,7 +248,7 @@ fn test_transaction_result_account_delta() { } #[test] -fn test_prove_witness() { +fn test_prove_witness_and_verify() { let data_store = MockDataStore::new(); let mut executor = TransactionExecutor::new(data_store.clone()); @@ -272,9 +271,10 @@ fn test_prove_witness() { // prove the transaction with the witness let proof_options = ProvingOptions::default(); let prover = TransactionProver::new(proof_options); - let proven_transaction = prover.prove_transaction_witness(witness); + let proven_transaction = prover.prove_transaction_witness(witness).unwrap(); - assert!(proven_transaction.is_ok()); + let verifier = TransactionVerifier::new(); + assert!(verifier.verify(proven_transaction).is_ok()); } #[test] @@ -297,23 +297,11 @@ fn test_prove_and_verify_with_tx_executor() { .prepare_transaction(account_id, block_ref, ¬e_origins, None) .unwrap(); - // extract transaction data for later consumption - let program_hash = prepared_transaction.tx_program().hash(); - let kernel = prepared_transaction.tx_program().kernel().clone(); - // prove transaction let proof_options = ProvingOptions::default(); let prover = TransactionProver::new(proof_options); let proven_transaction = prover.prove_prepared_transaction(prepared_transaction).unwrap(); - let stack_inputs = proven_transaction.build_stack_inputs(); - let stack_outputs = proven_transaction.build_stack_outputs(); - let program_info = ProgramInfo::new(program_hash, kernel); - let result = miden_verifier::verify( - program_info, - stack_inputs, - stack_outputs, - proven_transaction.proof().clone(), - ); - assert!(result.is_ok()); + let verifier = TransactionVerifier::new(); + assert!(verifier.verify(proven_transaction).is_ok()); } diff --git a/miden-tx/src/verifier/mod.rs b/miden-tx/src/verifier/mod.rs new file mode 100644 index 000000000..4141246c6 --- /dev/null +++ b/miden-tx/src/verifier/mod.rs @@ -0,0 +1,40 @@ +use crate::TransactionComplier; +use miden_objects::transaction::ProvenTransaction; +use miden_verifier::{verify, VerificationError}; + +/// The [TransactionVerifier] is used to verify a [ProvenTransaction]. +/// +/// The [TransactionVerifier] contains a [Kernel] object which we use to verify a given +/// transaction against. +pub struct TransactionVerifier { + compiler: TransactionComplier, +} + +impl TransactionVerifier { + /// Creates a new [TransactionVerifier] object. + pub fn new() -> Self { + let compiler = TransactionComplier::new(); + Self { compiler } + } + + /// Verifies the provided [ProvenTransaction] against the kernel. + pub fn verify(&self, transaction: ProvenTransaction) -> Result { + let consumed_notes_hashes = + transaction.consumed_notes().iter().map(|x| x.script_root()).collect(); + let program_info = self + .compiler + .build_program_info(consumed_notes_hashes, transaction.tx_script_root()); + verify( + program_info, + transaction.build_stack_inputs(), + transaction.build_stack_outputs(), + transaction.proof().clone(), + ) + } +} + +impl Default for TransactionVerifier { + fn default() -> Self { + Self::new() + } +} diff --git a/objects/src/transaction/proven_tx.rs b/objects/src/transaction/proven_tx.rs index 3e13da1e5..f192e4116 100644 --- a/objects/src/transaction/proven_tx.rs +++ b/objects/src/transaction/proven_tx.rs @@ -24,7 +24,6 @@ pub struct ProvenTransaction { consumed_notes: Vec, created_notes: Vec, tx_script_root: Option, - program_hash: Digest, block_ref: Digest, proof: ExecutionProof, } @@ -39,7 +38,6 @@ impl ProvenTransaction { consumed_notes: Vec, created_notes: Vec, tx_script_root: Option, - program_hash: Digest, block_ref: Digest, proof: ExecutionProof, ) -> Self { @@ -50,7 +48,6 @@ impl ProvenTransaction { consumed_notes, created_notes, tx_script_root, - program_hash, block_ref, proof, } @@ -87,11 +84,6 @@ impl ProvenTransaction { self.tx_script_root } - /// Returns the transaction program info. - pub fn program_hash(&self) -> Digest { - self.program_hash - } - /// Returns the proof of the transaction. pub fn proof(&self) -> &ExecutionProof { &self.proof