From 1cbd5f9e6ca444d2bf022068fd701edcec3be09b Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Tue, 12 May 2026 08:18:10 +0000 Subject: [PATCH 1/8] feat: add skeleton batch kernel + ProvenBatch proof field Establishes the public input/output contract for the batch kernel (#1122) plus the Rust plumbing that surrounds it, without any verification logic. - main.masm drops TRANSACTIONS_COMMITMENT + BLOCK_HASH and exits; the VM's depth >= 16 invariant leaves the all-zero 16-felt output region in place. - BatchKernel struct exposes prepare_inputs / build_input_stack / build_output_stack / parse_output_stack; build_advice_inputs returns the default empty AdviceInputs since the skeleton ignores advice data. - ProvenBatch carries a proof: ExecutionProof field through new_unchecked and serde. - LocalBatchProver::prove now runs the kernel via miden_prover::prove and attaches the proof to the returned ProvenBatch. The kernel's public outputs are not yet cross-checked against the proposed batch; that lands with the verification logic. - prove_dummy retained for tests that don't want proof generation. Smoke test exercises the full plumbing: builds a realistic two-transaction ProposedBatch, runs the kernel via FastProcessor, asserts the parsed outputs are empty / zero. TODO list in main.masm enumerates the checks the verification PR will introduce. --- CHANGELOG.md | 1 + Cargo.lock | 2 + .../asm/kernels/batch/main.masm | 53 ++++++ crates/miden-protocol/build.rs | 27 ++- crates/miden-protocol/src/batch/kernel.rs | 177 ++++++++++++++++++ crates/miden-protocol/src/batch/mod.rs | 3 + .../src/batch/proposed_batch.rs | 5 + .../miden-protocol/src/batch/proven_batch.rs | 17 +- crates/miden-protocol/src/errors/mod.rs | 13 ++ .../src/kernel_tests/batch/batch_kernel.rs | 129 +++++++++++++ .../src/kernel_tests/batch/mod.rs | 1 + crates/miden-tx-batch-prover/Cargo.toml | 13 +- .../src/local_batch_prover.rs | 80 ++++++-- 13 files changed, 494 insertions(+), 27 deletions(-) create mode 100644 crates/miden-protocol/asm/kernels/batch/main.masm create mode 100644 crates/miden-protocol/src/batch/kernel.rs create mode 100644 crates/miden-testing/src/kernel_tests/batch/batch_kernel.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index c360ebb25a..aef584236b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - [BREAKING] Renamed account ID version 0 to version 1 and made encoded version 0 invalid ([#2842](https://github.com/0xMiden/protocol/issues/2842)). - [BREAKING] Changed note metadata version 1 to encode as `1`, leaving encoded version `0` invalid. - Documented the `miden::protocol::account_id` module in the protocol library docs ([#2607](https://github.com/0xMiden/protocol/issues/2607)). +- Added a skeleton batch kernel program with the public input/output contract from issue [#1122](https://github.com/0xMiden/protocol/issues/1122), wired through `LocalBatchProver::prove` and attached to `ProvenBatch` as an `ExecutionProof`. The kernel does not yet perform any verification; the verification chain that fills in the real outputs will land in a follow-up PR. ### Fixes diff --git a/Cargo.lock b/Cargo.lock index 86f4fada84..a34e2823b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1978,7 +1978,9 @@ dependencies = [ name = "miden-tx-batch-prover" version = "0.15.0" dependencies = [ + "miden-processor", "miden-protocol", + "miden-prover", "miden-tx", ] diff --git a/crates/miden-protocol/asm/kernels/batch/main.masm b/crates/miden-protocol/asm/kernels/batch/main.masm new file mode 100644 index 0000000000..89e70417be --- /dev/null +++ b/crates/miden-protocol/asm/kernels/batch/main.masm @@ -0,0 +1,53 @@ +# MAIN +# ================================================================================================= + +#! Batch kernel program (skeleton). +#! +#! A transaction batch groups a set of independently-proven transactions so they can later be +#! aggregated into a block by the block kernel. This program defines the public input/output +#! contract that the batch kernel will eventually verify, but does not yet perform any +#! verification: it drops its inputs and exits, leaving the all-zero word output region as the +#! stack's initial padding zeros. +#! +#! Splitting the kernel into "shape only" and "verification" lets downstream Rust tooling +#! (`BatchKernel`'s input/advice/output builders, `LocalBatchProver::prove`, the `ProvenBatch` +#! proof field) be built and reviewed against a stable interface before the verification logic +#! lands. The verification chain that fills in the real outputs is added in a follow-up PR. +#! +#! Inputs: [ +#! TRANSACTIONS_COMMITMENT, +#! BLOCK_HASH, +#! pad(8), +#! ] +#! +#! Outputs: [ +#! INPUT_NOTES_COMMITMENT, +#! OUTPUT_NOTES_COMMITMENT, +#! batch_expiration_block_num, +#! pad(7), +#! ] +#! +#! Where: +#! - TRANSACTIONS_COMMITMENT is the sequential hash of the `(tx_id, account_id)` tuples +#! committing to the transactions in the batch (i.e. the `BatchId` value). +#! - BLOCK_HASH is the commitment of the batch's reference block. +#! - INPUT_NOTES_COMMITMENT will be the sequential hash over every transaction's input note +#! commitments. In this skeleton it is the empty word. +#! - OUTPUT_NOTES_COMMITMENT will be the sequential hash over every transaction's output note +#! commitments. In this skeleton it is the empty word. +#! - batch_expiration_block_num will be the minimum of every transaction's +#! `expiration_block_num`. In this skeleton it is zero. +#! +#! TODO: replace this skeleton with the verification chain that reconstructs every transaction +#! from the advice provider, anchors the reconstruction in `TRANSACTIONS_COMMITMENT`, and +#! emits the real batch commitments and expiration. +proc main + # Drop TRANSACTIONS_COMMITMENT and BLOCK_HASH. The VM keeps the stack at depth >= 16, so the + # remaining 8 felts (the explicit `pad(8)` inputs) plus 8 zeros auto-filled below them form + # the all-zero 16-felt output region. + dropw dropw +end + +begin + exec.main +end diff --git a/crates/miden-protocol/build.rs b/crates/miden-protocol/build.rs index f116980634..6d45f6cdec 100644 --- a/crates/miden-protocol/build.rs +++ b/crates/miden-protocol/build.rs @@ -20,6 +20,7 @@ const ASM_PROTOCOL_DIR: &str = "protocol"; const SHARED_UTILS_DIR: &str = "shared_utils"; const SHARED_MODULES_DIR: &str = "shared_modules"; const ASM_TX_KERNEL_DIR: &str = "kernels/transaction"; +const ASM_BATCH_KERNEL_DIR: &str = "kernels/batch"; const PROTOCOL_LIB_NAMESPACE: &str = "miden::protocol"; @@ -30,7 +31,7 @@ const PROTOCOL_LIB_ERRORS_RS_FILE: &str = "protocol_errors.rs"; const TX_KERNEL_ERRORS_ARRAY_NAME: &str = "TX_KERNEL_ERRORS"; const PROTOCOL_LIB_ERRORS_ARRAY_NAME: &str = "PROTOCOL_LIB_ERRORS"; -const TX_KERNEL_ERROR_CATEGORIES: [&str; 14] = [ +const TX_KERNEL_ERROR_CATEGORIES: [&str; 15] = [ "KERNEL", "PROLOGUE", "EPILOGUE", @@ -45,6 +46,7 @@ const TX_KERNEL_ERROR_CATEGORIES: [&str; 14] = [ "LINK_MAP", "INPUT_NOTE", "OUTPUT_NOTE", + "BATCH", ]; // PRE-PROCESSING @@ -85,6 +87,9 @@ fn main() -> Result<()> { let protocol_lib = compile_protocol_lib(&source_dir, &target_dir, assembler.clone())?; assembler.link_dynamic_library(protocol_lib)?; + // compile batch kernel + compile_batch_kernel(&source_dir, &target_dir.join("kernels"))?; + generate_error_constants(&source_dir, &build_dir)?; generate_event_constants(&source_dir, &target_dir)?; @@ -92,6 +97,26 @@ fn main() -> Result<()> { Ok(()) } +// COMPILE BATCH KERNEL +// ================================================================================================ + +/// Reads the batch kernel MASM source from the `source_dir`, compiles it, and saves the result +/// to the `target_dir` as a `batch_kernel.masb` binary file. +/// +/// Unlike the transaction kernel, the batch kernel does not expose syscalls, so there is no +/// `KernelLibrary` to build — only a single executable program assembled from +/// `kernels/batch/main.masm`. +fn compile_batch_kernel(source_dir: &Path, target_dir: &Path) -> Result<()> { + let batch_kernel_dir = source_dir.join(ASM_BATCH_KERNEL_DIR); + let main_file_path = batch_kernel_dir.join("main.masm"); + + let assembler = build_assembler(None)?; + let batch_main = assembler.assemble_program(main_file_path)?; + + let masb_file_path = target_dir.join("batch_kernel.masb"); + batch_main.write_to_file(masb_file_path).into_diagnostic() +} + // COMPILE TRANSACTION KERNEL // ================================================================================================ diff --git a/crates/miden-protocol/src/batch/kernel.rs b/crates/miden-protocol/src/batch/kernel.rs new file mode 100644 index 0000000000..29c5ad4fa8 --- /dev/null +++ b/crates/miden-protocol/src/batch/kernel.rs @@ -0,0 +1,177 @@ +use alloc::vec::Vec; + +use miden_core::program::Kernel; + +use crate::batch::ProposedBatch; +use crate::block::BlockNumber; +use crate::errors::BatchOutputError; +use crate::utils::serde::Deserializable; +use crate::utils::sync::LazyLock; +use crate::vm::{AdviceInputs, Program, ProgramInfo, StackInputs, StackOutputs}; +use crate::{Felt, Word}; + +// CONSTANTS +// ================================================================================================ + +static KERNEL_MAIN: LazyLock = LazyLock::new(|| { + let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/kernels/batch_kernel.masb")); + Program::read_from_bytes(bytes).expect("failed to deserialize batch kernel runtime") +}); + +// Output stack indices, kept in sync with the layout at the end of `main.masm::main`. These are +// felt offsets, not word indices: `get_word(N)` returns the four felts at positions `N..N+4`. +const INPUT_NOTES_COMMITMENT_WORD_IDX: usize = 0; +const OUTPUT_NOTES_COMMITMENT_WORD_IDX: usize = 4; +const BATCH_EXPIRATION_BLOCK_NUM_ELEMENT_IDX: usize = 8; +// The word containing `batch_expiration_block_num` plus three padding zeros. +const EXPIRATION_PAD_WORD_FELT_IDX: usize = 8; +const EXPIRATION_PAD_WORD_INNER_OFFSET: usize = 1; +// The trailing word at felt indices 12..16 must be all zero. +const TRAILING_PAD_WORD_FELT_IDX: usize = 12; + +// BATCH KERNEL +// ================================================================================================ + +/// The batch kernel program: an executable Miden program that proves a batch of transactions. +/// +/// The kernel takes `[TRANSACTIONS_COMMITMENT, BLOCK_HASH]` as public inputs and emits +/// `[INPUT_NOTES_COMMITMENT, OUTPUT_NOTES_COMMITMENT, batch_expiration_block_num]`. See +/// `asm/kernels/batch/main.masm` for the input/output contract and the `TODO` listing checks +/// the kernel does not yet enforce. +pub struct BatchKernel; + +impl BatchKernel { + // KERNEL SOURCE CODE + // -------------------------------------------------------------------------------------------- + + /// Returns the executable batch kernel program loaded from the build's `OUT_DIR`. + pub fn main() -> Program { + KERNEL_MAIN.clone() + } + + /// Returns [`ProgramInfo`] for the batch kernel program. + /// + /// The batch kernel does not expose syscalls, so the associated [`Kernel`] is empty. + pub fn program_info() -> ProgramInfo { + ProgramInfo::new(Self::main().hash(), Kernel::default()) + } + + // INPUT BUILDERS + // -------------------------------------------------------------------------------------------- + + /// Transforms the provided [`ProposedBatch`] into the stack and advice inputs needed to + /// execute the batch kernel. + pub fn prepare_inputs(proposed_batch: &ProposedBatch) -> (StackInputs, AdviceInputs) { + let block_hash = proposed_batch.reference_block_header().commitment(); + let transactions_commitment = proposed_batch.id().as_word(); + + let stack_inputs = Self::build_input_stack(block_hash, transactions_commitment); + let advice_inputs = Self::build_advice_inputs(proposed_batch); + + (stack_inputs, advice_inputs) + } + + /// Returns the stack with the public inputs required by the batch kernel. + /// + /// The initial stack is: + /// + /// ```text + /// [TRANSACTIONS_COMMITMENT, BLOCK_HASH, pad(8)] + /// ``` + /// + /// Where: + /// - `TRANSACTIONS_COMMITMENT` is the value [`BatchId`](crate::batch::BatchId) computes — a + /// sequential hash of `(transaction_id || account_id_prefix || account_id_suffix || 0 || 0)` + /// over all transactions in the batch. + /// - `BLOCK_HASH` is the commitment of the batch's reference block. + pub fn build_input_stack(block_hash: Word, transactions_commitment: Word) -> StackInputs { + let mut inputs: Vec = Vec::with_capacity(8); + inputs.extend_from_slice(transactions_commitment.as_elements()); + inputs.extend_from_slice(block_hash.as_elements()); + + StackInputs::new(&inputs).expect("number of stack inputs should be <= 16") + } + + /// Builds the stack with the expected batch kernel outputs. + /// + /// The output stack is defined as: + /// + /// ```text + /// [INPUT_NOTES_COMMITMENT, OUTPUT_NOTES_COMMITMENT, batch_expiration_block_num] + /// ``` + pub fn build_output_stack( + input_notes_commitment: Word, + output_notes_commitment: Word, + batch_expiration_block_num: BlockNumber, + ) -> StackOutputs { + let mut outputs: Vec = Vec::with_capacity(9); + outputs.extend_from_slice(input_notes_commitment.as_elements()); + outputs.extend_from_slice(output_notes_commitment.as_elements()); + outputs.push(Felt::from(batch_expiration_block_num)); + + StackOutputs::new(&outputs).expect("number of stack outputs should be <= 16") + } + + /// Extracts batch output data from the provided stack outputs. + /// + /// # Errors + /// + /// Returns an error if: + /// - The padding cells (positions 9..16) are not all zero. + /// - `batch_expiration_block_num` does not fit into a `u32`. + pub fn parse_output_stack( + stack: &StackOutputs, + ) -> Result<(Word, Word, BlockNumber), BatchOutputError> { + let input_notes_commitment = stack + .get_word(INPUT_NOTES_COMMITMENT_WORD_IDX) + .expect("input_notes_commitment word missing"); + let output_notes_commitment = stack + .get_word(OUTPUT_NOTES_COMMITMENT_WORD_IDX) + .expect("output_notes_commitment word missing"); + + let expiration_felt = stack + .get_element(BATCH_EXPIRATION_BLOCK_NUM_ELEMENT_IDX) + .expect("batch_expiration_block_num missing"); + + // The word at felt indices 8..12 contains [batch_expiration_block_num, 0, 0, 0]. Indices + // 9..12 of the output stack must be zero. + let pad_word = stack + .get_word(EXPIRATION_PAD_WORD_FELT_IDX) + .expect("expiration pad word missing"); + if pad_word.as_elements()[EXPIRATION_PAD_WORD_INNER_OFFSET..] + != Word::empty().as_elements()[1..] + { + return Err(BatchOutputError::OutputStackInvalid( + "batch_expiration_block_num must be followed by zero padding".into(), + )); + } + + // Felts 12..16 (the trailing word) must also be zero. + let trailing_word = + stack.get_word(TRAILING_PAD_WORD_FELT_IDX).expect("trailing word missing"); + if trailing_word != Word::empty() { + return Err(BatchOutputError::OutputStackInvalid( + "trailing output stack cells must be zero".into(), + )); + } + + let batch_expiration_block_num = u32::try_from(expiration_felt.as_canonical_u64()) + .map_err(|_| BatchOutputError::ExpirationBlockNumberTooLarge(expiration_felt))? + .into(); + + Ok((input_notes_commitment, output_notes_commitment, batch_expiration_block_num)) + } + + // ADVICE BUILDER + // -------------------------------------------------------------------------------------------- + + /// Builds the advice inputs (map + stack) consumed by the batch kernel. + /// + /// The skeleton kernel ignores its advice inputs, so this returns the default empty value. + /// The follow-up PR that adds the verification chain will populate the advice map with the + /// `(tx_id, account_id)` tuple list keyed by `TRANSACTIONS_COMMITMENT` and the per-tx headers + /// and note tuples. + fn build_advice_inputs(_proposed_batch: &ProposedBatch) -> AdviceInputs { + AdviceInputs::default() + } +} diff --git a/crates/miden-protocol/src/batch/mod.rs b/crates/miden-protocol/src/batch/mod.rs index 1cef432dd3..a16ee4d083 100644 --- a/crates/miden-protocol/src/batch/mod.rs +++ b/crates/miden-protocol/src/batch/mod.rs @@ -18,3 +18,6 @@ pub use ordered_batches::OrderedBatches; mod input_output_note_tracker; pub(crate) use input_output_note_tracker::InputOutputNoteTracker; + +mod kernel; +pub use kernel::BatchKernel; diff --git a/crates/miden-protocol/src/batch/proposed_batch.rs b/crates/miden-protocol/src/batch/proposed_batch.rs index 72c9eebfd0..8bf4ab6d51 100644 --- a/crates/miden-protocol/src/batch/proposed_batch.rs +++ b/crates/miden-protocol/src/batch/proposed_batch.rs @@ -341,6 +341,11 @@ impl ProposedBatch { self.id } + /// Returns the header of the reference block this batch is proposed for. + pub fn reference_block_header(&self) -> &BlockHeader { + &self.reference_block_header + } + /// Returns the block number at which the batch will expire. pub fn batch_expiration_block_num(&self) -> BlockNumber { self.batch_expiration_block_num diff --git a/crates/miden-protocol/src/batch/proven_batch.rs b/crates/miden-protocol/src/batch/proven_batch.rs index 32a0bf9d18..48c4ec8286 100644 --- a/crates/miden-protocol/src/batch/proven_batch.rs +++ b/crates/miden-protocol/src/batch/proven_batch.rs @@ -15,11 +15,10 @@ use crate::utils::serde::{ DeserializationError, Serializable, }; +use crate::vm::ExecutionProof; use crate::{MIN_PROOF_SECURITY_LEVEL, Word}; -/// A transaction batch with an execution proof. -/// Currently, there is no proof attached. Future versions will extend this structure to include -/// a proof artifact once recursive proving is implemented. +/// A transaction batch with an execution proof over the batch kernel's public commitments. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ProvenBatch { id: BatchId, @@ -30,6 +29,7 @@ pub struct ProvenBatch { output_notes: Vec, batch_expiration_block_num: BlockNumber, transactions: OrderedTransactionHeaders, + proof: ExecutionProof, } impl ProvenBatch { @@ -45,6 +45,7 @@ impl ProvenBatch { /// /// Returns an error if the batch expiration block number is not greater than the reference /// block number. + #[allow(clippy::too_many_arguments)] pub fn new_unchecked( id: BatchId, reference_block_commitment: Word, @@ -54,6 +55,7 @@ impl ProvenBatch { output_notes: Vec, batch_expiration_block_num: BlockNumber, transactions: OrderedTransactionHeaders, + proof: ExecutionProof, ) -> Result { // Check that the batch expiration block number is greater than the reference block number. if batch_expiration_block_num <= reference_block_num { @@ -72,6 +74,7 @@ impl ProvenBatch { output_notes, batch_expiration_block_num, transactions, + proof, }) } @@ -144,6 +147,11 @@ impl ProvenBatch { &self.transactions } + /// Returns the execution proof attached to this batch. + pub fn proof(&self) -> &ExecutionProof { + &self.proof + } + // MUTATORS // -------------------------------------------------------------------------------------------- @@ -166,6 +174,7 @@ impl Serializable for ProvenBatch { self.output_notes.write_into(target); self.batch_expiration_block_num.write_into(target); self.transactions.write_into(target); + self.proof.write_into(target); } } @@ -179,6 +188,7 @@ impl Deserializable for ProvenBatch { let output_notes = Vec::::read_from(source)?; let batch_expiration_block_num = BlockNumber::read_from(source)?; let transactions = OrderedTransactionHeaders::read_from(source)?; + let proof = ExecutionProof::read_from(source)?; Self::new_unchecked( id, @@ -189,6 +199,7 @@ impl Deserializable for ProvenBatch { output_notes, batch_expiration_block_num, transactions, + proof, ) .map_err(|e| DeserializationError::UnknownError(e.to_string())) } diff --git a/crates/miden-protocol/src/errors/mod.rs b/crates/miden-protocol/src/errors/mod.rs index d89e98fc9f..fc63c1281d 100644 --- a/crates/miden-protocol/src/errors/mod.rs +++ b/crates/miden-protocol/src/errors/mod.rs @@ -1058,6 +1058,19 @@ pub enum ProvenBatchError { batch_expiration_block_num: BlockNumber, reference_block_num: BlockNumber, }, + #[error("batch kernel execution failed: {0}")] + BatchKernelExecutionFailed(String), +} + +// BATCH OUTPUT ERROR +// ================================================================================================ + +#[derive(Debug, Error)] +pub enum BatchOutputError { + #[error("batch kernel output stack is invalid: {0}")] + OutputStackInvalid(String), + #[error("batch expiration block number {0} does not fit into a u32")] + ExpirationBlockNumberTooLarge(Felt), } // PROPOSED BLOCK ERROR diff --git a/crates/miden-testing/src/kernel_tests/batch/batch_kernel.rs b/crates/miden-testing/src/kernel_tests/batch/batch_kernel.rs new file mode 100644 index 0000000000..6964990410 --- /dev/null +++ b/crates/miden-testing/src/kernel_tests/batch/batch_kernel.rs @@ -0,0 +1,129 @@ +use alloc::sync::Arc; +use std::collections::BTreeMap; + +use anyhow::Context; +use miden_core_lib::CoreLibrary; +use miden_processor::{DefaultHost, ExecutionOptions, FastProcessor}; +use miden_protocol::Word; +use miden_protocol::account::{Account, AccountStorageMode}; +use miden_protocol::batch::{BatchKernel, ProposedBatch}; +use miden_protocol::block::BlockNumber; +use miden_protocol::note::{Note, NoteType}; +use miden_protocol::vm::{AdviceInputs, Program, StackInputs, StackOutputs}; +use miden_standards::testing::account_component::MockAccountComponent; +use rand::Rng; + +use super::proposed_batch::{mock_note, mock_output_note}; +use super::proven_tx_builder::MockProvenTxBuilder; +use crate::{AccountState, Auth, MockChain, MockChainBuilder}; + +// SETUP HELPERS +// ================================================================================================ + +struct TestSetup { + chain: MockChain, + account1: Account, + account2: Account, + auth_note: Note, +} + +fn setup() -> TestSetup { + let mut builder = MockChain::builder(); + let account1 = generate_account(&mut builder); + let account2 = generate_account(&mut builder); + let auth_note = builder + .add_p2id_note(account1.id(), account2.id(), &[], NoteType::Public) + .expect("adding p2id note should work"); + let mut chain = builder.build().expect("genesis should be valid"); + chain.prove_next_block().expect("first block should prove"); + TestSetup { chain, account1, account2, auth_note } +} + +fn generate_account(chain: &mut MockChainBuilder) -> Account { + let account_builder = Account::builder(rand::rng().random()) + .storage_mode(AccountStorageMode::Private) + .with_component(MockAccountComponent::with_empty_slots()); + chain + .add_account_from_builder(Auth::IncrNonce, account_builder, AccountState::Exists) + .expect("failed to add pending account from builder") +} + +/// Builds a two-transaction batch with realistic inputs and outputs. The skeleton kernel does not +/// inspect any of this data, but the batch is built end-to-end so the smoke test exercises the +/// real `prepare_inputs` path that the verification PR will eventually consume. +fn two_tx_batch(setup: &mut TestSetup) -> anyhow::Result { + let block1 = setup.chain.block_header(1); + let block2 = setup.chain.prove_next_block()?; + + let tx1 = MockProvenTxBuilder::with_account( + setup.account1.id(), + Word::empty(), + setup.account1.to_commitment(), + ) + .ref_block_commitment(block1.commitment()) + .authenticated_notes(vec![setup.auth_note.clone()]) + .output_notes(vec![mock_output_note(80)]) + .expiration_block_num(BlockNumber::from(1234u32)) + .build()?; + + let tx2_input = mock_note(81); + let tx2 = MockProvenTxBuilder::with_account( + setup.account2.id(), + Word::empty(), + setup.account2.to_commitment(), + ) + .ref_block_commitment(block1.commitment()) + .unauthenticated_notes(vec![tx2_input]) + .output_notes(vec![mock_output_note(82), mock_output_note(83)]) + .expiration_block_num(BlockNumber::from(800u32)) + .build()?; + + Ok(ProposedBatch::new( + [tx1, tx2].into_iter().map(Arc::new).collect(), + block2.header().clone(), + setup.chain.latest_partial_blockchain(), + BTreeMap::default(), + )?) +} + +fn run_kernel( + program: &Program, + stack_inputs: StackInputs, + advice_inputs: AdviceInputs, +) -> Result { + let mut host = DefaultHost::default(); + host.load_library(CoreLibrary::default().mast_forest()) + .expect("loading the core library into the test host should succeed"); + + let processor = + FastProcessor::new_with_options(stack_inputs, advice_inputs, ExecutionOptions::default()) + .with_debugging(true); + let output = processor.execute_sync(program, &mut host)?; + Ok(output.stack) +} + +// SMOKE TEST +// ================================================================================================ + +/// The skeleton batch kernel drops its public inputs and exits, leaving the all-zero word output +/// region. This test exercises the full plumbing path (build a realistic `ProposedBatch`, derive +/// stack and advice inputs via `BatchKernel::prepare_inputs`, run the kernel, parse the outputs) +/// and asserts that the contract holds: the kernel runs to completion and emits the empty word +/// shape. +#[test] +fn batch_kernel_skeleton_emits_empty_outputs() -> anyhow::Result<()> { + let mut setup = setup(); + let batch = two_tx_batch(&mut setup)?; + + let (stack_inputs, advice_inputs) = BatchKernel::prepare_inputs(&batch); + let stack_outputs = run_kernel(&BatchKernel::main(), stack_inputs, advice_inputs) + .context("kernel execution failed")?; + let (input_notes_commitment, output_notes_commitment, expiration) = + BatchKernel::parse_output_stack(&stack_outputs).context("parse output stack failed")?; + + assert_eq!(input_notes_commitment, Word::empty()); + assert_eq!(output_notes_commitment, Word::empty()); + assert_eq!(expiration, BlockNumber::from(0u32)); + + Ok(()) +} diff --git a/crates/miden-testing/src/kernel_tests/batch/mod.rs b/crates/miden-testing/src/kernel_tests/batch/mod.rs index b7dcf5b03d..987752b629 100644 --- a/crates/miden-testing/src/kernel_tests/batch/mod.rs +++ b/crates/miden-testing/src/kernel_tests/batch/mod.rs @@ -1,2 +1,3 @@ +mod batch_kernel; mod proposed_batch; mod proven_tx_builder; diff --git a/crates/miden-tx-batch-prover/Cargo.toml b/crates/miden-tx-batch-prover/Cargo.toml index df138ae0e2..158c16ef40 100644 --- a/crates/miden-tx-batch-prover/Cargo.toml +++ b/crates/miden-tx-batch-prover/Cargo.toml @@ -18,9 +18,14 @@ doctest = false [features] default = ["std"] -std = ["miden-protocol/std", "miden-tx/std"] -testing = [] +std = ["miden-processor/std", "miden-protocol/std", "miden-prover/std", "miden-tx/std"] +testing = ["miden-processor/testing", "miden-protocol/testing"] [dependencies] -miden-protocol = { workspace = true } -miden-tx = { workspace = true } +miden-processor = { workspace = true } +miden-protocol = { workspace = true } +miden-prover = { workspace = true } +miden-tx = { workspace = true } + +[dev-dependencies] +miden-protocol = { features = ["testing"], workspace = true } diff --git a/crates/miden-tx-batch-prover/src/local_batch_prover.rs b/crates/miden-tx-batch-prover/src/local_batch_prover.rs index 09a5dc3fce..fdd70bf6cb 100644 --- a/crates/miden-tx-batch-prover/src/local_batch_prover.rs +++ b/crates/miden-tx-batch-prover/src/local_batch_prover.rs @@ -1,36 +1,58 @@ use alloc::boxed::Box; +use alloc::string::ToString; -use miden_protocol::batch::{ProposedBatch, ProvenBatch}; +use miden_processor::DefaultHost; +use miden_protocol::batch::{BatchKernel, ProposedBatch, ProvenBatch}; use miden_protocol::errors::ProvenBatchError; +use miden_prover::{ExecutionProof, ProvingOptions, prove}; use miden_tx::TransactionVerifier; // LOCAL BATCH PROVER // ================================================================================================ -/// A local prover for transaction batches, proving the transactions in a [`ProposedBatch`] and -/// returning a [`ProvenBatch`]. +/// A local prover for transaction batches. +/// +/// Verifies each transaction's `ExecutionProof` natively, then runs the batch kernel program +/// via `miden_prover::prove` to produce an [`ExecutionProof`] over the batch's public +/// commitments. #[derive(Clone)] pub struct LocalBatchProver { proof_security_level: u32, + proving_options: ProvingOptions, } impl LocalBatchProver { /// Creates a new [`LocalBatchProver`] instance. pub fn new(proof_security_level: u32) -> Self { - Self { proof_security_level } + Self { + proof_security_level, + proving_options: ProvingOptions::default(), + } + } + + /// Returns this prover's configured proof security level. + pub fn proof_security_level(&self) -> u32 { + self.proof_security_level } /// Attempts to prove the [`ProposedBatch`] into a [`ProvenBatch`]. /// - /// Currently we don't perform any recursive proving. For now, this function runs a native - /// verifier for each transaction separately, and outputs a `ProvenBatch` object if all of the - /// individual proofs verify. + /// Verifies each transaction's `ExecutionProof` natively first, then runs the batch kernel + /// via `miden_prover::prove` and attaches the resulting proof to the returned + /// [`ProvenBatch`]. The kernel's public outputs are not yet cross-checked against the + /// proposed batch's expected values; that check is added together with the kernel's + /// verification logic in a follow-up PR. /// /// # Errors /// /// Returns an error if: - /// - a proof of any transaction in the batch fails to verify. - pub fn prove(&self, proposed_batch: ProposedBatch) -> Result { + /// - any transaction's proof in the batch fails to verify; + /// - the batch kernel program fails to execute or produce a proof; + /// - the kernel output stack fails to parse. + pub async fn prove( + &self, + proposed_batch: ProposedBatch, + ) -> Result { let verifier = TransactionVerifier::new(self.proof_security_level); for tx in proposed_batch.transactions() { @@ -42,25 +64,44 @@ impl LocalBatchProver { })?; } - self.prove_inner(proposed_batch) + let (stack_inputs, advice_inputs) = BatchKernel::prepare_inputs(&proposed_batch); + let mut host = DefaultHost::default(); + + let (stack_outputs, proof) = prove( + &BatchKernel::main(), + stack_inputs, + advice_inputs, + &mut host, + self.proving_options.clone(), + ) + .await + .map_err(|err| ProvenBatchError::BatchKernelExecutionFailed(err.to_string()))?; + + // Validate the output stack shape (padding cells are zero and the expiration fits in + // u32); the actual output values themselves are not checked until the kernel verifies + // them. + BatchKernel::parse_output_stack(&stack_outputs) + .map_err(|err| ProvenBatchError::BatchKernelExecutionFailed(err.to_string()))?; + + Self::build_proven_batch(proposed_batch, proof) } - /// Proves the provided [`ProposedBatch`] into a [`ProvenBatch`], **without verifying batches - /// and proving the block**. - /// - /// This is exposed for testing purposes. + /// Returns a [`ProvenBatch`] built from the proposed batch with a dummy [`ExecutionProof`] + /// attached, without running the batch kernel. #[cfg(any(feature = "testing", test))] pub fn prove_dummy( &self, proposed_batch: ProposedBatch, ) -> Result { - self.prove_inner(proposed_batch) + Self::build_proven_batch(proposed_batch, ExecutionProof::new_dummy()) } - /// Converts a proposed batch into a proven batch. - /// - /// For now, this doesn't do anything interesting. - fn prove_inner(&self, proposed_batch: ProposedBatch) -> Result { + /// Combines the parts of a [`ProposedBatch`] with the produced [`ExecutionProof`] into a + /// [`ProvenBatch`]. + fn build_proven_batch( + proposed_batch: ProposedBatch, + proof: ExecutionProof, + ) -> Result { let tx_headers = proposed_batch.transaction_headers(); let ( _transactions, @@ -83,6 +124,7 @@ impl LocalBatchProver { output_notes, batch_expiration_block_num, tx_headers, + proof, ) } } From 0411259f46bfa9a63a99c3adb0e4a8eb38668f9a Mon Sep 17 00:00:00 2001 From: Marti Date: Wed, 27 May 2026 16:38:58 +0200 Subject: [PATCH 2/8] Apply suggestions from code review Co-authored-by: Marti --- crates/miden-protocol/asm/kernels/batch/main.masm | 15 ++------------- crates/miden-protocol/build.rs | 4 ---- crates/miden-protocol/src/batch/kernel.rs | 6 +----- crates/miden-protocol/src/batch/proven_batch.rs | 3 ++- .../src/local_batch_prover.rs | 3 +-- 5 files changed, 6 insertions(+), 25 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/batch/main.masm b/crates/miden-protocol/asm/kernels/batch/main.masm index 89e70417be..2771540ac9 100644 --- a/crates/miden-protocol/asm/kernels/batch/main.masm +++ b/crates/miden-protocol/asm/kernels/batch/main.masm @@ -5,15 +5,10 @@ #! #! A transaction batch groups a set of independently-proven transactions so they can later be #! aggregated into a block by the block kernel. This program defines the public input/output -#! contract that the batch kernel will eventually verify, but does not yet perform any -#! verification: it drops its inputs and exits, leaving the all-zero word output region as the +#! contract that the batch kernel will eventually verify, but currently does not yet perform +#! any verification: it drops its inputs and exits, leaving the all-zero word output region as the #! stack's initial padding zeros. #! -#! Splitting the kernel into "shape only" and "verification" lets downstream Rust tooling -#! (`BatchKernel`'s input/advice/output builders, `LocalBatchProver::prove`, the `ProvenBatch` -#! proof field) be built and reviewed against a stable interface before the verification logic -#! lands. The verification chain that fills in the real outputs is added in a follow-up PR. -#! #! Inputs: [ #! TRANSACTIONS_COMMITMENT, #! BLOCK_HASH, @@ -38,13 +33,7 @@ #! - batch_expiration_block_num will be the minimum of every transaction's #! `expiration_block_num`. In this skeleton it is zero. #! -#! TODO: replace this skeleton with the verification chain that reconstructs every transaction -#! from the advice provider, anchors the reconstruction in `TRANSACTIONS_COMMITMENT`, and -#! emits the real batch commitments and expiration. proc main - # Drop TRANSACTIONS_COMMITMENT and BLOCK_HASH. The VM keeps the stack at depth >= 16, so the - # remaining 8 felts (the explicit `pad(8)` inputs) plus 8 zeros auto-filled below them form - # the all-zero 16-felt output region. dropw dropw end diff --git a/crates/miden-protocol/build.rs b/crates/miden-protocol/build.rs index 6d45f6cdec..39bba72407 100644 --- a/crates/miden-protocol/build.rs +++ b/crates/miden-protocol/build.rs @@ -102,10 +102,6 @@ fn main() -> Result<()> { /// Reads the batch kernel MASM source from the `source_dir`, compiles it, and saves the result /// to the `target_dir` as a `batch_kernel.masb` binary file. -/// -/// Unlike the transaction kernel, the batch kernel does not expose syscalls, so there is no -/// `KernelLibrary` to build — only a single executable program assembled from -/// `kernels/batch/main.masm`. fn compile_batch_kernel(source_dir: &Path, target_dir: &Path) -> Result<()> { let batch_kernel_dir = source_dir.join(ASM_BATCH_KERNEL_DIR); let main_file_path = batch_kernel_dir.join("main.masm"); diff --git a/crates/miden-protocol/src/batch/kernel.rs b/crates/miden-protocol/src/batch/kernel.rs index 29c5ad4fa8..74e6c50c0b 100644 --- a/crates/miden-protocol/src/batch/kernel.rs +++ b/crates/miden-protocol/src/batch/kernel.rs @@ -18,8 +18,7 @@ static KERNEL_MAIN: LazyLock = LazyLock::new(|| { Program::read_from_bytes(bytes).expect("failed to deserialize batch kernel runtime") }); -// Output stack indices, kept in sync with the layout at the end of `main.masm::main`. These are -// felt offsets, not word indices: `get_word(N)` returns the four felts at positions `N..N+4`. +// Output stack indices (layout at the end of `batch/main.masm::main`). const INPUT_NOTES_COMMITMENT_WORD_IDX: usize = 0; const OUTPUT_NOTES_COMMITMENT_WORD_IDX: usize = 4; const BATCH_EXPIRATION_BLOCK_NUM_ELEMENT_IDX: usize = 8; @@ -168,9 +167,6 @@ impl BatchKernel { /// Builds the advice inputs (map + stack) consumed by the batch kernel. /// /// The skeleton kernel ignores its advice inputs, so this returns the default empty value. - /// The follow-up PR that adds the verification chain will populate the advice map with the - /// `(tx_id, account_id)` tuple list keyed by `TRANSACTIONS_COMMITMENT` and the per-tx headers - /// and note tuples. fn build_advice_inputs(_proposed_batch: &ProposedBatch) -> AdviceInputs { AdviceInputs::default() } diff --git a/crates/miden-protocol/src/batch/proven_batch.rs b/crates/miden-protocol/src/batch/proven_batch.rs index 48c4ec8286..753ceaf67a 100644 --- a/crates/miden-protocol/src/batch/proven_batch.rs +++ b/crates/miden-protocol/src/batch/proven_batch.rs @@ -18,7 +18,8 @@ use crate::utils::serde::{ use crate::vm::ExecutionProof; use crate::{MIN_PROOF_SECURITY_LEVEL, Word}; -/// A transaction batch with an execution proof over the batch kernel's public commitments. +/// A transaction batch with an execution proof. +/// Currently, this only carries a skeleton proof which does not attest to anything meaningful. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ProvenBatch { id: BatchId, diff --git a/crates/miden-tx-batch-prover/src/local_batch_prover.rs b/crates/miden-tx-batch-prover/src/local_batch_prover.rs index fdd70bf6cb..5a69181288 100644 --- a/crates/miden-tx-batch-prover/src/local_batch_prover.rs +++ b/crates/miden-tx-batch-prover/src/local_batch_prover.rs @@ -40,8 +40,7 @@ impl LocalBatchProver { /// Verifies each transaction's `ExecutionProof` natively first, then runs the batch kernel /// via `miden_prover::prove` and attaches the resulting proof to the returned /// [`ProvenBatch`]. The kernel's public outputs are not yet cross-checked against the - /// proposed batch's expected values; that check is added together with the kernel's - /// verification logic in a follow-up PR. + /// proposed batch's expected values. /// /// # Errors /// From ee2df534f4e5af34ba11fd6e98fec4c591853ea6 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 27 May 2026 14:50:48 +0000 Subject: [PATCH 3/8] chore(protocol): drop premature BATCH error category The batch kernel is compiled separately and its MASM errors are not extracted by generate_error_constants (which only scans the transaction kernel dir), so the BATCH entry in TX_KERNEL_ERROR_CATEGORIES is inert. Batch error handling will be added properly alongside the verification logic. --- crates/miden-protocol/build.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/miden-protocol/build.rs b/crates/miden-protocol/build.rs index 39bba72407..a252bb7527 100644 --- a/crates/miden-protocol/build.rs +++ b/crates/miden-protocol/build.rs @@ -31,7 +31,7 @@ const PROTOCOL_LIB_ERRORS_RS_FILE: &str = "protocol_errors.rs"; const TX_KERNEL_ERRORS_ARRAY_NAME: &str = "TX_KERNEL_ERRORS"; const PROTOCOL_LIB_ERRORS_ARRAY_NAME: &str = "PROTOCOL_LIB_ERRORS"; -const TX_KERNEL_ERROR_CATEGORIES: [&str; 15] = [ +const TX_KERNEL_ERROR_CATEGORIES: [&str; 14] = [ "KERNEL", "PROLOGUE", "EPILOGUE", @@ -46,7 +46,6 @@ const TX_KERNEL_ERROR_CATEGORIES: [&str; 15] = [ "LINK_MAP", "INPUT_NOTE", "OUTPUT_NOTE", - "BATCH", ]; // PRE-PROCESSING From c5087401f0dc5d0915f678ab89c4f940888b3033 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 27 May 2026 14:51:07 +0000 Subject: [PATCH 4/8] refactor(protocol): order build_input_stack params to match stack layout Pass transactions_commitment before block_hash so the parameter order matches the documented input stack [TRANSACTIONS_COMMITMENT, BLOCK_HASH]. Also drop the now-stale reference to the main.masm TODO from the BatchKernel doc comment. --- crates/miden-protocol/src/batch/kernel.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/miden-protocol/src/batch/kernel.rs b/crates/miden-protocol/src/batch/kernel.rs index 74e6c50c0b..39cc00f11c 100644 --- a/crates/miden-protocol/src/batch/kernel.rs +++ b/crates/miden-protocol/src/batch/kernel.rs @@ -35,8 +35,7 @@ const TRAILING_PAD_WORD_FELT_IDX: usize = 12; /// /// The kernel takes `[TRANSACTIONS_COMMITMENT, BLOCK_HASH]` as public inputs and emits /// `[INPUT_NOTES_COMMITMENT, OUTPUT_NOTES_COMMITMENT, batch_expiration_block_num]`. See -/// `asm/kernels/batch/main.masm` for the input/output contract and the `TODO` listing checks -/// the kernel does not yet enforce. +/// `asm/kernels/batch/main.masm` for the input/output contract. pub struct BatchKernel; impl BatchKernel { @@ -64,7 +63,7 @@ impl BatchKernel { let block_hash = proposed_batch.reference_block_header().commitment(); let transactions_commitment = proposed_batch.id().as_word(); - let stack_inputs = Self::build_input_stack(block_hash, transactions_commitment); + let stack_inputs = Self::build_input_stack(transactions_commitment, block_hash); let advice_inputs = Self::build_advice_inputs(proposed_batch); (stack_inputs, advice_inputs) @@ -83,7 +82,7 @@ impl BatchKernel { /// sequential hash of `(transaction_id || account_id_prefix || account_id_suffix || 0 || 0)` /// over all transactions in the batch. /// - `BLOCK_HASH` is the commitment of the batch's reference block. - pub fn build_input_stack(block_hash: Word, transactions_commitment: Word) -> StackInputs { + pub fn build_input_stack(transactions_commitment: Word, block_hash: Word) -> StackInputs { let mut inputs: Vec = Vec::with_capacity(8); inputs.extend_from_slice(transactions_commitment.as_elements()); inputs.extend_from_slice(block_hash.as_elements()); From 9ed96f7d757dfb054d469becbe7319180404fca9 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 27 May 2026 14:51:19 +0000 Subject: [PATCH 5/8] refactor(batch-prover): remove unused proof_security_level accessor The getter had no callers. --- crates/miden-tx-batch-prover/src/local_batch_prover.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crates/miden-tx-batch-prover/src/local_batch_prover.rs b/crates/miden-tx-batch-prover/src/local_batch_prover.rs index 5a69181288..32b49eea68 100644 --- a/crates/miden-tx-batch-prover/src/local_batch_prover.rs +++ b/crates/miden-tx-batch-prover/src/local_batch_prover.rs @@ -30,11 +30,6 @@ impl LocalBatchProver { } } - /// Returns this prover's configured proof security level. - pub fn proof_security_level(&self) -> u32 { - self.proof_security_level - } - /// Attempts to prove the [`ProposedBatch`] into a [`ProvenBatch`]. /// /// Verifies each transaction's `ExecutionProof` natively first, then runs the batch kernel From 52adb85ed5da35ea34ffaaa155cdf2b7ed757054 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 27 May 2026 14:52:49 +0000 Subject: [PATCH 6/8] refactor(protocol): simplify batch output padding check Replace the two separate pad-word checks with a single scan over every cell after batch_expiration_block_num, dropping the EXPIRATION_PAD_WORD_* and TRAILING_PAD_WORD_FELT_IDX constants. --- crates/miden-protocol/src/batch/kernel.rs | 27 +++++------------------ 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/crates/miden-protocol/src/batch/kernel.rs b/crates/miden-protocol/src/batch/kernel.rs index 39cc00f11c..b2121cdc51 100644 --- a/crates/miden-protocol/src/batch/kernel.rs +++ b/crates/miden-protocol/src/batch/kernel.rs @@ -8,7 +8,7 @@ use crate::errors::BatchOutputError; use crate::utils::serde::Deserializable; use crate::utils::sync::LazyLock; use crate::vm::{AdviceInputs, Program, ProgramInfo, StackInputs, StackOutputs}; -use crate::{Felt, Word}; +use crate::{Felt, Word, ZERO}; // CONSTANTS // ================================================================================================ @@ -22,11 +22,6 @@ static KERNEL_MAIN: LazyLock = LazyLock::new(|| { const INPUT_NOTES_COMMITMENT_WORD_IDX: usize = 0; const OUTPUT_NOTES_COMMITMENT_WORD_IDX: usize = 4; const BATCH_EXPIRATION_BLOCK_NUM_ELEMENT_IDX: usize = 8; -// The word containing `batch_expiration_block_num` plus three padding zeros. -const EXPIRATION_PAD_WORD_FELT_IDX: usize = 8; -const EXPIRATION_PAD_WORD_INNER_OFFSET: usize = 1; -// The trailing word at felt indices 12..16 must be all zero. -const TRAILING_PAD_WORD_FELT_IDX: usize = 12; // BATCH KERNEL // ================================================================================================ @@ -131,28 +126,16 @@ impl BatchKernel { .get_element(BATCH_EXPIRATION_BLOCK_NUM_ELEMENT_IDX) .expect("batch_expiration_block_num missing"); - // The word at felt indices 8..12 contains [batch_expiration_block_num, 0, 0, 0]. Indices - // 9..12 of the output stack must be zero. - let pad_word = stack - .get_word(EXPIRATION_PAD_WORD_FELT_IDX) - .expect("expiration pad word missing"); - if pad_word.as_elements()[EXPIRATION_PAD_WORD_INNER_OFFSET..] - != Word::empty().as_elements()[1..] + // Every cell after batch_expiration_block_num must be zero padding. + if stack[BATCH_EXPIRATION_BLOCK_NUM_ELEMENT_IDX + 1..] + .iter() + .any(|&felt| felt != ZERO) { return Err(BatchOutputError::OutputStackInvalid( "batch_expiration_block_num must be followed by zero padding".into(), )); } - // Felts 12..16 (the trailing word) must also be zero. - let trailing_word = - stack.get_word(TRAILING_PAD_WORD_FELT_IDX).expect("trailing word missing"); - if trailing_word != Word::empty() { - return Err(BatchOutputError::OutputStackInvalid( - "trailing output stack cells must be zero".into(), - )); - } - let batch_expiration_block_num = u32::try_from(expiration_felt.as_canonical_u64()) .map_err(|_| BatchOutputError::ExpirationBlockNumberTooLarge(expiration_felt))? .into(); From ee3f559c3fe617b727e56b493436de1bd94bfc80 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 27 May 2026 14:55:21 +0000 Subject: [PATCH 7/8] test(batch): reuse shared chain setup helpers Expose TestSetup and setup_chain from the proposed_batch test module and reuse them in the batch kernel smoke test instead of re-implementing the identical TestSetup/setup/generate_account fixtures. --- .../src/kernel_tests/batch/batch_kernel.rs | 39 ++----------------- .../src/kernel_tests/batch/proposed_batch.rs | 12 +++--- 2 files changed, 9 insertions(+), 42 deletions(-) diff --git a/crates/miden-testing/src/kernel_tests/batch/batch_kernel.rs b/crates/miden-testing/src/kernel_tests/batch/batch_kernel.rs index 6964990410..602f163471 100644 --- a/crates/miden-testing/src/kernel_tests/batch/batch_kernel.rs +++ b/crates/miden-testing/src/kernel_tests/batch/batch_kernel.rs @@ -5,49 +5,16 @@ use anyhow::Context; use miden_core_lib::CoreLibrary; use miden_processor::{DefaultHost, ExecutionOptions, FastProcessor}; use miden_protocol::Word; -use miden_protocol::account::{Account, AccountStorageMode}; use miden_protocol::batch::{BatchKernel, ProposedBatch}; use miden_protocol::block::BlockNumber; -use miden_protocol::note::{Note, NoteType}; use miden_protocol::vm::{AdviceInputs, Program, StackInputs, StackOutputs}; -use miden_standards::testing::account_component::MockAccountComponent; -use rand::Rng; -use super::proposed_batch::{mock_note, mock_output_note}; +use super::proposed_batch::{TestSetup, mock_note, mock_output_note, setup_chain}; use super::proven_tx_builder::MockProvenTxBuilder; -use crate::{AccountState, Auth, MockChain, MockChainBuilder}; // SETUP HELPERS // ================================================================================================ -struct TestSetup { - chain: MockChain, - account1: Account, - account2: Account, - auth_note: Note, -} - -fn setup() -> TestSetup { - let mut builder = MockChain::builder(); - let account1 = generate_account(&mut builder); - let account2 = generate_account(&mut builder); - let auth_note = builder - .add_p2id_note(account1.id(), account2.id(), &[], NoteType::Public) - .expect("adding p2id note should work"); - let mut chain = builder.build().expect("genesis should be valid"); - chain.prove_next_block().expect("first block should prove"); - TestSetup { chain, account1, account2, auth_note } -} - -fn generate_account(chain: &mut MockChainBuilder) -> Account { - let account_builder = Account::builder(rand::rng().random()) - .storage_mode(AccountStorageMode::Private) - .with_component(MockAccountComponent::with_empty_slots()); - chain - .add_account_from_builder(Auth::IncrNonce, account_builder, AccountState::Exists) - .expect("failed to add pending account from builder") -} - /// Builds a two-transaction batch with realistic inputs and outputs. The skeleton kernel does not /// inspect any of this data, but the batch is built end-to-end so the smoke test exercises the /// real `prepare_inputs` path that the verification PR will eventually consume. @@ -61,7 +28,7 @@ fn two_tx_batch(setup: &mut TestSetup) -> anyhow::Result { setup.account1.to_commitment(), ) .ref_block_commitment(block1.commitment()) - .authenticated_notes(vec![setup.auth_note.clone()]) + .authenticated_notes(vec![setup.note1.clone()]) .output_notes(vec![mock_output_note(80)]) .expiration_block_num(BlockNumber::from(1234u32)) .build()?; @@ -112,7 +79,7 @@ fn run_kernel( /// shape. #[test] fn batch_kernel_skeleton_emits_empty_outputs() -> anyhow::Result<()> { - let mut setup = setup(); + let mut setup = setup_chain(); let batch = two_tx_batch(&mut setup)?; let (stack_inputs, advice_inputs) = BatchKernel::prepare_inputs(&batch); diff --git a/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs b/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs index 254af358f1..c185887e64 100644 --- a/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs +++ b/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs @@ -40,14 +40,14 @@ pub fn mock_output_note(num: u8) -> OutputNote { RawOutputNote::Full(mock_note(num)).into_output_note().unwrap() } -struct TestSetup { - chain: MockChain, - account1: Account, - account2: Account, - note1: Note, +pub struct TestSetup { + pub chain: MockChain, + pub account1: Account, + pub account2: Account, + pub note1: Note, } -fn setup_chain() -> TestSetup { +pub fn setup_chain() -> TestSetup { let mut builder = MockChain::builder(); let account1 = generate_account(&mut builder); let account2 = generate_account(&mut builder); From e3d15c579e9879716e0ec449af0350344c4ff46e Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 27 May 2026 14:55:34 +0000 Subject: [PATCH 8/8] docs: trim verbose batch kernel CHANGELOG entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aef584236b..bdc3fab8f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ - [BREAKING] Renamed account ID version 0 to version 1 and made encoded version 0 invalid ([#2842](https://github.com/0xMiden/protocol/issues/2842)). - [BREAKING] Changed note metadata version 1 to encode as `1`, leaving encoded version `0` invalid. - Documented the `miden::protocol::account_id` module in the protocol library docs ([#2607](https://github.com/0xMiden/protocol/issues/2607)). -- Added a skeleton batch kernel program with the public input/output contract from issue [#1122](https://github.com/0xMiden/protocol/issues/1122), wired through `LocalBatchProver::prove` and attached to `ProvenBatch` as an `ExecutionProof`. The kernel does not yet perform any verification; the verification chain that fills in the real outputs will land in a follow-up PR. +- Added a skeleton batch kernel ([#1122](https://github.com/0xMiden/protocol/issues/1122)) wired through `LocalBatchProver::prove` and attached to `ProvenBatch` as an `ExecutionProof`. It does not yet perform any verification. ### Fixes