Skip to main content

miden_protocol/block/
signed_block.rs

1use miden_core::Word;
2use miden_crypto::dsa::ecdsa_k256_keccak::Signature;
3
4use crate::block::{BlockBody, BlockHeader, BlockNumber};
5use crate::utils::serde::{
6    ByteReader,
7    ByteWriter,
8    Deserializable,
9    DeserializationError,
10    Serializable,
11};
12
13// SIGNED BLOCK ERROR
14// ================================================================================================
15
16#[derive(Debug, thiserror::Error)]
17pub enum SignedBlockError {
18    #[error(
19        "ECDSA signature verification failed based on the signed block's header commitment, validator public key and signature"
20    )]
21    InvalidSignature,
22    #[error(
23        "header tx commitment ({header_tx_commitment}) does not match body tx commitment ({body_tx_commitment})"
24    )]
25    TxCommitmentMismatch {
26        header_tx_commitment: Word,
27        body_tx_commitment: Word,
28    },
29    #[error(
30        "signed block previous block commitment ({expected}) does not match expected parent's block commitment ({parent})"
31    )]
32    ParentCommitmentMismatch { expected: Word, parent: Word },
33    #[error("parent block number ({parent}) is not signed block number - 1 ({expected})")]
34    ParentNumberMismatch {
35        expected: BlockNumber,
36        parent: BlockNumber,
37    },
38    #[error(
39        "signed block header note root ({header_root}) does not match the corresponding body's note root ({body_root})"
40    )]
41    NoteRootMismatch { header_root: Word, body_root: Word },
42    #[error("supplied parent block ({parent}) cannot be parent to genesis block")]
43    GenesisBlockHasNoParent { parent: BlockNumber },
44}
45
46// SIGNED BLOCK
47// ================================================================================================
48
49/// Represents a block in the Miden blockchain that has been signed by the Validator.
50///
51/// Signed blocks are applied to the chain's state before they are proven.
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct SignedBlock {
54    /// The header of the Signed block.
55    header: BlockHeader,
56
57    /// The body of the Signed block.
58    body: BlockBody,
59
60    /// The Validator's signature over the block header.
61    signature: Signature,
62}
63
64impl SignedBlock {
65    /// Returns a new [`SignedBlock`] instantiated from the provided components.
66    ///
67    /// Validates that the provided components correspond to each other by verifying the signature,
68    /// and checking for matching commitments and note roots.
69    ///
70    /// Involves non-trivial computation. Use [`Self::new_unchecked`] if the validation is not
71    /// necessary.
72    pub fn new(
73        header: BlockHeader,
74        body: BlockBody,
75        signature: Signature,
76    ) -> Result<Self, SignedBlockError> {
77        let signed_block = Self { header, body, signature };
78
79        // Verify signature.
80        signed_block.validate_signature()?;
81
82        // Validate that header / body transaction commitments match.
83        signed_block.validate_tx_commitment()?;
84
85        // Validate that header / body note roots match.
86        signed_block.validate_note_root()?;
87
88        Ok(signed_block)
89    }
90
91    /// Returns a new [`SignedBlock`] instantiated from the provided components.
92    ///
93    /// # Warning
94    ///
95    /// This constructor does not do any validation as to whether the arguments correctly correspond
96    /// to each other, which could cause errors downstream.
97    pub fn new_unchecked(header: BlockHeader, body: BlockBody, signature: Signature) -> Self {
98        Self { header, signature, body }
99    }
100
101    /// Returns the header of the block.
102    pub fn header(&self) -> &BlockHeader {
103        &self.header
104    }
105
106    /// Returns the body of the block.
107    pub fn body(&self) -> &BlockBody {
108        &self.body
109    }
110
111    /// Returns the Validator's signature over the block header.
112    pub fn signature(&self) -> &Signature {
113        &self.signature
114    }
115
116    /// Destructures this signed block into individual parts.
117    pub fn into_parts(self) -> (BlockHeader, BlockBody, Signature) {
118        (self.header, self.body, self.signature)
119    }
120
121    /// Performs ECDSA signature verification against the header commitment and validator key.
122    fn validate_signature(&self) -> Result<(), SignedBlockError> {
123        if !self.signature.verify(self.header.commitment(), self.header.validator_key()) {
124            Err(SignedBlockError::InvalidSignature)
125        } else {
126            Ok(())
127        }
128    }
129
130    /// Validates that the transaction commitments between the header and body match for this signed
131    /// block.
132    ///
133    /// Involves non-trivial computation of the body's transaction commitment.
134    fn validate_tx_commitment(&self) -> Result<(), SignedBlockError> {
135        let header_tx_commitment = self.header.tx_commitment();
136        let body_tx_commitment = self.body.transactions().commitment();
137        if header_tx_commitment != body_tx_commitment {
138            Err(SignedBlockError::TxCommitmentMismatch { header_tx_commitment, body_tx_commitment })
139        } else {
140            Ok(())
141        }
142    }
143
144    /// Validates that the header's note tree root matches that of the body.
145    ///
146    /// Involves non-trivial computation of the body's note tree.
147    fn validate_note_root(&self) -> Result<(), SignedBlockError> {
148        let header_root = self.header.note_root();
149        let body_root = self.body.compute_block_note_tree().root();
150        if header_root != body_root {
151            Err(SignedBlockError::NoteRootMismatch { header_root, body_root })
152        } else {
153            Ok(())
154        }
155    }
156
157    /// Validates that the provided parent block's commitment and number correctly corresponds to
158    /// the signed block.
159    ///
160    /// # Errors
161    ///
162    /// Returns an error if:
163    /// - The signed block is the genesis block.
164    /// - The parent block number is not the signed block number - 1.
165    /// - The parent block's commitment is not equal to the signed block's previous block
166    ///   commitment.
167    pub fn validate_parent(&self, parent_block: &BlockHeader) -> Result<(), SignedBlockError> {
168        // Check block numbers.
169        if let Some(expected) = self.header.block_num().checked_sub(1) {
170            let parent = parent_block.block_num();
171            if expected != parent {
172                return Err(SignedBlockError::ParentNumberMismatch { expected, parent });
173            }
174
175            // Check commitments.
176            let expected = self.header.prev_block_commitment();
177            let parent = parent_block.commitment();
178            if expected != parent {
179                return Err(SignedBlockError::ParentCommitmentMismatch { expected, parent });
180            }
181
182            Ok(())
183        } else {
184            // Block 0 does not have a parent.
185            let parent = parent_block.block_num();
186            Err(SignedBlockError::GenesisBlockHasNoParent { parent })
187        }
188    }
189}
190
191// SERIALIZATION
192// ================================================================================================
193
194impl Serializable for SignedBlock {
195    fn write_into<W: ByteWriter>(&self, target: &mut W) {
196        self.header.write_into(target);
197        self.body.write_into(target);
198        self.signature.write_into(target);
199    }
200}
201
202impl Deserializable for SignedBlock {
203    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
204        let block = Self {
205            header: BlockHeader::read_from(source)?,
206            body: BlockBody::read_from(source)?,
207            signature: Signature::read_from(source)?,
208        };
209
210        Ok(block)
211    }
212}