Skip to main content

miden_node_proto/domain/
proof_request.rs

1use std::collections::BTreeMap;
2
3use miden_protobuf::{BuildUnchecked, ConversionResultExt, DecodeMessageExt, Verify};
4use miden_protocol::account::AccountId;
5use miden_protocol::batch::OrderedBatches;
6use miden_protocol::block::account_tree::AccountWitness;
7use miden_protocol::block::nullifier_tree::NullifierWitness;
8use miden_protocol::block::{BlockHeader, BlockInputs, ProposedBlock};
9use miden_protocol::note::{NoteId, NoteInclusionProof, Nullifier};
10use miden_protocol::transaction::PartialBlockchain;
11
12use crate::errors::ConversionError;
13use crate::generated as proto;
14
15/// The domain inputs needed by the block prover.
16#[derive(Debug)]
17pub struct BlockProofRequest {
18    pub tx_batches: OrderedBatches,
19    pub block_header: BlockHeader,
20    pub block_inputs: BlockInputs,
21}
22
23impl From<&BlockProofRequest> for proto::block_proving::BlockProofRequest {
24    fn from(value: &BlockProofRequest) -> Self {
25        Self {
26            batches: value.tx_batches.as_slice().iter().map(Into::into).collect(),
27            block_inputs: Some((&value.block_inputs).into()),
28            timestamp: value.block_header.timestamp(),
29            next_validator_config: Some(value.block_header.validator_config().into()),
30            next_protocol_config: value.block_header.next_protocol_config().map(Into::into),
31        }
32    }
33}
34
35impl From<BlockProofRequest> for proto::block_proving::BlockProofRequest {
36    fn from(value: BlockProofRequest) -> Self {
37        Self::from(&value)
38    }
39}
40
41impl BuildUnchecked for proto::block_proving::DecodedBlockProofRequest {
42    type Output = BlockProofRequest;
43    type Error = ConversionError;
44
45    /// Reconstruct the proposed block without verifying batch proofs or parent chain state. Batch
46    /// construction also skips input-note authentication, aggregation, and transaction order. The
47    /// caller must complete these checks before accepting the block.
48    fn build_unchecked(self) -> Result<Self::Output, Self::Error> {
49        // SAFETY: This unchecked constructor leaves parent authentication to its caller.
50        // ProposedBlock checks the supplied chain and witnesses against that parent below.
51        let block_inputs = self.block_inputs.build_unchecked().context("block_inputs")?;
52        // SAFETY: The caller must validate each batch. ProposedBlock checks consistency across
53        // batches, not within each batch.
54        let batches = self.batches.build_unchecked()?;
55        let next_validator_config =
56            self.next_validator_config.verify().context("next_validator_config")?;
57        let next_protocol_config = self.next_protocol_config.verify()?;
58
59        let proposed_block =
60            ProposedBlock::new_at(block_inputs.clone(), batches.clone(), self.timestamp)
61                .map_err(ConversionError::new)?
62                .with_next_validator_config(next_validator_config)
63                .with_next_protocol_config(next_protocol_config);
64        let (block_header, _) =
65            proposed_block.into_header_and_body().map_err(ConversionError::new)?;
66
67        Ok(BlockProofRequest {
68            tx_batches: OrderedBatches::new(batches),
69            block_header,
70            block_inputs,
71        })
72    }
73}
74
75impl From<&BlockInputs> for proto::block_proving::BlockInputs {
76    fn from(value: &BlockInputs) -> Self {
77        Self {
78            prev_block_header: Some(value.prev_block_header().into()),
79            partial_blockchain: Some(value.partial_blockchain().into()),
80            account_witnesses: value
81                .account_witnesses()
82                .iter()
83                .map(|(account_id, witness)| proto::block_proving::AccountWitnessRecord {
84                    account_id: Some((*account_id).into()),
85                    witness: Some(witness.into()),
86                })
87                .collect(),
88            nullifier_witnesses: value
89                .nullifier_witnesses()
90                .iter()
91                .map(|(nullifier, witness)| proto::block_proving::NullifierWitness {
92                    nullifier: Some(nullifier.as_word().into()),
93                    opening: Some(witness.proof().clone().into()),
94                })
95                .collect(),
96            unauthenticated_note_proofs: value
97                .unauthenticated_note_proofs()
98                .iter()
99                .map(Into::into)
100                .collect(),
101        }
102    }
103}
104
105impl BuildUnchecked for proto::block_proving::DecodedBlockInputs {
106    type Output = BlockInputs;
107    type Error = ConversionError;
108
109    /// Build block inputs without checking header linkage, signatures, or protocol transitions.
110    /// This includes headers in the partial blockchain. The caller must check the headers, chain
111    /// root, and witness roots against trusted chain state.
112    fn build_unchecked(self) -> Result<Self::Output, Self::Error> {
113        // SAFETY: The caller must authenticate this parent against trusted chain state.
114        let prev_block_header: BlockHeader =
115            self.prev_block_header.build_unchecked().context("prev_block_header")?;
116        // SAFETY: Construction checks MMR membership. The caller must authenticate the MMR root.
117        let partial_blockchain: PartialBlockchain =
118            self.partial_blockchain.build_unchecked().context("partial_blockchain")?;
119
120        let mut account_witnesses = BTreeMap::<AccountId, AccountWitness>::new();
121        for (index, record) in self.account_witnesses.into_inner().into_iter().enumerate() {
122            let account_id = record
123                .account_id
124                .verify()
125                .with_context(|| format!("account_witnesses[{index}].account_id"))?;
126            let witness = record
127                .witness
128                .verify()
129                .with_context(|| format!("account_witnesses[{index}].witness"))?;
130            if account_witnesses.insert(account_id, witness).is_some() {
131                return Err(ConversionError::message(format!(
132                    "account_witnesses[{index}]: duplicate requested account ID {account_id}"
133                )));
134            }
135        }
136
137        let mut nullifier_witnesses = BTreeMap::<Nullifier, NullifierWitness>::new();
138        for (index, record) in self.nullifier_witnesses.into_inner().into_iter().enumerate() {
139            let nullifier = Nullifier::from_raw(record.nullifier);
140            let proof = record
141                .opening
142                .verify()
143                .with_context(|| format!("nullifier_witnesses[{index}].opening"))?;
144            if nullifier_witnesses.insert(nullifier, NullifierWitness::new(proof)).is_some() {
145                return Err(ConversionError::message(format!(
146                    "nullifier_witnesses[{index}]: duplicate nullifier {nullifier}"
147                )));
148            }
149        }
150
151        let mut unauthenticated_note_proofs = BTreeMap::<NoteId, NoteInclusionProof>::new();
152        for (index, proof) in self.unauthenticated_note_proofs.into_inner().into_iter().enumerate()
153        {
154            let (note_id, proof) = proof
155                .verify()
156                .with_context(|| format!("unauthenticated_note_proofs[{index}]"))?;
157            if unauthenticated_note_proofs.insert(note_id, proof).is_some() {
158                return Err(ConversionError::message(format!(
159                    "unauthenticated_note_proofs[{index}]: duplicate note ID {note_id}"
160                )));
161            }
162        }
163
164        Ok(BlockInputs::new(
165            prev_block_header,
166            partial_blockchain,
167            account_witnesses,
168            nullifier_witnesses,
169            unauthenticated_note_proofs,
170        ))
171    }
172}
173
174impl miden_node_persistence::ProtobufValue for BlockProofRequest {
175    type Message = proto::block_proving::BlockProofRequest;
176
177    fn to_proto(&self) -> Self::Message {
178        self.into()
179    }
180
181    fn from_proto(
182        message: Self::Message,
183    ) -> Result<Self, miden_node_persistence::PersistenceError> {
184        Ok(message.decode_and_build_unchecked()?)
185    }
186}