Skip to content

feat(batch kernel): wire up INPUT_NOTES_COMMITMENT & note erasure#2905

Open
mmagician wants to merge 35 commits into
nextfrom
mmagician-claude/batch-kernel-logic
Open

feat(batch kernel): wire up INPUT_NOTES_COMMITMENT & note erasure#2905
mmagician wants to merge 35 commits into
nextfrom
mmagician-claude/batch-kernel-logic

Conversation

@mmagician

@mmagician mmagician commented May 12, 2026

Copy link
Copy Markdown
Collaborator

Builds on the batch kernel skeleton (#2904) to fill in the first part of the verification chain. The kernel recomputes the batch INPUT_NOTES_COMMITMENT as the nullifier-sorted, post-erasure commitment matching ProposedBatch::input_notes().commitment(). Output notes are processed only to track erasure; computing BATCH_NOTE_TREE_ROOT and the expiration_block_num running-minimum are deferred to follow-up PRs.

What this does

Prologue

Verification of data layers

The kernel takes [BLOCK_COMMITMENT, BATCH_ID, pad(8)] and walks a layered advice map, each layer keyed by a hash the previous layer verified. It then writes all this data to memory. The advice map is structured as:

  • Layer 1 - BATCH_ID -> the (tx_id, account_id) tuple list
  • Layer 2 - each tx_id_i -> the transaction header preimage, i.e. [INIT_ACCOUNT_COMMITMENT_i, FINAL_ACCOUNT_COMMITMENT_i, INPUT_NOTES_COMMITMENT_i, OUTPUT_NOTES_COMMITMENT_i, FEE_ASSET_i]. For the scope of this PR, we only care about the input & output notes commitments.
  • Layer 3a - each INPUT_NOTES_COMMITMENT_i -> (NULLIFIER, EMPTY_OR_COMMITMENT) tuples
  • Layer 3b - each OUTPUT_NOTES_COMMITMENT_i -> (NOTE_DETAILS_COMMITMENT, METADATA_COMMITMENT) tuples

The prologue only verifies Layers 1 and 2: it writes the tuple list and the per-transaction headers (which include each INPUT_NOTES_COMMITMENT_i / OUTPUT_NOTES_COMMITMENT_i) to memory, in tx_id order. The Layer 3 note pre-images are not loaded here — they are streamed in per transaction later (see Note matching).

Loading "global" notes

So at this point we have, in tx_id order, each transaction's verified input/output notes commitments — but our goal is to compute INPUT_NOTES_COMMITMENT over the notes sorted by their nullifier, and post-erasure.
To do this, we load two more lists into memory, supplied by the host and unverified (for now):

  • input note list, sorted by their nullifier, via prepare_input_note_list
  • output note list, sorted by their note id, via prepare_output_note_list

So at the end of the prologue's prepare_batch, for both input and output notes we have a verified tx_id-ordered view (the per-transaction commitments) and an untrusted nullifier/note-id-sorted list.

The next step is to match them.

Note tracker

Note erasure

A note is erased when it is created and consumed within the same batch. Only an unauthenticated input note (one carrying a note id) can be erased, so for each such entry in the input list we look up its note id in the output list. If it is found, the note is created somewhere in the batch and is a candidate for erasure: we flag the input entry expected-to-be-erased and link it to the matching output entry.

This is a static, order-independent pass over the (untrusted) sorted lists. Whether the erasure is actually valid — the creating transaction runs before the consuming one — is enforced during matching.

Note matching

So far we've determined erasure, but the nullifier/note-id-sorted lists are still untrusted.

We now process one transaction at a time. Its note tuples are piped from the advice provider into a small scratch buffer — re-used for each transaction, so only one transaction's notes are written to scratch memory at a time — and verified by asserting their hash equals that transaction's INPUT_NOTES_COMMITMENT_i / OUTPUT_NOTES_COMMITMENT_i. Then, for each note, we look it up in the untrusted list — input notes by nullifier, output notes by note id — assert the looked-up entry matches, and mark it {consumed for inputs, created for outputs}.

Because every verified per-transaction note must be found, each entry may be marked at most once, and the lists are strictly sorted (no duplicates), this proves the untrusted lists are exactly the per-transaction note sets: the host cannot inject, omit, or duplicate notes.

Epilogue

Once every note is matched, we:

  • assert every input entry was consumed, every output entry was created, and no erasure is left pending — so the host could not tamper with the list;
  • compute INPUT_NOTES_COMMITMENT as the sequential hash over the non-erased input entries in nullifier order, reproducing build_input_note_commitment

@mmagician mmagician changed the title feat: batch kernel verification chain + tests feat: minimal batch kernel May 12, 2026
Comment thread crates/miden-protocol/asm/kernels/batch/lib/prologue.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/lib/prologue.masm Outdated
@mmagician mmagician force-pushed the mmagician-claude/batch-kernel-logic branch from 9b2a31a to 7e97e87 Compare June 1, 2026 10:11
@mmagician mmagician changed the title feat: minimal batch kernel feat: verify batch tx list, tx headers, and input-notes commitment Jun 1, 2026
@mmagician mmagician force-pushed the mmagician-claude/batch-kernel-logic branch 2 times, most recently from 7dbd6b7 to 5bccee1 Compare June 1, 2026 11:25
@mmagician mmagician changed the title feat: verify batch tx list, tx headers, and input-notes commitment feat(batch kernel): verify INPUT_NOTES_COMMITMENT Jun 1, 2026
@mmagician mmagician force-pushed the mmagician-claude/batch-kernel-logic branch from 5bccee1 to 133f9bb Compare June 1, 2026 11:38
Comment thread crates/miden-protocol/asm/kernels/batch/main.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/main.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/main.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/main.masm Outdated
Comment thread crates/miden-protocol/src/batch/kernel.rs Outdated
Comment thread crates/miden-protocol/src/transaction/transaction_id.rs
Comment thread crates/miden-protocol/src/transaction/transaction_id.rs
Comment thread crates/miden-testing/src/kernel_tests/batch/test_batch_kernel.rs Outdated
Comment thread crates/miden-testing/src/kernel_tests/batch/test_batch_kernel.rs Outdated
Comment thread crates/miden-testing/src/kernel_tests/batch/test_batch_kernel.rs Outdated
@mmagician mmagician force-pushed the mmagician-claude/batch-kernel-logic branch from 133f9bb to 59c2efb Compare June 1, 2026 13:51
Base automatically changed from mmagician-claude/batch-kernel-skeleton to next June 3, 2026 07:39
claude added 4 commits June 3, 2026 09:43
Fill in the batch kernel to verify the batch's transaction list against
its BATCH_ID (Layer 1), verify each transaction header (Layer 2), and
recompute the batch INPUT_NOTES_COMMITMENT from the verified per-tx input
notes (Layer 3). Output-notes (BATCH_NOTE_TREE_ROOT) and the expiration
running-min stay as zero placeholders, wired up in follow-up PRs.
… injection

Add BatchExecutor::extend_advice_inputs (mirroring TransactionContextBuilder)
so the kernel's rejection paths can be exercised through the normal executor
with tampered advice, and drop the low-level run_kernel test helper. The three
negative tests now corrupt the Layer 1/2/3 advice-map entries via the executor.
Match the operand-stack (# =>) and advice-stack (# AS =>) comment style used
throughout the rest of the protocol assembly.
Reconstruct the batch's input notes from a host-provided global list sorted by
nullifier, bind every per-transaction input note to that list (lookup via
sorted_array::find_key_value, with consumption + note-id checks so the host
cannot omit, inject, duplicate, or alter notes), and hash the list to emit
INPUT_NOTES_COMMITMENT == ProposedBatch::input_notes().commitment() for batches
without intra-batch note erasure. BatchExecutor loads the CoreLibrary itself so
the sorted_array event handlers are registered, and BatchVerifier now verifies
against the real input-notes commitment.

Intra-batch note erasure is a follow-up; the output-note advice (sorted list +
per-tx output tuples) and the output memory regions are provided here as
scaffolding for it.
@mmagician mmagician force-pushed the mmagician-claude/batch-kernel-logic branch from 093ac8e to 622bee3 Compare June 3, 2026 12:32
claude added 7 commits June 3, 2026 12:47
…ut notes

Add the output-note half of the note tracker: load the host-provided
note-id-sorted output list, bind every per-transaction output note to it
(deriving each NoteId via poseidon2::merge of its details/metadata commitments),
and cross-reference the two lists to mark notes that are both created and
consumed within the batch. Processing transactions in batch order (outputs
before inputs) with a 1->2 erasure gate rejects consuming such a note before
its creator runs (circular/incorrectly-ordered dependencies), and the epilogue
hashes only the surviving input notes. INPUT_NOTES_COMMITMENT now matches
ProposedBatch::input_notes().commitment() for batches with intra-batch erasure.
…gate

Process each transaction's input notes before its output notes. With
outputs-first, a note created and consumed by the same transaction had its
erasure flag advanced to 2 before the consume check, so it was erased instead
of rejected. Inputs-first means the consume sees the flag still at 1 (the
creating output has not been processed yet), tripping the consume-before-create
gate, matching the Rust note tracker's rejection. Cross-transaction erasure is
unaffected (an earlier transaction's outputs are still processed before any
later transaction's inputs).

Adds a test that drives the gate by claiming a consumed note is created in-batch
without any transaction creating it.
…tives

Extract the batch kernel's MASM error constants (build.rs + the
errors::batch_kernel module, mirroring tx_kernel/protocol) and assert the
exact ERR_BATCH_* raised by each global-list binding and erasure negative test
(via matches_execution_error), instead of just checking is_err. The three
layer-tamper tests still assert only failure, since their hash check uses
pipe_preimage_to_memory's bare assertion, which carries no named error code.
Rewrite comments that referenced the PR's development history ("currently",
"still all-zero placeholders", "not yet", "wired up in follow-up PRs") so they
describe what the code does. Tighten the CHANGELOG entry to match the
surrounding single-sentence style.
Address review feedback on PR #2905:

- Add bounds asserts so the kernel rejects oversized inputs before they
  overflow the fixed-size memory regions: the per-batch transaction count,
  the two global note-list lengths, and each transaction's note-set length
  are checked against MAX_TRANSACTIONS_PER_BATCH / MAX_NOTES_PER_BATCH. The
  global-list check is the load-bearing one, since those lists are not bound
  by a commitment hash.
- Make note_tracker.masm the single source of truth for the advice-map
  sentinel keys: build.rs generates the matching Rust constants, and both
  the kernel and the tests derive them, removing the duplicated literals.
- Add epilogue negative tests (unconsumed input note, uncreated output note)
  and an oversized-list bound test.
Address pre-push review findings on the batch verifier and output stack:

- BatchVerifier::verify now returns the verified proof's security level
  instead of discarding it, so the acceptance test pins the proof's actual
  level (via a zero-minimum verify) rather than assuming the hardcoded
  MIN_PROOF_SECURITY_LEVEL constant equals it.
- Rename the verifier's stored minimum to min_proof_security_level to
  distinguish it from the actual verified level.
- Add a BatchOutputs::into_stack_outputs round-trip unit test covering
  distinct, non-zero fields.
The BATCH_ID and reference block commitment come from the ProvenBatch's
own fields and are not recomputed from its transactions, so a successful
verification binds nothing the batch's constructor could not forge. State
this in the warning and restate the trust-boundary caveat on verify().
Comment thread crates/miden-protocol/build.rs Outdated
Comment thread crates/miden-tx-batch-prover/src/batch_executor.rs Outdated
Comment thread crates/miden-tx-batch-prover/src/batch_executor.rs Outdated
Comment thread crates/miden-testing/src/kernel_tests/batch/test_batch_kernel.rs Outdated
Comment thread crates/miden-testing/src/kernel_tests/batch/test_batch_kernel.rs Outdated
Comment thread crates/miden-testing/src/kernel_tests/batch/test_batch_kernel.rs Outdated
Comment thread crates/miden-testing/src/kernel_tests/batch/test_batch_kernel.rs Outdated
Comment thread crates/miden-testing/src/kernel_tests/batch/test_batch_kernel.rs Outdated
Comment thread crates/miden-protocol/src/batch/kernel.rs
- note_tracker.masm: lowercase pointer names in stack-state comments.
- kernel.rs: rename generated module to generated_constants; rename the
  per-tx note Vec to preimage_data.
- test_batch_kernel.rs: introduce FELTS_PER_NOTE_ENTRY and use
  MAX_INPUT_NOTES_PER_BATCH instead of magic 8/1024; parametrize the three
  tampered-advice tests with rstest; drop the thin note-list-key wrappers in
  favor of BatchKernel::{input,output}_note_list_key.
- build.rs / batch_executor.rs: trim verbose doc comments.
Comment thread crates/miden-protocol/asm/kernels/batch/lib/prologue.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/lib/prologue.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/lib/memory.masm
Comment thread crates/miden-protocol/asm/kernels/batch/lib/prologue.masm
Comment thread crates/miden-protocol/asm/kernels/batch/lib/prologue.masm
Comment thread crates/miden-protocol/asm/kernels/batch/lib/memory.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/lib/memory.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/lib/memory.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/lib/memory.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/lib/memory.masm Outdated
claude added 2 commits June 12, 2026 16:52
- main.masm / prologue.masm: tighten the doc comments per review (reword the
  program/validation summary, the INPUT_NOTES_COMMITMENT description and the
  Layer 2 step; drop redundant lines) and replace the output truncation with
  swapdw dropw dropw.
- note-list keys: make each key a full poseidon2-hash word instead of a felt
  repeated four times. build.rs now emits a generated MASM proc per key that
  pushes the word, so the prologue execs it (no dup dup dup), and the Rust
  advice builder uses the matching [u64; 4] word.
Replace the generate_batch_kernel_keys codegen (a generated MASM module +
Rust constants + reverse-push-order workaround) with the assembler's native
word(...) constant: the prologue defines

    const INPUT_NOTE_LIST_KEY=word("miden::batch_kernel::input_note_list")

and pushes it directly, while the Rust advice builder derives the same word
from the same message via miden_core's hash_string_to_word. Both sides hash
the identical domain string with the identical function, so they agree by
construction (asserted implicitly by the kernel's advice lookups in tests).
Removes the build.rs key generation entirely.
Comment thread crates/miden-protocol/asm/kernels/batch/lib/note_tracker.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/lib/note_tracker.masm
Comment thread crates/miden-protocol/asm/kernels/batch/lib/note_tracker.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/lib/note_tracker.masm
Comment thread crates/miden-protocol/asm/kernels/batch/lib/note_tracker.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/lib/note_tracker.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/lib/note_tracker.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/lib/note_tracker.masm
Comment thread crates/miden-protocol/asm/kernels/batch/lib/prologue.masm Outdated
Comment thread crates/miden-protocol/src/batch/kernel.rs Outdated
Comment thread crates/miden-protocol/src/batch/kernel.rs Outdated
Address review comments asking for more explanation:
- Define what "binding" a per-transaction note means (look it up in the
  batch's sorted list and mark the entry; proves the list is exactly the
  per-tx note set) on bind_per_tx_notes, and trim the input-before-output
  rationale to one sentence.
- Explain the scratch region and add per-block inline comments and stack
  comments to the per-tx input/output binding procs.
- Comment the empty-commitment fast path.
- Reword the bind_scratch_* summaries.
- Rename "global ... note list" to "batch ... note list" within this file
  (matching the error messages).
Comment thread crates/miden-protocol/asm/kernels/batch/lib/epilogue.masm
Comment thread crates/miden-protocol/asm/kernels/batch/lib/epilogue.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/lib/epilogue.masm
Comment thread crates/miden-protocol/asm/kernels/batch/lib/epilogue.masm
claude added 7 commits June 12, 2026 18:01
Rename the 'global input/output-note list' to 'nullifier-sorted input-note
list' / 'note-id-sorted output-note list' across the batch kernel (docs,
error messages, section headers, the memory-layout table, and load_note_list),
distinguishing the sorted batch lists from the per-transaction notes per
review.
- absorb_input_entry: use poseidon2::absorb_double_words_from_memory instead
  of manually loading the rate words and permuting (the loaded rate words were
  immediately discarded), per review.
- compute_input_notes_commitment: split into commented blocks; clarify that
  the empty-set commitment is the empty word (matching build_input_note_commitment's
  early return), not the hash of zero elements.
- Trim the verbose advice-key comments in prologue.masm and kernel.rs and drop
  the word "sentinel".
- remove the unused SCRATCH_WORDS_COUNT/WORD_INDEX slots and accessors
  (note counts are derived from pipe_preimage_to_memory's end_ptr)
- document the duplicate-note-id conservative abort in the erasure
  cross-reference and the non-multiple-of-8 advice-length failure mode
  in load_note_list
- reword the main.masm output docs: the placeholder outputs are bound
  by the proof, just not yet derived from batch data
…sert

Absorb contiguous non-erased entries in a single
absorb_double_words_from_memory call, so the hasher state round-trips
through memory once per run (once in total for a batch with no
erasures) instead of once per entry.

Also remove the epilogue's pending-erasure assert: an entry with
erasure == 1 either was consumed - tripping the note tracker's ordering
gate at consume time (erasure never transitions back to 1) - or was
not, tripping the epilogue's consumption assert. This also removes the
error constant duplicated with note_tracker.masm.
- tampered-advice case for Layer 3' (OUTPUT_NOTES_COMMITMENT preimage)
- oversized output-note list, layer-1 tuple list, and per-transaction
  note set (each length assert fires before its hash check)
- consumed-twice / created-twice checks, driven by executing the kernel
  directly with a forged BATCH_ID over a duplicated transaction tuple
  list, modeling a malicious prover bypassing ProposedBatch validation
- gate BatchExecutor::extend_advice_inputs behind the testing feature
  since it exists only to drive these rejection paths
Replace the test-only execute_kernel_raw helper with a testing-gated
BatchExecutor::with_batch_id override (mirroring extend_advice_inputs),
so the consumed-twice / created-twice tests run through the real
executor path instead of duplicating processor/host setup.
claude added 3 commits June 12, 2026 21:38
Make BatchExecutor a stateless unit struct again. The production
execute path derives its inputs and delegates to a private
execute_with_inputs; the testing-gated execute_with_advice_overrides
entry point merges override advice and delegates to the same body, so
execute contains no testing-only code.

Also drop the forged-batch-id override and the consumed-twice /
created-twice tests that needed it: those kernel checks are unreachable
through a valid ProposedBatch and are not worth a test-only input path.
…executor

Replace the testing-gated execute_with_advice_overrides with a plain
advice_inputs parameter on BatchExecutor::execute, mirroring how
TransactionArgs carry caller advice into the transaction executor (and
execute_tx_view_script's advice_inputs parameter). Advice is untrusted
by design - the kernel verifies everything it consumes - so extending
it is a legitimate production capability, not a testing hook. No
cfg-gated code remains in the executor.
Trim test doc comments to what each test exercises, drop comments that
restate the code or justify choices that only made sense during review
(cross-references to the tx-kernel test layout, duplicated section
headers, redundant TODO), and tighten the remaining prose.
Comment thread crates/miden-tx-batch/src/batch_executor.rs Outdated
Comment thread crates/miden-protocol/src/batch/kernel.rs Outdated
Comment thread crates/miden-protocol/src/batch/kernel.rs Outdated
Comment thread crates/miden-protocol/src/batch/kernel.rs Outdated
Comment thread crates/miden-protocol/src/batch/kernel.rs Outdated
Comment thread crates/miden-protocol/src/batch/kernel.rs Outdated
Comment thread crates/miden-protocol/src/batch/kernel.rs
Comment thread crates/miden-protocol/asm/kernels/batch/lib/epilogue.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/lib/epilogue.masm Outdated
Comment thread crates/miden-protocol/asm/kernels/batch/lib/epilogue.masm Outdated
@mmagician mmagician added the pr-from-maintainers PRs that come from internal contributors or integration partners. They should be given priority label Jun 12, 2026
claude and others added 2 commits June 12, 2026 22:43
- strongly type the note lists in build_advice_inputs (Nullifier,
  NoteId)
- rename Layer 3 / Layer 3' to Layer 3a / 3b across kernel docs;
  relabel the prologue's global-list step to avoid the collision
- clarify everywhere that input notes sort by nullifier and output
  notes by note id
- drop NULLIFIER[4]-style suffixes (uppercase already means word) and
  apply the suggested doc/comment trims
- shorten the batch error messages per suggestions (the duplicated
  consumed-before-created constant is kept in sync in note_tracker)
@mmagician mmagician marked this pull request as ready for review June 12, 2026 22:49
@mmagician mmagician changed the title feat(batch kernel): verify INPUT_NOTES_COMMITMENT feat(batch kernel): wire up INPUT_NOTES_COMMITMENT & note erasure Jun 12, 2026
for note_commit in tx.input_notes().iter() {
let nullifier = note_commit.nullifier();
let note_id_or_empty =
note_commit.header().map_or(Word::empty(), |header| header.id().as_word());

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.

NoteTracker turns an unauthenticated note with a valid inclusion proof into InputNoteCommitment::from(nullifier), so batch.input_notes().commitment() hashes (NULLIFIER, EMPTY). This advice path still puts (NULLIFIER, NOTE_ID) in the global list, so the epilogue emits a different commitment and that batch should fail verification. A regression test with unauthenticated_note_proofs would catch it.

Could this use the batch-level input note representation instead of the per-tx one?

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.

The batch kernel's job is to independently recompute batch.input_notes().commitment() from committed data. It does this by:

  1. Reading each tx's notes (Layer 3a), verified against the tx's committed
    INPUT_NOTES_COMMITMENT_i
  2. Loading an untrusted, nullifier-sorted list supplied as advice.
  3. Binding: for every per-tx note, look it up in the untrusted list (by nullifier) and assert the full entry matches - including asserting the untrusted entry's NoteId equals the per-tx NoteId.
  4. Hashing the non-erased untrusted entries to produce INPUT_NOTES_COMMITMENT.

Could this use the batch-level input note representation instead of the per-tx one?

Building the advice-supplied untrusted list from the batch representation would put (NULLIFIER, EMPTY) in that list. It solves the problem you mention, but then step 3 above fails instead.

To legitimately turn NOTE_ID into EMPTY, the kernel must prove the note is authenticated, which is already tracked in an existing TODO: "authenticate unauthenticated, non-erased input notes against BLOCK_COMMITMENT's chain MMR".

This can lead to a liveness issue, which we could detect and just reject at batch-building time for now, WDYT?

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 this should be an explicit unsupported-batch case for now.

I’d prefer to reject at the batch execution/proving boundary rather than in ProposedBatch::new, because the batch itself is still a valid protocol object. The current limitation is specifically that this batch kernel cannot yet prove the note-authentication transition from (NULLIFIER, NOTE_ID) to (NULLIFIER, EMPTY) without the MMR authentication step.

So the temporary shape I’d expect is:

  • detect when a tx consumes an unauthenticated note, but the batch-level input note set contains the authenticated/nullifier-only form for the same nullifier;
  • return a clear ProvenBatchError before executing the kernel;
  • add a regression test for that case;
  • leave the real fix tied to the existing in-kernel note-authentication TODO.

That keeps the liveness failure explicit, without weakening the binding check.

# => [num_notes, num_words, KEY, write_ptr]
# A length that is not a multiple of 8 felts wraps `num_notes` (field `div`) to a non-u32
# felt, which the range check below rejects.
dup u32lte.MAX_NOTES_PER_BATCH assert.err=ERR_BATCH_NOTE_LIST_TOO_LONG

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.

ProposedBatch checks MAX_INPUT_NOTES_PER_BATCH / MAX_OUTPUT_NOTES_PER_BATCH after erasure, but this list is pre-erasure.

A batch can have more than 1024 notes created and consumed inside the batch, end with at most 1024 final notes, pass ProposedBatch, and then fail here because the kernel stores the pre-erasure lists in fixed 1024-entry regions.

@mmagician mmagician Jul 1, 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.

Thanks, good catch. For now we surface this early: batch execution rejects a batch whose pre-erasure input/output note union exceeds the limit. The full fix (keep 1024 as a post-erasure limit) is deferred to #3184.

…h-kernel-logic

# Conflicts:
#	crates/miden-protocol/src/transaction/transaction_id.rs
The batch kernel cannot yet correctly prove two kinds of batch, so reject
them up front with a clear error instead of an opaque in-kernel failure:

- an input note authenticated within the batch (an unauthenticated note
  converted to authenticated via a supplied inclusion proof): the kernel
  reconstructs the commitment from the per-tx (NULLIFIER, NOTE_ID) tuple
  while the batch commits (NULLIFIER, EMPTY), so the two diverge. Proper
  support is the in-kernel note-authentication TODO in main.masm.
- a pre-erasure note union exceeding MAX_*_NOTES_PER_BATCH, which the
  kernel stores in fixed-size regions. Tracked in #3184.

Adds BatchKernel::ensure_supported (called by BatchExecutor::execute), the
matching ProvenBatchError variants, and a regression test covering the
in-batch authenticated-note case.
Comment thread CHANGELOG.md
- 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.
- The batch kernel verifies the transaction list against `BATCH_ID` and computes the nullifier-sorted `INPUT_NOTES_COMMITMENT` matching `ProposedBatch::input_notes().commitment()` ([#2905](https://github.com/0xMiden/protocol/pull/2905)).

- [BREAKING] Renamed `AccountStorageDelta` to `AccountStoragePatch` ([#3002](https://github.com/0xMiden/protocol/pull/3002)).

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.

Looks like this line duplicates the next changelog entry. The next one also includes #3154, so this shorter copy can probably be dropped.

/// FINAL_ACCOUNT_COMMITMENT,
/// INPUT_NOTES_COMMITMENT,
/// OUTPUT_NOTES_COMMITMENT,
/// FEE_ASSET,

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.

Is this FEE_ASSET line stale? TransactionId::new and input_elements() still hash only the four words below, and the batch kernel now depends on that exact layout. Leaving fee asset in this list makes the commitment contract look wider than it is.

Comment thread CHANGELOG.md
- [BREAKING] Replaced the `BurnNote` marker type and its `BurnNote::create` factory with a struct built via a `bon` typestate builder (`BurnNote::builder()`); a `BurnNote` converts into a `Note` via `Note::from` ([#2283](https://github.com/0xMiden/protocol/issues/2283)).
- [BREAKING] Replaced the `P2ideNote` marker type and its `P2ideNote::create` factory with a `P2ideNote` struct built via a `bon` typestate builder (`P2ideNote::builder()`). P2IDE notes must now carry at least one asset; the optional `reclaim_height`/`timelock_height` are set via the builder, `P2ideNoteStorage::into_recipient` is now infallible, and a `P2ideNote` converts into a `Note` via `Note::from` ([#2283](https://github.com/0xMiden/protocol/issues/2283)).
- 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.
- The batch kernel verifies the transaction list against `BATCH_ID` and computes the nullifier-sorted `INPUT_NOTES_COMMITMENT` matching `ProposedBatch::input_notes().commitment()` ([#2905](https://github.com/0xMiden/protocol/pull/2905)).

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.

This entry may need to call out the BatchExecutor::execute signature change too. It now requires caller AdviceInputs, so downstream code using execute(batch) will stop compiling.

// fits in u32); the actual output values themselves are not checked until the kernel
// verifies them.
// Parse and validate the output stack shape (zero padding, u32 expiration).
let batch_outputs = BatchOutputs::parse(trace_inputs.stack_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.

Could execute reject when the parsed outputs do not match the ProposedBatch values before returning?

Right now kernel/advice drift can build an ExecutedBatch, then LocalBatchProver::prove packages the original batch fields into ProvenBatch; that proof only fails later in BatchVerifier.

for note_commit in tx.input_notes().iter() {
let nullifier = note_commit.nullifier();
let note_id_or_empty =
note_commit.header().map_or(Word::empty(), |header| header.id().as_word());

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 this should be an explicit unsupported-batch case for now.

I’d prefer to reject at the batch execution/proving boundary rather than in ProposedBatch::new, because the batch itself is still a valid protocol object. The current limitation is specifically that this batch kernel cannot yet prove the note-authentication transition from (NULLIFIER, NOTE_ID) to (NULLIFIER, EMPTY) without the MMR authentication step.

So the temporary shape I’d expect is:

  • detect when a tx consumes an unauthenticated note, but the batch-level input note set contains the authenticated/nullifier-only form for the same nullifier;
  • return a clear ProvenBatchError before executing the kernel;
  • add a regression test for that case;
  • leave the real fix tied to the existing in-kernel note-authentication TODO.

That keeps the liveness failure explicit, without weakening the binding check.

greenhat added a commit to 0xMiden/compiler that referenced this pull request Jul 7, 2026
Port the Miden protocol batch kernel (0xMiden/protocol#2905, plus the expiration running-minimum of 0xMiden/protocol#3019) to Rust compiled with the Miden compiler, to exercise the compiler on a realistic kernel-sized program.

The fixture at tests/fixtures/batch-kernel mirrors the MASM kernel module-for-module: the prologue unhashes the layered advice data anchored at the public BATCH_ID, the note tracker determines intra-batch erasure and binds the host-provided sorted note lists to the verified per-transaction notes, and the epilogue enforces the tracking invariants and computes INPUT_NOTES_COMMITMENT. The smoke test plays the protocol's BatchKernel::prepare_inputs role: it derives batch ids, transaction ids and note commitments for mock transactions with the host hasher, feeds the layered advice map and the expiration advice stack, and checks the kernel outputs across four scenarios (a plain two-transaction batch, intra-batch note erasure, a tampered BATCH_ID pre-image, and a consume-before-create rejection).

Note-flag state is felt-typed and the sorted-list lookup uses a hand-rolled word comparison: integer discriminant matches currently lower to a br_table whose checked I32->U32 selector cast rejects legitimately wrapped selectors ("value does not fit in i32"), so the fixture stays in the felt domain where comparisons lower to opaque intrinsics.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-from-maintainers PRs that come from internal contributors or integration partners. They should be given priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants