1use alloc::vec::Vec;
2
3use crate::account::AccountId;
4use crate::block::{BlockNumber, BlockSignatures, SignatureVerificationError, ValidatorKeys};
5use crate::utils::serde::{
6 ByteReader,
7 ByteWriter,
8 Deserializable,
9 DeserializationError,
10 Serializable,
11};
12use crate::{Felt, Hasher, Word, ZERO};
13
14#[derive(Debug, Eq, PartialEq, Clone)]
40pub struct BlockHeader {
41 version: u32,
42 prev_block_commitment: Word,
43 block_num: BlockNumber,
44 chain_commitment: Word,
45 account_root: Word,
46 nullifier_root: Word,
47 note_root: Word,
48 tx_commitment: Word,
49 tx_kernel_commitment: Word,
50 validator_keys: ValidatorKeys,
51 fee_parameters: FeeParameters,
52 timestamp: u32,
53 sub_commitment: Word,
54 commitment: Word,
55}
56
57impl BlockHeader {
58 #[allow(clippy::too_many_arguments)]
60 pub fn new(
61 version: u32,
62 prev_block_commitment: Word,
63 block_num: BlockNumber,
64 chain_commitment: Word,
65 account_root: Word,
66 nullifier_root: Word,
67 note_root: Word,
68 tx_commitment: Word,
69 tx_kernel_commitment: Word,
70 validator_keys: ValidatorKeys,
71 fee_parameters: FeeParameters,
72 timestamp: u32,
73 ) -> Self {
74 let sub_commitment = Self::compute_sub_commitment(
76 version,
77 prev_block_commitment,
78 chain_commitment,
79 account_root,
80 nullifier_root,
81 tx_commitment,
82 tx_kernel_commitment,
83 &validator_keys,
84 &fee_parameters,
85 timestamp,
86 block_num,
87 );
88
89 let commitment = Hasher::merge(&[sub_commitment, note_root]);
94
95 Self {
96 version,
97 prev_block_commitment,
98 block_num,
99 chain_commitment,
100 account_root,
101 nullifier_root,
102 note_root,
103 tx_commitment,
104 tx_kernel_commitment,
105 validator_keys,
106 fee_parameters,
107 timestamp,
108 sub_commitment,
109 commitment,
110 }
111 }
112
113 pub fn version(&self) -> u32 {
118 self.version
119 }
120
121 pub fn commitment(&self) -> Word {
123 self.commitment
124 }
125
126 pub fn sub_commitment(&self) -> Word {
133 self.sub_commitment
134 }
135
136 pub fn prev_block_commitment(&self) -> Word {
138 self.prev_block_commitment
139 }
140
141 pub fn block_num(&self) -> BlockNumber {
143 self.block_num
144 }
145
146 pub fn block_epoch(&self) -> u16 {
150 self.block_num.block_epoch()
151 }
152
153 pub fn chain_commitment(&self) -> Word {
155 self.chain_commitment
156 }
157
158 pub fn account_root(&self) -> Word {
160 self.account_root
161 }
162
163 pub fn nullifier_root(&self) -> Word {
165 self.nullifier_root
166 }
167
168 pub fn note_root(&self) -> Word {
170 self.note_root
171 }
172
173 pub fn validator_keys(&self) -> &ValidatorKeys {
178 &self.validator_keys
179 }
180
181 pub fn tx_commitment(&self) -> Word {
187 self.tx_commitment
188 }
189
190 pub fn tx_kernel_commitment(&self) -> Word {
195 self.tx_kernel_commitment
196 }
197
198 pub fn fee_parameters(&self) -> &FeeParameters {
200 &self.fee_parameters
201 }
202
203 pub fn timestamp(&self) -> u32 {
205 self.timestamp
206 }
207
208 pub fn epoch_block_num(&self) -> BlockNumber {
210 BlockNumber::from_epoch(self.block_epoch())
211 }
212
213 pub(crate) fn validate_against_parent(
228 &self,
229 parent: &BlockHeader,
230 signatures: &BlockSignatures,
231 ) -> Result<(), ParentValidationError> {
232 let Some(expected_parent_num) = self.block_num().checked_sub(1) else {
234 return Err(ParentValidationError::GenesisBlockHasNoParent {
235 parent: parent.block_num(),
236 });
237 };
238
239 if expected_parent_num != parent.block_num() {
241 return Err(ParentValidationError::ParentNumberMismatch {
242 expected: expected_parent_num,
243 parent: parent.block_num(),
244 });
245 }
246
247 let expected_prev_commitment = self.prev_block_commitment();
249 if expected_prev_commitment != parent.commitment() {
250 return Err(ParentValidationError::ParentCommitmentMismatch {
251 expected: expected_prev_commitment,
252 parent: parent.commitment(),
253 });
254 }
255
256 signatures
259 .verify_against(self.commitment(), parent.validator_keys())
260 .map_err(|err| match err {
261 SignatureVerificationError::SignatureCountMismatch { expected, actual } => {
262 ParentValidationError::SignatureCountMismatch { expected, actual }
263 },
264 SignatureVerificationError::InvalidSignatureAtPosition { position } => {
265 ParentValidationError::InvalidSignatureAtPosition { position }
266 },
267 })?;
268
269 Ok(())
270 }
271
272 #[allow(clippy::too_many_arguments)]
283 fn compute_sub_commitment(
284 version: u32,
285 prev_block_commitment: Word,
286 chain_commitment: Word,
287 account_root: Word,
288 nullifier_root: Word,
289 tx_commitment: Word,
290 tx_kernel_commitment: Word,
291 validator_keys: &ValidatorKeys,
292 fee_parameters: &FeeParameters,
293 timestamp: u32,
294 block_num: BlockNumber,
295 ) -> Word {
296 let mut elements: Vec<Felt> = Vec::with_capacity(40);
297 elements.extend_from_slice(prev_block_commitment.as_elements());
298 elements.extend_from_slice(chain_commitment.as_elements());
299 elements.extend_from_slice(account_root.as_elements());
300 elements.extend_from_slice(nullifier_root.as_elements());
301 elements.extend_from_slice(tx_commitment.as_elements());
302 elements.extend_from_slice(tx_kernel_commitment.as_elements());
303 elements.extend(validator_keys.commitment());
304 elements.extend([block_num.into(), Felt::from(version), Felt::from(timestamp), ZERO]);
305 elements.extend([
306 ZERO,
307 Felt::from(fee_parameters.verification_base_fee()),
308 fee_parameters.fee_faucet_id().suffix(),
309 fee_parameters.fee_faucet_id().prefix().as_felt(),
310 ]);
311 elements.extend([ZERO, ZERO, ZERO, ZERO]);
312 Hasher::hash_elements(&elements)
313 }
314
315 #[cfg(test)]
325 pub(crate) fn new_dummy(
326 block_num: u32,
327 prev_block_commitment: Word,
328 validator_keys: ValidatorKeys,
329 ) -> Self {
330 use crate::block::{BlockBody, FeeParameters};
331 use crate::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET;
332 use crate::transaction::OrderedTransactionHeaders;
333
334 let body = BlockBody::new_unchecked(
335 Vec::new(),
336 Vec::new(),
337 Vec::new(),
338 OrderedTransactionHeaders::new_unchecked(Vec::new()),
339 );
340 let note_root = body.compute_block_note_tree().root();
341 let tx_commitment = body.transactions().commitment();
342
343 let fee_parameters =
344 FeeParameters::new(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(), 500);
345 BlockHeader::new(
346 0,
347 prev_block_commitment,
348 BlockNumber::from(block_num),
349 Word::empty(),
350 Word::empty(),
351 Word::empty(),
352 note_root,
353 tx_commitment,
354 Word::empty(),
355 validator_keys,
356 fee_parameters,
357 0,
358 )
359 }
360}
361
362impl Serializable for BlockHeader {
366 fn write_into<W: ByteWriter>(&self, target: &mut W) {
367 let Self {
368 version,
369 prev_block_commitment,
370 block_num,
371 chain_commitment,
372 account_root,
373 nullifier_root,
374 note_root,
375 tx_commitment,
376 tx_kernel_commitment,
377 validator_keys,
378 fee_parameters,
379 timestamp,
380 sub_commitment: _,
382 commitment: _,
383 } = self;
384
385 version.write_into(target);
386 prev_block_commitment.write_into(target);
387 block_num.write_into(target);
388 chain_commitment.write_into(target);
389 account_root.write_into(target);
390 nullifier_root.write_into(target);
391 note_root.write_into(target);
392 tx_commitment.write_into(target);
393 tx_kernel_commitment.write_into(target);
394 validator_keys.write_into(target);
395 fee_parameters.write_into(target);
396 timestamp.write_into(target);
397 }
398}
399
400impl Deserializable for BlockHeader {
401 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
402 let version = source.read()?;
403 let prev_block_commitment = source.read()?;
404 let block_num = source.read()?;
405 let chain_commitment = source.read()?;
406 let account_root = source.read()?;
407 let nullifier_root = source.read()?;
408 let note_root = source.read()?;
409 let tx_commitment = source.read()?;
410 let tx_kernel_commitment = source.read()?;
411 let validator_keys = source.read()?;
412 let fee_parameters = source.read()?;
413 let timestamp = source.read()?;
414
415 Ok(Self::new(
416 version,
417 prev_block_commitment,
418 block_num,
419 chain_commitment,
420 account_root,
421 nullifier_root,
422 note_root,
423 tx_commitment,
424 tx_kernel_commitment,
425 validator_keys,
426 fee_parameters,
427 timestamp,
428 ))
429 }
430}
431
432#[derive(Debug, Clone, PartialEq, Eq)]
442pub struct FeeParameters {
443 fee_faucet_id: AccountId,
446
447 verification_base_fee: u32,
449}
450
451impl FeeParameters {
452 pub fn new(fee_faucet_id: AccountId, verification_base_fee: u32) -> Self {
457 Self { fee_faucet_id, verification_base_fee }
458 }
459
460 pub fn fee_faucet_id(&self) -> AccountId {
466 self.fee_faucet_id
467 }
468
469 pub fn verification_base_fee(&self) -> u32 {
471 self.verification_base_fee
472 }
473}
474
475impl Serializable for FeeParameters {
476 fn write_into<W: ByteWriter>(&self, target: &mut W) {
477 self.fee_faucet_id.write_into(target);
478 self.verification_base_fee.write_into(target);
479 }
480}
481
482impl Deserializable for FeeParameters {
483 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
484 let fee_faucet_id = source.read()?;
485 let verification_base_fee = source.read()?;
486
487 Ok(Self::new(fee_faucet_id, verification_base_fee))
488 }
489}
490
491#[derive(Debug)]
500pub(crate) enum ParentValidationError {
501 SignatureCountMismatch {
502 expected: usize,
503 actual: usize,
504 },
505 InvalidSignatureAtPosition {
506 position: usize,
507 },
508 ParentNumberMismatch {
509 expected: BlockNumber,
510 parent: BlockNumber,
511 },
512 ParentCommitmentMismatch {
513 expected: Word,
514 parent: Word,
515 },
516 GenesisBlockHasNoParent {
517 parent: BlockNumber,
518 },
519}
520
521#[cfg(test)]
525mod tests {
526 use miden_core::Word;
527 use miden_crypto::rand::test_utils::rand_value;
528
529 use super::*;
530 use crate::testing::validator_keys::{random_validator_set, sign_all};
531
532 #[test]
533 fn test_serde() {
534 let chain_commitment = rand_value::<Word>();
535 let note_root = rand_value::<Word>();
536 let tx_kernel_commitment = rand_value::<Word>();
537 let header = BlockHeader::mock(
538 0,
539 Some(chain_commitment),
540 Some(note_root),
541 &[],
542 tx_kernel_commitment,
543 );
544 let serialized = header.to_bytes();
545 let deserialized = BlockHeader::read_from_bytes(&serialized).unwrap();
546
547 assert_eq!(deserialized, header);
548 }
549
550 fn child_of(parent: &BlockHeader, child_num: u32, next_count: usize) -> BlockHeader {
553 let (_, next_keys) = random_validator_set(next_count);
554 BlockHeader::new_dummy(child_num, parent.commitment(), next_keys)
555 }
556
557 #[test]
558 fn validate_against_parent_accepts_all_signatures() {
559 let (signers, keys) = random_validator_set(5);
560 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
561 let child = child_of(&parent, 1, 5);
562 let signatures = sign_all(&keys, &signers, child.commitment());
563
564 child.validate_against_parent(&parent, &signatures).unwrap();
565 }
566
567 #[test]
568 fn validate_against_parent_accepts_single_validator() {
569 let (signers, keys) = random_validator_set(1);
570 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
571 let child = child_of(&parent, 1, 1);
572 let signatures = sign_all(&keys, &signers, child.commitment());
573
574 child.validate_against_parent(&parent, &signatures).unwrap();
575 }
576
577 #[test]
578 fn validate_against_parent_rejects_incomplete_signatures() {
579 let (signers, keys) = random_validator_set(3);
580 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
581 let child = child_of(&parent, 1, 3);
582 let signatures =
586 BlockSignatures::new(alloc::vec![signers[0].sign(child.commitment())]).unwrap();
587
588 let result = child.validate_against_parent(&parent, &signatures);
589 assert!(matches!(
590 result,
591 Err(ParentValidationError::SignatureCountMismatch { expected: 3, actual: 1 })
592 ));
593 }
594
595 #[test]
596 fn validate_against_parent_rejects_signature_count_mismatch() {
597 let (_, keys) = random_validator_set(3);
598 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
599 let child = child_of(&parent, 1, 3);
600
601 let (other_signers, other_keys) = random_validator_set(4);
604 let signatures = sign_all(&other_keys, &other_signers, child.commitment());
605 let bytes = signatures.to_bytes();
606 let deserialized = BlockSignatures::read_from_bytes(&bytes).unwrap();
607
608 let result = child.validate_against_parent(&parent, &deserialized);
609 assert!(matches!(
610 result,
611 Err(ParentValidationError::SignatureCountMismatch { expected: 3, actual: 4 })
612 ));
613 }
614
615 #[test]
616 fn validate_against_parent_rejects_uncommitted_signatures() {
617 let (_, keys) = random_validator_set(3);
618 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
619 let child = child_of(&parent, 1, 3);
620
621 let (impostor_signers, impostor_keys) = random_validator_set(3);
624 let signatures = sign_all(&impostor_keys, &impostor_signers, child.commitment());
625
626 let result = child.validate_against_parent(&parent, &signatures);
627 assert!(matches!(result, Err(ParentValidationError::InvalidSignatureAtPosition { .. })));
628 }
629
630 #[test]
631 fn validate_against_parent_rejects_genesis() {
632 let (signers, keys) = random_validator_set(3);
633 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
634 let child = BlockHeader::new_dummy(0, parent.commitment(), random_validator_set(3).1);
636 let signatures = sign_all(&keys, &signers, child.commitment());
637
638 let result = child.validate_against_parent(&parent, &signatures);
639 assert!(matches!(result, Err(ParentValidationError::GenesisBlockHasNoParent { .. })));
640 }
641
642 #[test]
643 fn validate_against_parent_rejects_wrong_parent_number() {
644 let (signers, keys) = random_validator_set(3);
645 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
646 let child = child_of(&parent, 2, 3);
648 let signatures = sign_all(&keys, &signers, child.commitment());
649
650 let result = child.validate_against_parent(&parent, &signatures);
651 assert!(matches!(result, Err(ParentValidationError::ParentNumberMismatch { .. })));
652 }
653
654 #[test]
655 fn validate_against_parent_rejects_wrong_parent_commitment() {
656 let (signers, keys) = random_validator_set(3);
657 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
658 let child = BlockHeader::new_dummy(1, Word::empty(), random_validator_set(3).1);
660 let signatures = sign_all(&keys, &signers, child.commitment());
661
662 let result = child.validate_against_parent(&parent, &signatures);
663 assert!(matches!(result, Err(ParentValidationError::ParentCommitmentMismatch { .. })));
664 }
665}