1use alloc::string::ToString;
2
3use miden_core::Word;
4
5use crate::MIN_PROOF_SECURITY_LEVEL;
6use crate::block::header::ParentValidationError;
7use crate::block::{BlockBody, BlockHeader, BlockNumber, BlockSignatures};
8use crate::utils::serde::{
9 ByteReader,
10 ByteWriter,
11 Deserializable,
12 DeserializationError,
13 Serializable,
14};
15use crate::vm::ExecutionProof;
16
17#[derive(Debug, thiserror::Error)]
21pub enum ProvenBlockError {
22 #[error("block proof contains precompiles")]
23 BlockProofContainsPrecompiles,
24 #[error(
25 "proven block has {actual} signatures but its parent's validator set has {expected} keys"
26 )]
27 SignatureCountMismatch { expected: usize, actual: usize },
28 #[error(
29 "proven block signature at position {position} does not verify against the parent's validator key at that position"
30 )]
31 InvalidSignatureAtPosition { position: usize },
32 #[error(
33 "header tx commitment ({header_tx_commitment}) does not match body tx commitment ({body_tx_commitment})"
34 )]
35 TxCommitmentMismatch {
36 header_tx_commitment: Word,
37 body_tx_commitment: Word,
38 },
39 #[error(
40 "proven block header note root ({header_root}) does not match the corresponding body's note root ({body_root})"
41 )]
42 NoteRootMismatch { header_root: Word, body_root: Word },
43 #[error(
44 "proven block previous block commitment ({expected}) does not match expected parent's block commitment ({parent})"
45 )]
46 ParentCommitmentMismatch { expected: Word, parent: Word },
47 #[error("parent block number ({parent}) is not proven block number - 1 ({expected})")]
48 ParentNumberMismatch {
49 expected: BlockNumber,
50 parent: BlockNumber,
51 },
52 #[error("supplied parent block ({parent}) cannot be parent to genesis block")]
53 GenesisBlockHasNoParent { parent: BlockNumber },
54}
55
56impl From<ParentValidationError> for ProvenBlockError {
57 fn from(err: ParentValidationError) -> Self {
58 match err {
59 ParentValidationError::SignatureCountMismatch { expected, actual } => {
60 Self::SignatureCountMismatch { expected, actual }
61 },
62 ParentValidationError::InvalidSignatureAtPosition { position } => {
63 Self::InvalidSignatureAtPosition { position }
64 },
65 ParentValidationError::ParentNumberMismatch { expected, parent } => {
66 Self::ParentNumberMismatch { expected, parent }
67 },
68 ParentValidationError::ParentCommitmentMismatch { expected, parent } => {
69 Self::ParentCommitmentMismatch { expected, parent }
70 },
71 ParentValidationError::GenesisBlockHasNoParent { parent } => {
72 Self::GenesisBlockHasNoParent { parent }
73 },
74 }
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct ProvenBlock {
89 header: BlockHeader,
91
92 body: BlockBody,
94
95 signatures: BlockSignatures,
97
98 proof: ExecutionProof,
106}
107
108impl ProvenBlock {
109 pub fn new(
136 header: BlockHeader,
137 body: BlockBody,
138 signatures: BlockSignatures,
139 proof: ExecutionProof,
140 ) -> Result<Self, ProvenBlockError> {
141 let proven_block = Self { header, signatures, body, proof };
142
143 proven_block.validate(None)?;
144
145 Ok(proven_block)
146 }
147
148 pub fn new_unchecked(
155 header: BlockHeader,
156 body: BlockBody,
157 signatures: BlockSignatures,
158 proof: ExecutionProof,
159 ) -> Self {
160 Self { header, signatures, body, proof }
161 }
162
163 pub fn validate(&self, parent: Option<&BlockHeader>) -> Result<(), ProvenBlockError> {
196 self.validate_proof()?;
197
198 self.validate_tx_commitment()?;
200
201 self.validate_note_root()?;
203
204 if let Some(parent) = parent {
206 self.header.validate_against_parent(parent, &self.signatures)?;
207 }
208
209 Ok(())
210 }
211
212 pub fn proof_security_level(&self) -> u32 {
214 MIN_PROOF_SECURITY_LEVEL
215 }
216
217 pub fn header(&self) -> &BlockHeader {
219 &self.header
220 }
221
222 pub fn body(&self) -> &BlockBody {
224 &self.body
225 }
226
227 pub fn signatures(&self) -> &BlockSignatures {
229 &self.signatures
230 }
231
232 pub fn proof(&self) -> &ExecutionProof {
234 &self.proof
235 }
236
237 pub fn into_parts(self) -> (BlockHeader, BlockBody, BlockSignatures, ExecutionProof) {
239 (self.header, self.body, self.signatures, self.proof)
240 }
241
242 fn validate_proof(&self) -> Result<(), ProvenBlockError> {
247 if self.proof.has_precompiles() {
248 Err(ProvenBlockError::BlockProofContainsPrecompiles)
249 } else {
250 Ok(())
251 }
252 }
253
254 fn validate_tx_commitment(&self) -> Result<(), ProvenBlockError> {
259 let header_tx_commitment = self.header.tx_commitment();
260 let body_tx_commitment = self.body.transactions().commitment();
261 if header_tx_commitment != body_tx_commitment {
262 Err(ProvenBlockError::TxCommitmentMismatch { header_tx_commitment, body_tx_commitment })
263 } else {
264 Ok(())
265 }
266 }
267
268 fn validate_note_root(&self) -> Result<(), ProvenBlockError> {
272 let header_root = self.header.note_root();
273 let body_root = self.body.compute_block_note_tree().root();
274 if header_root != body_root {
275 Err(ProvenBlockError::NoteRootMismatch { header_root, body_root })
276 } else {
277 Ok(())
278 }
279 }
280}
281
282impl Serializable for ProvenBlock {
286 fn write_into<W: ByteWriter>(&self, target: &mut W) {
287 self.header.write_into(target);
288 self.body.write_into(target);
289 self.signatures.write_into(target);
290 self.proof.write_into(target);
291 }
292}
293
294impl Deserializable for ProvenBlock {
295 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
296 let block = Self {
297 header: BlockHeader::read_from(source)?,
298 body: BlockBody::read_from(source)?,
299 signatures: BlockSignatures::read_from(source)?,
300 proof: ExecutionProof::read_from(source)?,
301 };
302
303 block
304 .validate_proof()
305 .map_err(|error| DeserializationError::InvalidValue(error.to_string()))?;
306
307 Ok(block)
308 }
309}
310
311#[cfg(test)]
315mod tests {
316 use alloc::vec::Vec;
317
318 use miden_crypto::dsa::ecdsa_k256_keccak::SigningKey;
319
320 use super::*;
321 use crate::Word;
322 use crate::block::ValidatorConfig;
323 use crate::transaction::OrderedTransactionHeaders;
324
325 fn empty_body() -> BlockBody {
326 BlockBody::new_unchecked(
327 Vec::new(),
328 Vec::new(),
329 Vec::new(),
330 OrderedTransactionHeaders::new_unchecked(Vec::new()),
331 )
332 }
333
334 fn block_one(
338 parent: &BlockHeader,
339 parent_keys: &ValidatorConfig,
340 signers: &[SigningKey],
341 ) -> ProvenBlock {
342 let next_keys = ValidatorConfig::random_with_signers(3).1;
343 let header = BlockHeader::new_dummy(1, parent.commitment(), next_keys);
344 let signatures = parent_keys.sign_all(signers, header.commitment());
345 ProvenBlock::new_unchecked(
346 header,
347 empty_body(),
348 signatures,
349 crate::testing::dummy_execution_proof(),
350 )
351 }
352
353 #[test]
354 fn validate_accepts_committed_signers() {
355 let (signers, keys) = ValidatorConfig::random_with_signers(3);
356 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
357 block_one(&parent, &keys, &signers).validate(Some(&parent)).unwrap();
358 }
359
360 #[test]
361 fn validate_accepts_single_validator() {
362 let (signers, keys) = ValidatorConfig::random_with_signers(1);
363 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
364 block_one(&parent, &keys, &signers).validate(Some(&parent)).unwrap();
365 }
366
367 #[test]
368 fn rejects_proofs_with_precompiles() {
369 let (signers, keys) = ValidatorConfig::random_with_signers(1);
370 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
371 let block = block_one(&parent, &keys, &signers);
372 let (header, body, signatures, _) = block.into_parts();
373
374 for proof in [
375 crate::testing::dummy_deferred_execution_proof(),
376 crate::testing::dummy_precompile_execution_proof(),
377 ] {
378 let error =
379 ProvenBlock::new(header.clone(), body.clone(), signatures.clone(), proof.clone())
380 .unwrap_err();
381 assert!(matches!(error, ProvenBlockError::BlockProofContainsPrecompiles));
382
383 let block =
384 ProvenBlock::new_unchecked(header.clone(), body.clone(), signatures.clone(), proof);
385 let error = ProvenBlock::read_from_bytes(&block.to_bytes()).unwrap_err();
386 assert!(matches!(error, DeserializationError::InvalidValue(_)));
387 }
388 }
389
390 #[test]
391 fn validate_rejects_uncommitted_signers() {
392 let (_, keys) = ValidatorConfig::random_with_signers(3);
393 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
394 let next_keys = ValidatorConfig::random_with_signers(3).1;
395 let header = BlockHeader::new_dummy(1, parent.commitment(), next_keys);
396
397 let (impostor_signers, impostor_keys) = ValidatorConfig::random_with_signers(3);
400 let signatures = impostor_keys.sign_all(&impostor_signers, header.commitment());
401 let block = ProvenBlock::new_unchecked(
402 header,
403 empty_body(),
404 signatures,
405 crate::testing::dummy_execution_proof(),
406 );
407
408 let result = block.validate(Some(&parent));
409 assert!(matches!(result, Err(ProvenBlockError::InvalidSignatureAtPosition { .. })));
410 }
411}