Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1cbd5f9
feat: add skeleton batch kernel + ProvenBatch proof field
claude May 12, 2026
0411259
Apply suggestions from code review
mmagician May 27, 2026
ee2df53
chore(protocol): drop premature BATCH error category
claude May 27, 2026
c508740
refactor(protocol): order build_input_stack params to match stack layout
claude May 27, 2026
9ed96f7
refactor(batch-prover): remove unused proof_security_level accessor
claude May 27, 2026
52adb85
refactor(protocol): simplify batch output padding check
claude May 27, 2026
ee3f559
test(batch): reuse shared chain setup helpers
claude May 27, 2026
e3d15c5
docs: trim verbose batch kernel CHANGELOG entry
claude May 27, 2026
ba9af23
Merge remote-tracking branch 'origin/next' into mmagician-claude/batc…
claude May 27, 2026
0d20126
Merge remote-tracking branch 'origin/next' into mmagician-claude/batc…
claude May 29, 2026
11f0760
refactor(protocol): name batch kernel inputs BATCH_ID and BLOCK_COMMI…
claude May 29, 2026
7a4afcb
refactor(protocol): use Felt::ZERO associated constant
claude May 29, 2026
26c044b
refactor(protocol): type batch kernel errors instead of stringifying
claude May 29, 2026
7c72675
test(batch): rename batch_kernel module to test_batch_kernel
claude May 29, 2026
b84ea28
chore: shorten comment
mmagician May 29, 2026
5e25d94
chore: fmt
mmagician May 29, 2026
d812d4d
refactor(batch-prover): split batch execution and proving
claude May 29, 2026
7f3a5df
Merge branch 'next' into mmagician-claude/batch-kernel-skeleton
mmagician May 29, 2026
fc7d855
Update crates/miden-tx-batch-prover/src/batch_executor.rs
mmagician May 29, 2026
79165e9
refactor(protocol): return a BatchOutput from parse_output_stack
claude May 29, 2026
e9c34d3
refactor(protocol): put BLOCK_COMMITMENT on top of the batch input stack
claude Jun 1, 2026
763a6a7
refactor(protocol): name the batch output BATCH_NOTE_TREE_ROOT
claude Jun 1, 2026
9c91b70
refactor(batch): strong-type build_input_stack batch_id param
claude Jun 1, 2026
727ee9b
refactor(batch): rename BatchOutput to BatchOutputs
claude Jun 1, 2026
eaa71d4
refactor(batch): move output parsing onto BatchOutputs and return errors
claude Jun 1, 2026
473d20a
refactor(batch): expose typed BatchOutputs from ExecutedBatch
claude Jun 1, 2026
5d1b7d7
test(batch): cover BatchOutputs::parse error branches
claude Jun 1, 2026
30fb74a
Apply suggestion from @mmagician
mmagician Jun 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## v0.16.0 (TBD)

### Changes
- 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.

- [BREAKING] Renamed `AccountStorageDelta` to `AccountStoragePatch` ([#3002](https://github.com/0xMiden/protocol/pull/3002)).
- [BREAKING] Extracted `NullifierTreeBackendReader` and `AccountTreeBackendReader` traits from existing `NullifierTreeBackend` and `AccountTreeBackend` traits ([#2755](https://github.com/0xMiden/protocol/pull/2755)).
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions crates/miden-protocol/asm/kernels/batch/main.masm
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 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 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.
#!
Comment thread
mmagician marked this conversation as resolved.
#! Inputs: [
#! BLOCK_COMMITMENT,
#! BATCH_ID,
#! pad(8),
#! ]
Comment on lines +12 to +16

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should also include:

  • block_num - for the reference block number of BLOCK_COMMITMENT.
  • accountId - for the account ID for the batch builder. Though, this is not something we've been just mentioning in passing, and the current Rust implementation doesn't require it - so, maybe we skip it for now.

Also, I would maybe put the BLOCK_COMMITMENT on the top of the stack to stay consistent with the transaction kernel.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIU, block_num is not needed for the batch kernel itself, but rather for the downstream block kernel, right?

In that case I'd also wait until we start working on the block kernel.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the suggestion for block_num came from mirroring the transaction kernel inputs, which takes block num in addition to block_commitment. I don't fully recall why we added this, since it could technically be recovered from the unhashed reference block. I believe it had something to do with ease of getting the necessary batch inputs for a set of proven transactions, and we didn't want to just add block number in the transaction without having it be verified somewhere.

So, it will probably make sense to also do this for batches, but we can probably also do this when we get there.

#!
#! Outputs: [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the original intention here was for the batch kernel to output the root of the BatchNoteTree, rather than just a commitment to the output notes.

This way, the block kernel would have to do much less work and could aggregate the individual BatchNoteTrees into a single BlockNoteTree. This would have to be the MASM equivalent of BlockNoteTree::insert_batch_note_subtree.

I think the reason we don't currently do this at block level, is because we decided to also have note erasure at block level, and so if the block erases on of the batch's notes, the tree root would become stale and would have to be updated. Doing this update is probably still better than recomputing the entire tree from scratch, but we may want to think about that.

To start simple, I see two routes:

  • Let the batch output the BatchNoteTree root and aggregate at block level, don't do note erasure at block level for now.
  • Let the batch output a list of output notes and let the block kernel do the full aggregation. We can optimize this whole process later.

I think the first route is a bit closer to the end goal, so I'd go with that one. Just a suggestion, though.

I think I'm skeptical of the usefulness of note erasure at block level, given that batches are already incentivized to do as much note erasure as possible, and it just adds extra complexity.

@mmagician mmagician May 29, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm skeptical of the usefulness of note erasure at block level, given that batches are already incentivized to do as much note erasure as possible, and it just adds extra complexity.

Agreed, my guess is that this will rarely if ever be used in practice. I'd be in favor of dropping it. Created #3008

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the original intention here was for the batch kernel to output the root of the BatchNoteTree, rather than just a commitment to the output notes.

Yes I know, I should have maybe mentioned this in the comments that the first version would just have a commitment to all output notes instead. Mostly because, AFAICS, the BatchNoteTree is not actually wired up anywhere during batch construction.

So what I'd suggest is:

  1. continue with the skeleton batch kernel to reference OUTPUT_NOTES_COMMITMENT
  2. wire up the construction of BatchNoteTree as part of ProposedBatch::new()
  3. change the batch kernel to output (and verify) BATCH_NOTE_TREE_COMMITMENT

WDYT? If so I'll create an issue for 2.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that the first version would just have a commitment to all output notes instead

Does "first version" mean the version after this (or some subsequent) PR or the version we go to mainnet with?

If it's the former, then I'd already setup the outputs to reference what we want it to be, i.e. BATCH_NOTE_TREE_ROOT, but if it's the latter, then using OUTPUT_NOTES_COMMITMENT seems fine.

Your second step will basically imply removing note erasure support at the block level, but I think that's fine to do for now.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have BATCH_NOTE_TREE_ROOT as an output of the kernel even now - even if it just getting there via advice stack or something similar.

Also, agree with the above conclusion that it is fine to disable not erasure at the block level for now. We may find a way to make it work in the future (assuming we want to) with batch kernel outputting BATCH_NOTE_TREE_ROOT as well.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does "first version" mean the version after this (or some subsequent) PR or the version we go to mainnet with?

One of the follow-up PRs. Mainnet version should have BatchNoteTree.

I would have BATCH_NOTE_TREE_ROOT as an output of the kernel even now - even if it just getting there via advice stack or something similar.

ATM with this PR these are all just stack comment names anyway. As mentioned in another comment, we're not wiring anything up in this PR, all of which will be done in a follow-up.

So sure, I can change the name of the stack comment to say BATCH_NOTE_TREE_ROOT.

Anyway as mentioned above, we don't even have the construction of BatchNoteTree in Rust, so that would need to happen first before we can fully wire up BATCH_NOTE_TREE_ROOT in MASM.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. One correction though: we do already have BatchNoteTree in Rust.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes but it's not constructed anywhere meaningfully as part of building a batch

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(unless I missed it)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed here: 763a6a7

#! INPUT_NOTES_COMMITMENT,
#! BATCH_NOTE_TREE_ROOT,
#! batch_expiration_block_num,
#! pad(7),
#! ]
Comment on lines +17 to +23

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of potential modifications here:

  • As discussed in the other comment, I'd replace OUTPUT_NOTES_COMMITMENT with BATCH_NOTE_TREE_ROOT.
  • It may be good to add something like ACCOUNT_UPDATES_COMMITMENT as another output element. This would commit to (accountId, initial_state_hash, final_state_hash) tuples for all updated accounts. The main purpose of this would be to make it simpler for the block kernel to verify consistency between included batches (e.g., "batch b1 updates account a1 from state x to y).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be good to add something like ACCOUNT_UPDATES_COMMITMENT as another output element. This would commit to (accountId, initial_state_hash, final_state_hash) tuples for all updated accounts. The main purpose of this would be to make it simpler for the block kernel to verify consistency between included batches (e.g., "batch b1 updates account a1 from state x to y).

I would keep things simple for now, especially since we don't have the block kernel even in its skeleton form. Once that starts taking shape, we'd be in a better position to know exactly what the batch kernel should output.
(I can include a stack comment that this is part of the output but anyway it won't be wired up in the next couple of PRs)

#!
#! Where:
#! - BLOCK_COMMITMENT is the commitment of the batch's reference block.
#! - BATCH_ID is the batch's `BatchId`, the commitment to its transactions.
#! - INPUT_NOTES_COMMITMENT will be the sequential hash over every transaction's input note
#! commitments. In this skeleton it is the empty word.
#! - BATCH_NOTE_TREE_ROOT will be the root of the batch note tree built over every transaction's
#! output notes. 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.
#!
proc main
dropw dropw
end
Comment thread
mmagician marked this conversation as resolved.

begin
exec.main
end
Comment on lines +35 to +41

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is meant to be an initial skeleton - but I wonder if we should push it one step further and instead of just using empty words everywhere, set things to real inputs and real outputs (even if the output just come from the advice stack for now).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be all wired up in a follow-up PR which is already in draft.
I kept this as minimal as possible for ease of review

20 changes: 20 additions & 0 deletions crates/miden-protocol/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -85,13 +86,32 @@ 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)?;

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.
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
// ================================================================================================

Expand Down
109 changes: 109 additions & 0 deletions crates/miden-protocol/src/batch/kernel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use alloc::vec::Vec;

use miden_core::program::Kernel;

use crate::batch::{BatchId, ProposedBatch};
use crate::block::BlockNumber;
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<Program> = 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")
});

// BATCH KERNEL
// ================================================================================================

/// The batch kernel program: an executable Miden program that proves a batch of transactions.
///
/// The kernel takes `[BLOCK_COMMITMENT, BATCH_ID]` as public inputs and emits
/// `[INPUT_NOTES_COMMITMENT, BATCH_NOTE_TREE_ROOT, batch_expiration_block_num]`. See
/// `asm/kernels/batch/main.masm` for the input/output contract.
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_commitment = proposed_batch.reference_block_header().commitment();
let batch_id = proposed_batch.id();

let stack_inputs = Self::build_input_stack(block_commitment, batch_id);
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
/// [BLOCK_COMMITMENT, BATCH_ID, pad(8)]
/// ```
///
/// Where:
/// - `BLOCK_COMMITMENT` is the commitment of the batch's reference block.
/// - `BATCH_ID` is the batch's [`BatchId`].
pub fn build_input_stack(block_commitment: Word, batch_id: BatchId) -> StackInputs {
let mut inputs: Vec<Felt> = Vec::with_capacity(8);
inputs.extend_from_slice(block_commitment.as_elements());
inputs.extend_from_slice(batch_id.as_word().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, BATCH_NOTE_TREE_ROOT, batch_expiration_block_num]
/// ```
pub fn build_output_stack(
input_notes_commitment: Word,
batch_note_tree_root: Word,
batch_expiration_block_num: BlockNumber,
) -> StackOutputs {
Comment on lines +84 to +91

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs and variable names might need an update if we change to BATCH_NOTE_TREE_ROOT.

let mut outputs: Vec<Felt> = Vec::with_capacity(9);
outputs.extend_from_slice(input_notes_commitment.as_elements());
outputs.extend_from_slice(batch_note_tree_root.as_elements());
outputs.push(Felt::from(batch_expiration_block_num));

StackOutputs::new(&outputs).expect("number of stack outputs should be <= 16")
}

// 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.
fn build_advice_inputs(_proposed_batch: &ProposedBatch) -> AdviceInputs {
AdviceInputs::default()
}
Comment thread
mmagician marked this conversation as resolved.
}
6 changes: 6 additions & 0 deletions crates/miden-protocol/src/batch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@ mod ordered_batches;
pub use ordered_batches::OrderedBatches;

pub(super) mod note_tracker;

mod kernel;
pub use kernel::BatchKernel;

mod output;
pub use output::BatchOutputs;
Loading
Loading