Skip to content

Commit

Permalink
Merge pull request #180 from 0xPolygonMiden/frisitano-tx-verifier
Browse files Browse the repository at this point in the history
Introduce `TransactionVerifier`
  • Loading branch information
frisitano authored Aug 2, 2023
2 parents 504065c + 74af9ef commit e2fdd05
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 134 deletions.
1 change: 1 addition & 0 deletions miden-tx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
149 changes: 94 additions & 55 deletions miden-tx/src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -202,27 +201,41 @@ 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 note script hashes and transaction script hash.
pub fn build_program_info(
&self,
note_script_hashes: Vec<Digest>,
tx_script_hash: Option<Digest>,
) -> 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(note_script_hashes);
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<CodeBlock>] 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<CodeBlock>), TransactionCompilerError> {
// Create vectors to store note programs and note roots
let mut note_script_hashes = Vec::new();
let mut note_programs = Vec::new();
let mut note_roots = Vec::new();

// Create and verify note programs. Note programs are verified against the target account.
for note in notes.iter() {
Expand All @@ -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_roots.push(note_root);
note_script_hashes.push(note_root.hash());
note_programs.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]));
}
// build the note program tree
let note_tree_root = self.build_note_program_tree(note_script_hashes);

// 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;
}

Ok((
note_programs.into_iter().next().expect("a single root code block exists"),
note_roots,
))
Ok((note_tree_root, note_programs))
}

/// Returns a ([CodeBlock], Option<Digest>) 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<ProgramAst>,
assembly_context: &mut AssemblyContext,
Expand Down Expand Up @@ -322,6 +304,63 @@ impl TransactionComplier {
NoteTarget::Procedures(procs) => Ok(procs),
}
}

/// 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_script_hashes: Vec<Digest>) -> CodeBlock {
let mut note_programs = note_script_hashes
.into_iter()
.map(|note_hash| {
CodeBlock::new_join([self.note_setup.clone(), CodeBlock::new_call(note_hash)])
})
.collect::<Vec<_>>();

// 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 {
Expand Down
7 changes: 7 additions & 0 deletions miden-tx/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use super::{
TransactionResultError,
};
use miden_objects::TransactionWitnessError;
use miden_verifier::VerificationError;

#[derive(Debug)]
pub enum TransactionError {
Expand Down Expand Up @@ -42,6 +43,12 @@ pub enum TransactionProverError {
CorruptTransactionWitnessConsumedNoteData(TransactionWitnessError),
}

#[derive(Debug)]
pub enum TransactionVerifierError {
TransactionVerificationFailed(VerificationError),
InsufficientProofSecurityLevel(u32, u32),
}

#[derive(Debug)]
pub enum DataStoreError {
AccountNotFound(AccountId),
Expand Down
5 changes: 4 additions & 1 deletion miden-tx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use assembly::{
ast::{ModuleAst, ProgramAst},
Assembler, AssemblyContext, AssemblyContextType, AssemblyError,
};
use crypto::{hash::rpo::RpoDigest as Digest, merkle::NodeIndex};
use crypto::{hash::rpo::Rpo256 as Hasher, hash::rpo::RpoDigest as Digest, merkle::NodeIndex};
use miden_core::{code_blocks::CodeBlock, utils::collections::BTreeMap, Operation, Program};
use miden_lib::{MidenLib, SatKernel};
use miden_objects::{
Expand All @@ -22,10 +22,13 @@ mod executor;
pub use error::TransactionError;
use error::{
DataStoreError, TransactionCompilerError, TransactionExecutorError, TransactionProverError,
TransactionVerifierError,
};
pub use executor::TransactionExecutor;
mod prover;
pub use prover::TransactionProver;
mod verifier;
pub use verifier::TransactionVerifier;

#[cfg(test)]
mod tests;
4 changes: 1 addition & 3 deletions miden-tx/src/prover/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -61,7 +61,6 @@ impl TransactionProver {
consumed_notes.into(),
created_notes.into(),
tx_script_root,
tx_program.hash(),
block_header.hash(),
proof,
))
Expand Down Expand Up @@ -112,7 +111,6 @@ impl TransactionProver {
consumed_notes_info,
created_notes.into(),
tx_script_root,
tx_program.hash(),
block_hash,
proof,
))
Expand Down
26 changes: 7 additions & 19 deletions miden-tx/src/tests.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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());

Expand All @@ -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(96);
assert!(verifier.verify(proven_transaction).is_ok());
}

#[test]
Expand All @@ -297,23 +297,11 @@ fn test_prove_and_verify_with_tx_executor() {
.prepare_transaction(account_id, block_ref, &note_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(96);
assert!(verifier.verify(proven_transaction).is_ok());
}
Loading

0 comments on commit e2fdd05

Please sign in to comment.