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