1use miden_core::Word;
2
3use crate::block::header::ParentValidationError;
4use crate::block::{BlockBody, BlockHeader, BlockNumber, BlockSignatures};
5use crate::utils::serde::{
6 ByteReader,
7 ByteWriter,
8 Deserializable,
9 DeserializationError,
10 Serializable,
11};
12
13#[derive(Debug, thiserror::Error)]
17pub enum SignedBlockError {
18 #[error(
19 "signed block has {actual} signatures but its parent's validator set has {expected} keys"
20 )]
21 SignatureCountMismatch { expected: usize, actual: usize },
22 #[error(
23 "signed block signature at position {position} does not verify against the parent's validator key at that position"
24 )]
25 InvalidSignatureAtPosition { position: usize },
26 #[error(
27 "header tx commitment ({header_tx_commitment}) does not match body tx commitment ({body_tx_commitment})"
28 )]
29 TxCommitmentMismatch {
30 header_tx_commitment: Word,
31 body_tx_commitment: Word,
32 },
33 #[error(
34 "signed block previous block commitment ({expected}) does not match expected parent's block commitment ({parent})"
35 )]
36 ParentCommitmentMismatch { expected: Word, parent: Word },
37 #[error("parent block number ({parent}) is not signed block number - 1 ({expected})")]
38 ParentNumberMismatch {
39 expected: BlockNumber,
40 parent: BlockNumber,
41 },
42 #[error(
43 "signed block header note root ({header_root}) does not match the corresponding body's note root ({body_root})"
44 )]
45 NoteRootMismatch { header_root: Word, body_root: Word },
46 #[error("supplied parent block ({parent}) cannot be parent to genesis block")]
47 GenesisBlockHasNoParent { parent: BlockNumber },
48}
49
50impl From<ParentValidationError> for SignedBlockError {
51 fn from(err: ParentValidationError) -> Self {
52 match err {
53 ParentValidationError::SignatureCountMismatch { expected, actual } => {
54 Self::SignatureCountMismatch { expected, actual }
55 },
56 ParentValidationError::InvalidSignatureAtPosition { position } => {
57 Self::InvalidSignatureAtPosition { position }
58 },
59 ParentValidationError::ParentNumberMismatch { expected, parent } => {
60 Self::ParentNumberMismatch { expected, parent }
61 },
62 ParentValidationError::ParentCommitmentMismatch { expected, parent } => {
63 Self::ParentCommitmentMismatch { expected, parent }
64 },
65 ParentValidationError::GenesisBlockHasNoParent { parent } => {
66 Self::GenesisBlockHasNoParent { parent }
67 },
68 }
69 }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct SignedBlock {
80 header: BlockHeader,
82
83 body: BlockBody,
85
86 signatures: BlockSignatures,
88}
89
90impl SignedBlock {
91 pub fn new(
101 header: BlockHeader,
102 body: BlockBody,
103 signatures: BlockSignatures,
104 ) -> Result<Self, SignedBlockError> {
105 let signed_block = Self { header, body, signatures };
106
107 signed_block.validate(None)?;
108
109 Ok(signed_block)
110 }
111
112 pub fn new_unchecked(
119 header: BlockHeader,
120 body: BlockBody,
121 signatures: BlockSignatures,
122 ) -> Self {
123 Self { header, signatures, body }
124 }
125
126 pub fn header(&self) -> &BlockHeader {
128 &self.header
129 }
130
131 pub fn body(&self) -> &BlockBody {
133 &self.body
134 }
135
136 pub fn signatures(&self) -> &BlockSignatures {
138 &self.signatures
139 }
140
141 pub fn into_parts(self) -> (BlockHeader, BlockBody, BlockSignatures) {
143 (self.header, self.body, self.signatures)
144 }
145
146 pub fn validate(&self, parent: Option<&BlockHeader>) -> Result<(), SignedBlockError> {
165 self.validate_tx_commitment()?;
167
168 self.validate_note_root()?;
170
171 if let Some(parent) = parent {
173 self.header.validate_against_parent(parent, &self.signatures)?;
174 }
175
176 Ok(())
177 }
178
179 fn validate_tx_commitment(&self) -> Result<(), SignedBlockError> {
184 let header_tx_commitment = self.header.tx_commitment();
185 let body_tx_commitment = self.body.transactions().commitment();
186 if header_tx_commitment != body_tx_commitment {
187 Err(SignedBlockError::TxCommitmentMismatch { header_tx_commitment, body_tx_commitment })
188 } else {
189 Ok(())
190 }
191 }
192
193 fn validate_note_root(&self) -> Result<(), SignedBlockError> {
197 let header_root = self.header.note_root();
198 let body_root = self.body.compute_block_note_tree().root();
199 if header_root != body_root {
200 Err(SignedBlockError::NoteRootMismatch { header_root, body_root })
201 } else {
202 Ok(())
203 }
204 }
205}
206
207impl Serializable for SignedBlock {
211 fn write_into<W: ByteWriter>(&self, target: &mut W) {
212 self.header.write_into(target);
213 self.body.write_into(target);
214 self.signatures.write_into(target);
215 }
216}
217
218impl Deserializable for SignedBlock {
219 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
220 let block = Self {
221 header: BlockHeader::read_from(source)?,
222 body: BlockBody::read_from(source)?,
223 signatures: BlockSignatures::read_from(source)?,
224 };
225
226 Ok(block)
227 }
228}
229
230#[cfg(test)]
234mod tests {
235 use alloc::vec::Vec;
236
237 use miden_crypto::dsa::ecdsa_k256_keccak::SigningKey;
238
239 use super::*;
240 use crate::Word;
241 use crate::block::ValidatorKeys;
242 use crate::testing::validator_keys::{random_validator_set as validator_set, sign_all};
243 use crate::transaction::OrderedTransactionHeaders;
244
245 fn empty_body() -> BlockBody {
246 BlockBody::new_unchecked(
247 Vec::new(),
248 Vec::new(),
249 Vec::new(),
250 OrderedTransactionHeaders::new_unchecked(Vec::new()),
251 )
252 }
253
254 fn block_one(
255 parent: &BlockHeader,
256 parent_keys: &ValidatorKeys,
257 signers: &[SigningKey],
258 ) -> SignedBlock {
259 let next_keys = validator_set(3).1;
260 let header = BlockHeader::new_dummy(1, parent.commitment(), next_keys);
261 let signatures = sign_all(parent_keys, signers, header.commitment());
262 SignedBlock::new_unchecked(header, empty_body(), signatures)
263 }
264
265 #[test]
266 fn validate_accepts_committed_signers() {
267 let (signers, keys) = validator_set(3);
268 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
269 block_one(&parent, &keys, &signers).validate(Some(&parent)).unwrap();
270 }
271
272 #[test]
273 fn validate_accepts_single_validator() {
274 let (signers, keys) = validator_set(1);
275 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
276 block_one(&parent, &keys, &signers).validate(Some(&parent)).unwrap();
277 }
278
279 #[test]
280 fn validate_rejects_uncommitted_signers() {
281 let (_, keys) = validator_set(3);
282 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
283 let next_keys = validator_set(3).1;
284 let header = BlockHeader::new_dummy(1, parent.commitment(), next_keys);
285
286 let (impostor_signers, impostor_keys) = validator_set(3);
289 let signatures = sign_all(&impostor_keys, &impostor_signers, header.commitment());
290 let block = SignedBlock::new_unchecked(header, empty_body(), signatures);
291
292 let result = block.validate(Some(&parent));
293 assert!(matches!(result, Err(SignedBlockError::InvalidSignatureAtPosition { .. })));
294 }
295}