Skip to main content

miden_protocol/block/
proven_block.rs

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// PROVEN BLOCK ERROR
15// ================================================================================================
16
17#[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// PROVEN BLOCK
74// ================================================================================================
75
76/// Represents a block in the Miden blockchain that has been signed and proven.
77///
78/// Blocks transition through proposed, signed, and proven states. This struct represents the final,
79/// proven state of a block.
80///
81/// Proven blocks are the final, canonical blocks in the chain.
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct ProvenBlock {
84    /// The header of the proven block.
85    header: BlockHeader,
86
87    /// The body of the proven block.
88    body: BlockBody,
89
90    /// The validators' positional signatures over the block header.
91    signatures: BlockSignatures,
92
93    /// The proof of the block.
94    proof: BlockProof,
95}
96
97impl ProvenBlock {
98    /// Returns a new [`ProvenBlock`] instantiated from the provided components.
99    ///
100    /// Validates that the header and body correspond by checking the transaction commitment and
101    /// note root. This does NOT verify the validator signatures, which can only be checked against
102    /// the parent block's validator keys; call [`Self::validate`] with the parent header to
103    /// authenticate the block.
104    ///
105    /// Involves non-trivial computation. Use [`Self::new_unchecked`] if the validation is not
106    /// necessary.
107    ///
108    /// Note: this does not fully validate the consistency of provided components. Specifically,
109    /// we cannot validate that:
110    /// - That applying the account updates in the block body to the account tree represented by the
111    ///   root from the previous block header would actually result in the account root in the
112    ///   provided header.
113    /// - That inserting the created nullifiers in the block body to the nullifier tree represented
114    ///   by the root from the previous block header would actually result in the nullifier root in
115    ///   the provided header.
116    ///
117    /// # Errors
118    /// Returns an error if:
119    /// - If the transaction commitment in the block header is inconsistent with the transactions
120    ///   included in the block body.
121    /// - If the note root in the block header is inconsistent with the notes included in the block
122    ///   body.
123    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    /// Returns a new [`ProvenBlock`] instantiated from the provided components.
137    ///
138    /// # Warning
139    ///
140    /// This constructor does not do any validation as to whether the arguments correctly correspond
141    /// to each other, which could cause errors downstream.
142    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    /// Validates that the components of the proven block correspond by checking the transaction
152    /// commitment and note root, and -- when `parent` is provided -- authenticates the block
153    /// against its parent.
154    ///
155    /// Pass `Some(parent)` to additionally authenticate the block against its parent; pass `None`
156    /// for the genesis block, which has no parent, or when only self-consistency is required.
157    ///
158    /// `parent` MUST come from already-trusted chain state. Because `prev_block_commitment` is
159    /// attacker-controlled, passing an untrusted parent would let a forged block self-authorize.
160    ///
161    /// Validation involves non-trivial computation, and depending on the size of the block may
162    /// take non-negligible amount of time.
163    ///
164    /// Note: this does not fully validate the consistency of internal components. Specifically,
165    /// we cannot validate that:
166    /// - That applying the account updates in the block body to the account tree represented by the
167    ///   root from the previous block header would actually result in the account root in the
168    ///   provided header.
169    /// - That inserting the created nullifiers in the block body to the nullifier tree represented
170    ///   by the root from the previous block header would actually result in the nullifier root in
171    ///   the provided header.
172    ///
173    /// # Errors
174    /// Returns an error if:
175    /// - the transaction commitment in the block header is inconsistent with the transactions
176    ///   included in the block body;
177    /// - the note root in the block header is inconsistent with the notes included in the block
178    ///   body; or
179    /// - a `parent` is provided and the block is not authorized by it: the block is the genesis
180    ///   block (which has no parent), the parent's number or commitment do not match, or the
181    ///   signatures do not verify against the parent's validator keys.
182    pub fn validate(&self, parent: Option<&BlockHeader>) -> Result<(), ProvenBlockError> {
183        // Validate that header / body transaction commitments match.
184        self.validate_tx_commitment()?;
185
186        // Validate that header / body note roots match.
187        self.validate_note_root()?;
188
189        // When a trusted parent is provided, authenticate the block against it.
190        if let Some(parent) = parent {
191            self.header.validate_against_parent(parent, &self.signatures)?;
192        }
193
194        Ok(())
195    }
196
197    /// Returns the proof security level of the block.
198    pub fn proof_security_level(&self) -> u32 {
199        MIN_PROOF_SECURITY_LEVEL
200    }
201
202    /// Returns the header of the block.
203    pub fn header(&self) -> &BlockHeader {
204        &self.header
205    }
206
207    /// Returns the body of the block.
208    pub fn body(&self) -> &BlockBody {
209        &self.body
210    }
211
212    /// Returns the validators' positional signatures over the block header.
213    pub fn signatures(&self) -> &BlockSignatures {
214        &self.signatures
215    }
216
217    /// Returns the proof of the block.
218    pub fn proof(&self) -> &BlockProof {
219        &self.proof
220    }
221
222    /// Destructures this proven block into individual parts.
223    pub fn into_parts(self) -> (BlockHeader, BlockBody, BlockSignatures, BlockProof) {
224        (self.header, self.body, self.signatures, self.proof)
225    }
226
227    // HELPER METHODS
228    // --------------------------------------------------------------------------------------------
229
230    /// Validates that the transaction commitments between the header and body match for this proven
231    /// block.
232    ///
233    /// Involves non-trivial computation of the body's transaction commitment.
234    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    /// Validates that the header's note tree root matches that of the body.
245    ///
246    /// Involves non-trivial computation of the body's note tree.
247    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
258// SERIALIZATION
259// ================================================================================================
260
261impl 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// TESTS
284// ================================================================================================
285
286#[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    /// Builds block 1 linked to `parent` and signed by `signers` over the validator set
308    /// `parent_keys` committed to by the parent. Here we only confirm `ProvenBlock::validate`
309    /// wires the signatures and parent header through to the shared check.
310    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        // The block is signed by a full, valid validator set of the same size the parent never
343        // committed.
344        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}