1use miden_core::Word;
2
3use crate::MIN_PROOF_SECURITY_LEVEL;
4use crate::block::header::ParentValidationError;
5use crate::block::{BlockBody, BlockHeader, BlockNumber, BlockProof, BlockSignatures};
6use crate::utils::serde::{
7 ByteReader,
8 ByteWriter,
9 Deserializable,
10 DeserializationError,
11 Serializable,
12};
13
14#[derive(Debug, thiserror::Error)]
18pub enum ProvenBlockError {
19 #[error(
20 "proven block has {actual} signatures but its parent's validator set has {expected} keys"
21 )]
22 SignatureCountMismatch { expected: usize, actual: usize },
23 #[error(
24 "proven block signature at position {position} does not verify against the parent's validator key at that position"
25 )]
26 InvalidSignatureAtPosition { position: usize },
27 #[error(
28 "header tx commitment ({header_tx_commitment}) does not match body tx commitment ({body_tx_commitment})"
29 )]
30 TxCommitmentMismatch {
31 header_tx_commitment: Word,
32 body_tx_commitment: Word,
33 },
34 #[error(
35 "proven block header note root ({header_root}) does not match the corresponding body's note root ({body_root})"
36 )]
37 NoteRootMismatch { header_root: Word, body_root: Word },
38 #[error(
39 "proven block previous block commitment ({expected}) does not match expected parent's block commitment ({parent})"
40 )]
41 ParentCommitmentMismatch { expected: Word, parent: Word },
42 #[error("parent block number ({parent}) is not proven block number - 1 ({expected})")]
43 ParentNumberMismatch {
44 expected: BlockNumber,
45 parent: BlockNumber,
46 },
47 #[error("supplied parent block ({parent}) cannot be parent to genesis block")]
48 GenesisBlockHasNoParent { parent: BlockNumber },
49}
50
51impl From<ParentValidationError> for ProvenBlockError {
52 fn from(err: ParentValidationError) -> Self {
53 match err {
54 ParentValidationError::SignatureCountMismatch { expected, actual } => {
55 Self::SignatureCountMismatch { expected, actual }
56 },
57 ParentValidationError::InvalidSignatureAtPosition { position } => {
58 Self::InvalidSignatureAtPosition { position }
59 },
60 ParentValidationError::ParentNumberMismatch { expected, parent } => {
61 Self::ParentNumberMismatch { expected, parent }
62 },
63 ParentValidationError::ParentCommitmentMismatch { expected, parent } => {
64 Self::ParentCommitmentMismatch { expected, parent }
65 },
66 ParentValidationError::GenesisBlockHasNoParent { parent } => {
67 Self::GenesisBlockHasNoParent { parent }
68 },
69 }
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct ProvenBlock {
84 header: BlockHeader,
86
87 body: BlockBody,
89
90 signatures: BlockSignatures,
92
93 proof: BlockProof,
95}
96
97impl ProvenBlock {
98 pub fn new(
124 header: BlockHeader,
125 body: BlockBody,
126 signatures: BlockSignatures,
127 proof: BlockProof,
128 ) -> Result<Self, ProvenBlockError> {
129 let proven_block = Self { header, signatures, body, proof };
130
131 proven_block.validate(None)?;
132
133 Ok(proven_block)
134 }
135
136 pub fn new_unchecked(
143 header: BlockHeader,
144 body: BlockBody,
145 signatures: BlockSignatures,
146 proof: BlockProof,
147 ) -> Self {
148 Self { header, signatures, body, proof }
149 }
150
151 pub fn validate(&self, parent: Option<&BlockHeader>) -> Result<(), ProvenBlockError> {
183 self.validate_tx_commitment()?;
185
186 self.validate_note_root()?;
188
189 if let Some(parent) = parent {
191 self.header.validate_against_parent(parent, &self.signatures)?;
192 }
193
194 Ok(())
195 }
196
197 pub fn proof_security_level(&self) -> u32 {
199 MIN_PROOF_SECURITY_LEVEL
200 }
201
202 pub fn header(&self) -> &BlockHeader {
204 &self.header
205 }
206
207 pub fn body(&self) -> &BlockBody {
209 &self.body
210 }
211
212 pub fn signatures(&self) -> &BlockSignatures {
214 &self.signatures
215 }
216
217 pub fn proof(&self) -> &BlockProof {
219 &self.proof
220 }
221
222 pub fn into_parts(self) -> (BlockHeader, BlockBody, BlockSignatures, BlockProof) {
224 (self.header, self.body, self.signatures, self.proof)
225 }
226
227 fn validate_tx_commitment(&self) -> Result<(), ProvenBlockError> {
235 let header_tx_commitment = self.header.tx_commitment();
236 let body_tx_commitment = self.body.transactions().commitment();
237 if header_tx_commitment != body_tx_commitment {
238 Err(ProvenBlockError::TxCommitmentMismatch { header_tx_commitment, body_tx_commitment })
239 } else {
240 Ok(())
241 }
242 }
243
244 fn validate_note_root(&self) -> Result<(), ProvenBlockError> {
248 let header_root = self.header.note_root();
249 let body_root = self.body.compute_block_note_tree().root();
250 if header_root != body_root {
251 Err(ProvenBlockError::NoteRootMismatch { header_root, body_root })
252 } else {
253 Ok(())
254 }
255 }
256}
257
258impl Serializable for ProvenBlock {
262 fn write_into<W: ByteWriter>(&self, target: &mut W) {
263 self.header.write_into(target);
264 self.body.write_into(target);
265 self.signatures.write_into(target);
266 self.proof.write_into(target);
267 }
268}
269
270impl Deserializable for ProvenBlock {
271 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
272 let block = Self {
273 header: BlockHeader::read_from(source)?,
274 body: BlockBody::read_from(source)?,
275 signatures: BlockSignatures::read_from(source)?,
276 proof: BlockProof::read_from(source)?,
277 };
278
279 Ok(block)
280 }
281}
282
283#[cfg(test)]
287mod tests {
288 use alloc::vec::Vec;
289
290 use miden_crypto::dsa::ecdsa_k256_keccak::SigningKey;
291
292 use super::*;
293 use crate::Word;
294 use crate::block::ValidatorKeys;
295 use crate::testing::validator_keys::{random_validator_set as validator_set, sign_all};
296 use crate::transaction::OrderedTransactionHeaders;
297
298 fn empty_body() -> BlockBody {
299 BlockBody::new_unchecked(
300 Vec::new(),
301 Vec::new(),
302 Vec::new(),
303 OrderedTransactionHeaders::new_unchecked(Vec::new()),
304 )
305 }
306
307 fn block_one(
311 parent: &BlockHeader,
312 parent_keys: &ValidatorKeys,
313 signers: &[SigningKey],
314 ) -> ProvenBlock {
315 let next_keys = validator_set(3).1;
316 let header = BlockHeader::new_dummy(1, parent.commitment(), next_keys);
317 let signatures = sign_all(parent_keys, signers, header.commitment());
318 ProvenBlock::new_unchecked(header, empty_body(), signatures, BlockProof::new_dummy())
319 }
320
321 #[test]
322 fn validate_accepts_committed_signers() {
323 let (signers, keys) = validator_set(3);
324 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
325 block_one(&parent, &keys, &signers).validate(Some(&parent)).unwrap();
326 }
327
328 #[test]
329 fn validate_accepts_single_validator() {
330 let (signers, keys) = validator_set(1);
331 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
332 block_one(&parent, &keys, &signers).validate(Some(&parent)).unwrap();
333 }
334
335 #[test]
336 fn validate_rejects_uncommitted_signers() {
337 let (_, keys) = validator_set(3);
338 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
339 let next_keys = validator_set(3).1;
340 let header = BlockHeader::new_dummy(1, parent.commitment(), next_keys);
341
342 let (impostor_signers, impostor_keys) = validator_set(3);
345 let signatures = sign_all(&impostor_keys, &impostor_signers, header.commitment());
346 let block =
347 ProvenBlock::new_unchecked(header, empty_body(), signatures, BlockProof::new_dummy());
348
349 let result = block.validate(Some(&parent));
350 assert!(matches!(result, Err(ProvenBlockError::InvalidSignatureAtPosition { .. })));
351 }
352}