Skip to content

Commit

Permalink
feat: add TransactionProver
Browse files Browse the repository at this point in the history
  • Loading branch information
frisitano committed Jul 31, 2023
1 parent 75e00fc commit f314325
Show file tree
Hide file tree
Showing 12 changed files with 312 additions and 45 deletions.
8 changes: 8 additions & 0 deletions miden-tx/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::{
AccountError, AccountId, AssemblyError, Digest, ExecutionError, NodeIndex,
TransactionResultError,
};
use miden_objects::TransactionWitnessError;

#[derive(Debug)]
pub enum TransactionError {
Expand Down Expand Up @@ -34,6 +35,13 @@ pub enum TransactionExecutorError {
TransactionResultError(TransactionResultError),
}

#[derive(Debug)]
pub enum TransactionProverError {
ProveTransactionProgramFailed(ExecutionError),
TransactionResultError(TransactionResultError),
CorruptTransactionWitnessConsumedNoteData(TransactionWitnessError),
}

#[derive(Debug)]
pub enum DataStoreError {
AccountNotFound(AccountId),
Expand Down
5 changes: 1 addition & 4 deletions miden-tx/src/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,6 @@ impl<D: DataStore> TransactionExecutor<D> {
.map_err(TransactionExecutorError::TransactionResultError)
}

// HELPER METHODS
// --------------------------------------------------------------------------------------------

/// Fetches the data required to execute the transaction from the [DataStore], compiles the
/// transaction into an executable program using the [TransactionComplier], and returns a
/// [PreparedTransaction].
Expand All @@ -139,7 +136,7 @@ impl<D: DataStore> TransactionExecutor<D> {
/// Returns an error if:
/// - If required data can not be fetched from the [DataStore].
/// - If the transaction can not be compiled.
fn prepare_transaction(
pub fn prepare_transaction(
&mut self,
account_id: AccountId,
block_ref: u32,
Expand Down
6 changes: 5 additions & 1 deletion miden-tx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ use data::DataStore;
mod error;
mod executor;
pub use error::TransactionError;
use error::{DataStoreError, TransactionCompilerError, TransactionExecutorError};
use error::{
DataStoreError, TransactionCompilerError, TransactionExecutorError, TransactionProverError,
};
pub use executor::TransactionExecutor;
mod prover;
pub use prover::TransactionProver;

#[cfg(test)]
mod tests;
120 changes: 120 additions & 0 deletions miden-tx/src/prover/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use super::TransactionProverError;
use miden_objects::{
transaction::{
CreatedNotes, FinalAccountStub, PreparedTransaction, ProvenTransaction, TransactionWitness,
},
TryFromVmResult,
};
use miden_prover::{prove, ProvingOptions};
use processor::MemAdviceProvider;

/// The [TransactionProver] is a stateless component which is responsible for proving transactions.
///
/// The [TransactionProver] exposes the `prove_transaction` method which takes a [TransactionWitness] and
/// produces a [ProvenTransaction].
pub struct TransactionProver {
proof_options: ProvingOptions,
}

impl TransactionProver {
// CONSTRUCTOR
// --------------------------------------------------------------------------------------------
/// Creates a new [TransactionProver] instance.
pub fn new(proof_options: ProvingOptions) -> Self {
Self { proof_options }
}

/// Proves the provided [PreparedTransaction] and returns a [ProvenTransaction].
///
/// # Errors
/// - If the transaction program cannot be proven.
/// - If the transaction result is corrupt.
pub fn prove_prepared_transaction(
&self,
transaction: PreparedTransaction,
) -> Result<ProvenTransaction, TransactionProverError> {
// prove transaction program
let mut advice_provider: MemAdviceProvider = transaction.advice_provider_inputs().into();
let (outputs, proof) = prove(
transaction.tx_program(),
transaction.stack_inputs(),
&mut advice_provider,
self.proof_options.clone(),
)
.map_err(TransactionProverError::ProveTransactionProgramFailed)?;

// extract transaction outputs and process transaction data
let (stack, map, store) = advice_provider.into_parts();
let final_account_stub =
FinalAccountStub::try_from_vm_result(&outputs, &stack, &map, &store)
.map_err(TransactionProverError::TransactionResultError)?;
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) =
transaction.into_parts();

Ok(ProvenTransaction::new(
account.id(),
account.hash(),
final_account_stub.0.hash(),
consumed_notes.into(),
created_notes.into(),
tx_script_root,
tx_program.hash(),
block_header.hash(),
proof,
))
}

/// Proves the provided [TransactionWitness] and returns a [ProvenTransaction].
///
/// # Errors
/// - If the consumed note data in the transaction witness is corrupt.
/// - If the transaction program cannot be proven.
/// - If the transaction result is corrupt.
pub fn prove_transaction_witness(
&self,
tx_witness: TransactionWitness,
) -> Result<ProvenTransaction, TransactionProverError> {
// extract required data from the transaction witness
let stack_inputs = tx_witness.get_stack_inputs();
let consumed_notes_info = tx_witness
.consumed_notes_info()
.map_err(TransactionProverError::CorruptTransactionWitnessConsumedNoteData)?;
let (
account_id,
initial_account_hash,
block_hash,
_consumed_notes_hash,
tx_script_root,
tx_program,
advice_witness,
) = tx_witness.into_parts();

let mut advice_provider: MemAdviceProvider = advice_witness.into();
let (outputs, proof) =
prove(&tx_program, stack_inputs, &mut advice_provider, self.proof_options.clone())
.map_err(TransactionProverError::ProveTransactionProgramFailed)?;

// extract transaction outputs and process transaction data
let (stack, map, store) = advice_provider.into_parts();
let final_account_stub =
FinalAccountStub::try_from_vm_result(&outputs, &stack, &map, &store)
.map_err(TransactionProverError::TransactionResultError)?;
let created_notes = CreatedNotes::try_from_vm_result(&outputs, &stack, &map, &store)
.map_err(TransactionProverError::TransactionResultError)?;

Ok(ProvenTransaction::new(
account_id,
initial_account_hash,
final_account_stub.0.hash(),
consumed_notes_info,
created_notes.into(),
tx_script_root,
tx_program.hash(),
block_hash,
proof,
))
}
}
76 changes: 74 additions & 2 deletions miden-tx/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
use super::{
Account, AccountId, BlockHeader, ChainMmr, DataStore, DataStoreError, Note, NoteOrigin,
TransactionExecutor,
TransactionExecutor, TransactionProver,
};
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,
CHILD_STORAGE_INDEX_0,
},
transaction::{testing::FinalAccountStub, CreatedNotes},
transaction::{CreatedNotes, FinalAccountStub},
AccountCode, TryFromVmResult,
};
use miden_prover::ProvingOptions;
use processor::MemAdviceProvider;

#[derive(Clone)]
Expand Down Expand Up @@ -245,3 +247,73 @@ fn test_transaction_result_account_delta() {
CHILD_STORAGE_INDEX_0
);
}

#[test]
fn test_prove_witness() {
let data_store = MockDataStore::new();
let mut executor = TransactionExecutor::new(data_store.clone());

let account_id = data_store.account.id();
executor.load_account(account_id).unwrap();

let block_ref = data_store.block_header.block_num().as_int() as u32;
let note_origins = data_store
.notes
.iter()
.map(|note| note.proof().as_ref().unwrap().origin().clone())
.collect::<Vec<_>>();

// execute the transaction and get the witness
let transaction_result = executor
.execute_transaction(account_id, block_ref, &note_origins, None)
.unwrap();
let witness = transaction_result.clone().into_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);

assert!(proven_transaction.is_ok());
}

#[test]
fn test_prove_and_verify_with_tx_executor() {
let data_store = MockDataStore::new();
let mut executor = TransactionExecutor::new(data_store.clone());

let account_id = data_store.account.id();
executor.load_account(account_id).unwrap();

let block_ref = data_store.block_header.block_num().as_int() as u32;
let note_origins = data_store
.notes
.iter()
.map(|note| note.proof().as_ref().unwrap().origin().clone())
.collect::<Vec<_>>();

// prove the transaction with the executor
let prepared_transaction = 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());
}
2 changes: 1 addition & 1 deletion objects/src/notes/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::{AccountId, Felt, NoteError, Word};
/// - sender is the account which created the note.
/// - tag is a tag which can be used to identify the target account for the note.
/// - num_assets is the number of assets in the note.
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct NoteMetadata {
sender: AccountId,
tag: Felt,
Expand Down
6 changes: 6 additions & 0 deletions objects/src/notes/stub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,9 @@ impl TryFrom<&[Word]> for NoteStub {
Ok(stub)
}
}

impl From<NoteStub> for NoteEnvelope {
fn from(note_stub: NoteStub) -> Self {
note_stub.envelope
}
}
12 changes: 12 additions & 0 deletions objects/src/transaction/consumed_notes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ impl ToAdviceInputs for ConsumedNotes {
}
}

impl From<ConsumedNotes> for Vec<ConsumedNoteInfo> {
fn from(consumed_notes: ConsumedNotes) -> Self {
consumed_notes.notes.into_iter().map(|note| note.into()).collect::<Vec<_>>()
}
}

// CONSUMED NOTE INFO
// ================================================================================================

Expand Down Expand Up @@ -150,3 +156,9 @@ impl From<ConsumedNoteInfo> for [u8; 64] {
elements
}
}

impl From<Note> for ConsumedNoteInfo {
fn from(note: Note) -> Self {
Self::new(note.nullifier(), note.script().hash())
}
}
10 changes: 8 additions & 2 deletions objects/src/transaction/created_notes.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{
BTreeMap, Digest, Felt, Hasher, MerkleStore, NoteStub, StackOutputs, TransactionResultError,
TryFromVmResult, Vec, Word, WORD_SIZE,
BTreeMap, Digest, Felt, Hasher, MerkleStore, NoteEnvelope, NoteStub, StackOutputs,
TransactionResultError, TryFromVmResult, Vec, Word, WORD_SIZE,
};
use miden_core::utils::group_slice_elements;
use miden_lib::memory::NOTE_MEM_SIZE;
Expand Down Expand Up @@ -101,3 +101,9 @@ pub fn generate_created_notes_stub_commitment(notes: &[NoteStub]) -> Digest {

Hasher::hash_elements(&elements)
}

impl From<CreatedNotes> for Vec<NoteEnvelope> {
fn from(created_notes: CreatedNotes) -> Self {
created_notes.notes.into_iter().map(|note| note.into()).collect::<Vec<_>>()
}
}
7 changes: 1 addition & 6 deletions objects/src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,5 @@ pub use created_notes::CreatedNotes;
pub use executed_tx::ExecutedTransaction;
pub use prepared_tx::PreparedTransaction;
pub use proven_tx::ProvenTransaction;
pub use tx_result::TransactionResult;
pub use tx_result::{FinalAccountStub, TransactionResult};
pub use tx_witness::TransactionWitness;

#[cfg(feature = "testing")]
pub mod testing {
pub use super::tx_result::FinalAccountStub;
}
Loading

0 comments on commit f314325

Please sign in to comment.