Skip to content
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
58708ca
Add SignedBlock type
sergerad Oct 22, 2025
580b3a0
Add unit tests
sergerad Oct 22, 2025
b4735cd
DerefMut
sergerad Oct 22, 2025
ed4f26e
Get latest design compiling
sergerad Oct 29, 2025
a81ee26
RM std
sergerad Oct 29, 2025
7466430
Move tests
sergerad Oct 29, 2025
3e3a11d
Comment
sergerad Oct 29, 2025
753ff2e
Move test to success file
sergerad Oct 29, 2025
1850416
Replace prover error and add header error tests
sergerad Oct 29, 2025
4cb2811
Merge branch 'next' of github.com:0xPolygonMiden/miden-base into serg…
sergerad Oct 29, 2025
eccbd06
Fix no std
sergerad Oct 29, 2025
ee8ee24
Revert header changes
sergerad Oct 29, 2025
2d2de2e
Move header and signing logic to lib
sergerad Oct 29, 2025
277ec4a
fix std
sergerad Oct 29, 2025
9a86df3
Lint
sergerad Oct 29, 2025
b00fbf4
Remove ownership and clone
sergerad Oct 30, 2025
c8acb0f
Refactor exports
sergerad Oct 30, 2025
d89ef84
comment
sergerad Oct 30, 2025
2a3f83c
Add missing file
sergerad Oct 30, 2025
a1ffe31
RM proof_commitment
sergerad Oct 30, 2025
ec5f61c
Remove signing and add block body
sergerad Oct 31, 2025
c940e2e
Split up logic in header.rs
sergerad Oct 31, 2025
8d89e03
new unchecked body
sergerad Oct 31, 2025
16ca754
Fix test comment
sergerad Oct 31, 2025
86f4dc5
Fix new doc comment
sergerad Oct 31, 2025
0d875b3
fill out doc comments
sergerad Oct 31, 2025
31294ff
Move fns to proposed block
sergerad Nov 2, 2025
fb7e40f
rm construct block body and add from impl
sergerad Nov 2, 2025
14618af
Doc comment
sergerad Nov 2, 2025
57c70b9
push fns to body our of proven block
sergerad Nov 2, 2025
fa35e48
Rm old error
sergerad Nov 2, 2025
2ff002e
pub crate OrderedBatches::new
sergerad Nov 3, 2025
355219f
RM stale comment
sergerad Nov 3, 2025
8647679
rm unnecessary collect
sergerad Nov 3, 2025
8d43a92
Fix proven block comment
sergerad Nov 3, 2025
b17ef3f
Rename fn
sergerad Nov 3, 2025
5f1d0cc
SignedBlock new_unchecked
sergerad Nov 3, 2025
a3db00c
Move compute_chain_commitment()
sergerad Nov 4, 2025
19c3fe9
Merge branch 'next' of github.com:0xPolygonMiden/miden-base into serg…
sergerad Nov 4, 2025
0f315d9
Fix partial null tree
sergerad Nov 5, 2025
9eca37c
RM #[from]
sergerad Nov 6, 2025
ae9f0d6
Machete
sergerad Nov 6, 2025
d6b7822
chore: minor code reorg
bobbinth Nov 8, 2025
d41f953
chore: move testing feature gate to impl
bobbinth Nov 8, 2025
cca75a3
chore: minor code reorg
bobbinth Nov 8, 2025
fcd75ba
Move module
sergerad Nov 9, 2025
70e9fd5
Proven block doctstring
sergerad Nov 9, 2025
67d4e1d
Signed block docstring
sergerad Nov 9, 2025
35a3471
Add BlockProof
sergerad Nov 10, 2025
e148ec4
toml
sergerad Nov 10, 2025
5794333
Rename to render fn and update doctstring
sergerad Nov 10, 2025
3546832
Merge branch 'next' of github.com:0xPolygonMiden/miden-base into serg…
sergerad Nov 10, 2025
8d1289e
Update changelog
sergerad Nov 10, 2025
685478b
Rename to build_block(), fix comment, update proven block fields
sergerad Nov 10, 2025
a699b6e
RM unused fns
sergerad Nov 10, 2025
f00fe9c
Update changelog
sergerad Nov 10, 2025
a46c985
compute_chain_commitment member fn
sergerad Nov 11, 2025
a72cf9b
Rename test fns
sergerad Nov 11, 2025
930dc1e
RM signed block and update flows
sergerad Nov 11, 2025
bc73e14
Merge branch 'next' into sergerad-signed-block
bobbinth Nov 13, 2025
f5bf318
Comments
sergerad Nov 15, 2025
1598277
Add mockchain::prove_block()
sergerad Nov 20, 2025
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
30 changes: 0 additions & 30 deletions crates/miden-block-prover/src/errors.rs

This file was deleted.

3 changes: 0 additions & 3 deletions crates/miden-block-prover/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
mod errors;
pub use errors::ProvenBlockError;

mod local_block_prover;
pub use local_block_prover::LocalBlockProver;
297 changes: 17 additions & 280 deletions crates/miden-block-prover/src/local_block_prover.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,9 @@
use std::collections::BTreeMap;
use std::vec::Vec;

use miden_lib::transaction::TransactionKernel;
use miden_objects::Word;
use miden_objects::account::AccountId;
use miden_objects::block::{
AccountUpdateWitness,
BlockAccountUpdate,
BlockHeader,
BlockNoteIndex,
BlockNoteTree,
BlockNumber,
NullifierWitness,
OutputNoteBatch,
PartialAccountTree,
PartialNullifierTree,
ProposedBlock,
ProvenBlock,
};
use miden_objects::note::Nullifier;
use miden_objects::transaction::PartialBlockchain;

use crate::errors::ProvenBlockError;
use miden_objects::block::{ProvenBlock, SignedBlock};

// LOCAL BLOCK PROVER
// ================================================================================================

/// A local prover for blocks, proving a [`ProposedBlock`] and returning a [`ProvenBlock`].
/// A local prover for blocks, proving a [`SignedBlock`] and returning a [`ProvenBlock`].
#[derive(Clone)]
pub struct LocalBlockProver {}

Expand All @@ -38,283 +15,43 @@ impl LocalBlockProver {
Self {}
}

/// Proves the provided [`ProposedBlock`] into a [`ProvenBlock`].
/// Proves the provided [`SignedBlock`] into a [`ProvenBlock`].
///
/// For now this does not actually verify the batches or create a block proof, but will be added
/// in the future.
///
/// # Errors
///
/// Returns an error if:
/// - the account witnesses provided in the proposed block result in a different account tree
/// - the account witnesses provided in the signed block result in a different account tree root
/// than the contained previous block header commits to.
/// - the nullifier witnesses provided in the signed block result in a different nullifier tree
/// root than the contained previous block header commits to.
/// - the nullifier witnesses provided in the proposed block result in a different nullifier
/// tree root than the contained previous block header commits to.
/// - the account tree root in the previous block header does not match the root of the tree
/// computed from the account witnesses.
/// - the nullifier tree root in the previous block header does not match the root of the tree
/// computed from the nullifier witnesses.
pub fn prove(&self, proposed_block: ProposedBlock) -> Result<ProvenBlock, ProvenBlockError> {
self.prove_without_batch_verification_inner(proposed_block)
pub fn prove(&self, signed_block: SignedBlock) -> ProvenBlock {
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 method no longer returns a Result so, the Errors section is obsolete.

But also, when we eventually get to proof generation, sending just the SignedBlock will not be sufficient as the prover would need at least:

  1. A list of transaction batches that made up the block.
  2. All the relevant witness data (e.g., account and nullifier witnesses).

Basically, the prover will need either the ProposedBlock or Vec<ProvenBatch> + BlockInputs. So, maybe the signature of this method should be:

pub fn prove(
    &self,
    tx_batches: OrderedBatches,
    block_header: BlockHeader,
    block_inputs: BlockInputs,
) -> Result<BlockProof, BlockProverError> {
    ...
}

Basically, the idea is that this method will, in the future, run the block kernel, the kernel will output the info that we'd be able to check against the block_header - and if all matches, it would return the proof.

Then, the caller of this method (i.e., the store) would construct the ProvenBlock from SignedBlock + BlockProof.

Since we don't have this process working yet, we have the following options:

  1. We get rid of the block prover entirely and re-introduce it later.
  2. We stub it out as something similar to what I have above, and just return a dummy proof.
    a. We could also run the process of re-building ProposedBlock and then using it to check that the block_header matches the result.

I have a slight preference towards the second approach.

Copy link
Copy Markdown
Contributor Author

@sergerad sergerad Nov 10, 2025

Choose a reason for hiding this comment

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

BlockInputs is used to create ProposedBlock I don't think it makes sense to then use that to create a proof but maybe ProposedBlock instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have this so far

        let (header, body) = render_proposed_block(proposed_block).unwrap();
        let signed_block = SignedBlock::new_unchecked(header, body);
        let block_proof = LocalBlockProver::new(0).prove_dummy(&signed_block).unwrap();
        ProvenBlock::new_unchecked(signed_block, block_proof)

We may not want to pass signed block into prove fn. Its hard to reason about these types and APIs without starting to integrate them into the node stack.

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.

We can't pass signed_block to the block prover because it won't have enough info to construct a block proof. The block prover will essentially run the same code as the constructor of proposed block + building the block body - so, it needs the same inputs as the proposed block construct. It also needs the block header because block header will contain the signature.

Since we don't have the MASM code for the above yet, I think the block prover can do the following:

  1. Build ProposedBlock from the provided parameters.
  2. Validate that BlockHeader info matches the ProposedBlock (this may require building BlockBody).

self.prove_without_batch_verification_inner(signed_block)
}

/// Proves the provided [`ProposedBlock`] into a [`ProvenBlock`], **without verifying batches
/// Proves the provided [`SignedBlock`] into a [`ProvenBlock`], **without verifying batches
/// and proving the block**.
///
/// This is exposed for testing purposes.
#[cfg(any(feature = "testing", test))]
pub fn prove_dummy(
&self,
proposed_block: ProposedBlock,
) -> Result<ProvenBlock, ProvenBlockError> {
self.prove_without_batch_verification_inner(proposed_block)
pub fn prove_dummy(&self, signed_block: SignedBlock) -> ProvenBlock {
self.prove_without_batch_verification_inner(signed_block)
}

/// Proves the provided [`ProposedBlock`] into a [`ProvenBlock`].
///
/// The assumptions of this method are that the checks made by construction of a
/// [`ProposedBlock`] are enforced.
/// Proves the provided [`SignedBlock`] into a [`ProvenBlock`].
///
/// See [`Self::prove`] for more details.
fn prove_without_batch_verification_inner(
&self,
proposed_block: ProposedBlock,
) -> Result<ProvenBlock, ProvenBlockError> {
// Get the block number and timestamp of the new block and compute the tx commitment.
// --------------------------------------------------------------------------------------------

let block_num = proposed_block.block_num();
let timestamp = proposed_block.timestamp();

// Split the proposed block into its parts.
// --------------------------------------------------------------------------------------------

let (
batches,
account_updated_witnesses,
output_note_batches,
created_nullifiers,
partial_blockchain,
prev_block_header,
) = proposed_block.into_parts();

let prev_block_commitment = prev_block_header.commitment();
// For now we copy the parameters of the previous header, which means the parameters set on
// the genesis block will be passed through. Eventually, the contained base fees will be
// updated based on the demand in the currently proposed block.
let fee_parameters = prev_block_header.fee_parameters().clone();

// Compute the root of the block note tree.
// --------------------------------------------------------------------------------------------

let note_tree = compute_block_note_tree(&output_note_batches);
let note_root = note_tree.root();

// Insert the created nullifiers into the nullifier tree to compute its new root.
// --------------------------------------------------------------------------------------------

let (created_nullifiers, new_nullifier_root) =
compute_nullifiers(created_nullifiers, &prev_block_header, block_num)?;

// Insert the state commitments of updated accounts into the account tree to compute its new
// root.
// --------------------------------------------------------------------------------------------

let new_account_root =
compute_account_root(&account_updated_witnesses, &prev_block_header)?;

// Insert the previous block header into the block partial blockchain to get the new chain
// commitment.
// --------------------------------------------------------------------------------------------

let new_chain_commitment = compute_chain_commitment(partial_blockchain, prev_block_header);

// Transform the account update witnesses into block account updates.
// --------------------------------------------------------------------------------------------

let updated_accounts = account_updated_witnesses
.into_iter()
.map(|(account_id, update_witness)| {
let (
_initial_state_commitment,
final_state_commitment,
// Note that compute_account_root took out this value so it should not be used.
_initial_state_proof,
details,
) = update_witness.into_parts();
BlockAccountUpdate::new(account_id, final_state_commitment, details)
})
.collect();

// Aggregate the verified transactions of all batches.
// --------------------------------------------------------------------------------------------

let txs = batches.into_transactions();
let tx_commitment = txs.commitment();

// Construct the new block header.
// --------------------------------------------------------------------------------------------

// Currently undefined and reserved for future use.
// See miden-base/1155.
let version = 0;
let tx_kernel_commitment = TransactionKernel.to_commitment();

// For now, we're not actually proving the block.
let proof_commitment = Word::empty();

let header = BlockHeader::new(
version,
prev_block_commitment,
block_num,
new_chain_commitment,
new_account_root,
new_nullifier_root,
note_root,
tx_commitment,
tx_kernel_commitment,
proof_commitment,
fee_parameters,
timestamp,
);

// Construct the new proven block.
// --------------------------------------------------------------------------------------------
fn prove_without_batch_verification_inner(&self, signed_block: SignedBlock) -> ProvenBlock {
// Deconstruct signed block into its components.
let (header, body) = signed_block.into_parts();

let proven_block = ProvenBlock::new_unchecked(
header,
updated_accounts,
output_note_batches,
created_nullifiers,
txs,
);

Ok(proven_block)
}
}

/// Computes the new nullifier root by inserting the nullifier witnesses into a partial nullifier
/// tree and marking each nullifier as spent in the given block number. Returns the list of
/// nullifiers and the new nullifier tree root.
fn compute_nullifiers(
created_nullifiers: BTreeMap<Nullifier, NullifierWitness>,
prev_block_header: &BlockHeader,
block_num: BlockNumber,
) -> Result<(Vec<Nullifier>, Word), ProvenBlockError> {
// If no nullifiers were created, the nullifier tree root is unchanged.
if created_nullifiers.is_empty() {
return Ok((Vec::new(), prev_block_header.nullifier_root()));
}

let nullifiers: Vec<Nullifier> = created_nullifiers.keys().copied().collect();

// First, reconstruct the current nullifier tree with the merkle paths of the nullifiers we want
// to update.
// Due to the guarantees of ProposedBlock we can safely assume that each nullifier is mapped to
// its corresponding nullifier witness, so we don't have to check again whether they match.
let mut partial_nullifier_tree =
PartialNullifierTree::with_witnesses(created_nullifiers.into_values())
.map_err(ProvenBlockError::NullifierWitnessRootMismatch)?;

// Check the nullifier tree root in the previous block header matches the reconstructed tree's
// root.
if prev_block_header.nullifier_root() != partial_nullifier_tree.root() {
return Err(ProvenBlockError::StaleNullifierTreeRoot {
prev_block_nullifier_root: prev_block_header.nullifier_root(),
stale_nullifier_root: partial_nullifier_tree.root(),
});
}

// Second, mark each nullifier as spent in the tree. Note that checking whether each nullifier
// is unspent is checked as part of the proposed block.

// SAFETY: As mentioned above, we can safely assume that each nullifier's witness was
// added and every nullifier should be tracked by the partial tree and
// therefore updatable.
partial_nullifier_tree.mark_spent(nullifiers.iter().copied(), block_num).expect(
"nullifiers' merkle path should have been added to the partial tree and the nullifiers should be unspent",
);

Ok((nullifiers, partial_nullifier_tree.root()))
}

/// Adds the commitment of the previous block header to the partial blockchain to compute the new
/// chain commitment.
fn compute_chain_commitment(
mut partial_blockchain: PartialBlockchain,
prev_block_header: BlockHeader,
) -> Word {
// SAFETY: This does not panic as long as the block header we're adding is the next one in the
// chain which is validated as part of constructing a `ProposedBlock`.
partial_blockchain.add_block(prev_block_header, true);
partial_blockchain.peaks().hash_peaks()
}

/// Computes the new account tree root after the given updates.
///
/// It uses a PartialMerkleTree for now while we use a SimpleSmt for the account tree. Once that is
/// updated to an Smt, we can use a PartialSmt instead.
fn compute_account_root(
updated_accounts: &[(AccountId, AccountUpdateWitness)],
prev_block_header: &BlockHeader,
) -> Result<Word, ProvenBlockError> {
// If no accounts were updated, the account tree root is unchanged.
if updated_accounts.is_empty() {
return Ok(prev_block_header.account_root());
}

// First reconstruct the current account tree from the provided merkle paths.
// If a witness points to a leaf where multiple account IDs share the same prefix, this will
// return an error.
let mut partial_account_tree = PartialAccountTree::with_witnesses(
updated_accounts.iter().map(|(_, update_witness)| update_witness.to_witness()),
)
.map_err(|source| ProvenBlockError::AccountWitnessTracking { source })?;

// Check the account tree root in the previous block header matches the reconstructed tree's
// root.
if prev_block_header.account_root() != partial_account_tree.root() {
return Err(ProvenBlockError::StaleAccountTreeRoot {
prev_block_account_root: prev_block_header.account_root(),
stale_account_root: partial_account_tree.root(),
});
// For now, we're not actually proving the block. Just return the block.
ProvenBlock::new_unchecked(header, body)
}

// Second, update the account tree by inserting the new final account state commitments to
// compute the new root of the account tree.
// If an account ID's prefix already exists in the tree, this will return an error.
// Note that we have inserted all witnesses that we want to update into the partial account
// tree, so we should not run into the untracked key error.
partial_account_tree
.upsert_state_commitments(updated_accounts.iter().map(|(account_id, update_witness)| {
(*account_id, update_witness.final_state_commitment())
}))
.map_err(|source| ProvenBlockError::AccountIdPrefixDuplicate { source })?;

Ok(partial_account_tree.root())
}

/// Compute the block note tree from the output note batches.
fn compute_block_note_tree(output_note_batches: &[OutputNoteBatch]) -> BlockNoteTree {
let output_notes_iter =
output_note_batches.iter().enumerate().flat_map(|(batch_idx, notes)| {
notes.iter().map(move |(note_idx_in_batch, note)| {
(
// SAFETY: The proposed block contains at most the max allowed number of
// batches and each batch is guaranteed to contain at most
// the max allowed number of output notes.
BlockNoteIndex::new(batch_idx, *note_idx_in_batch)
.expect("max batches in block and max notes in batches should be enforced"),
note.id(),
*note.metadata(),
)
})
});

// SAFETY: We only construct proposed blocks that:
// - do not contain duplicates
// - contain at most the max allowed number of batches and each batch is guaranteed to contain
// at most the max allowed number of output notes.
BlockNoteTree::with_entries(output_notes_iter)
.expect("the output notes of the block should not contain duplicates and contain at most the allowed maximum")
}
Loading
Loading