-
Notifications
You must be signed in to change notification settings - Fork 139
feat: batch kernel skeleton #2904
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
mmagician
wants to merge
9
commits into
next
Choose a base branch
from
mmagician-claude/batch-kernel-skeleton
base: next
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 1 commit
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
1cbd5f9
feat: add skeleton batch kernel + ProvenBatch proof field
claude 0411259
Apply suggestions from code review
mmagician ee2df53
chore(protocol): drop premature BATCH error category
claude c508740
refactor(protocol): order build_input_stack params to match stack layout
claude 9ed96f7
refactor(batch-prover): remove unused proof_security_level accessor
claude 52adb85
refactor(protocol): simplify batch output padding check
claude ee3f559
test(batch): reuse shared chain setup helpers
claude e3d15c5
docs: trim verbose batch kernel CHANGELOG entry
claude ba9af23
Merge remote-tracking branch 'origin/next' into mmagician-claude/batc…
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
mmagician marked this conversation as resolved.
Outdated
|
||
| #! 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. | ||
| #! | ||
|
mmagician marked this conversation as resolved.
Outdated
|
||
| #! 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. | ||
|
mmagician marked this conversation as resolved.
Outdated
|
||
| 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. | ||
|
mmagician marked this conversation as resolved.
Outdated
|
||
| dropw dropw | ||
| end | ||
|
|
||
| begin | ||
| exec.main | ||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<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") | ||
| }); | ||
|
|
||
| // 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`. | ||
|
mmagician marked this conversation as resolved.
Outdated
|
||
| 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<Felt> = 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<Felt> = 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(), | ||
| )); | ||
| } | ||
|
mmagician marked this conversation as resolved.
Outdated
|
||
|
|
||
| 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. | ||
|
mmagician marked this conversation as resolved.
Outdated
|
||
| fn build_advice_inputs(_proposed_batch: &ProposedBatch) -> AdviceInputs { | ||
| AdviceInputs::default() | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.