1use alloc::vec::Vec;
2
3use crate::block::{BlockNumber, BlockSignatures, SignatureVerificationError, ValidatorConfig};
4use crate::protocol_config::NextProtocolConfig;
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)]
46pub struct BlockHeader {
47 version: u8,
48 timestamp: u32,
49 block_num: BlockNumber,
50 prev_block_commitment: Word,
51 chain_commitment: Word,
52 account_root: Word,
53 nullifier_root: Word,
54 note_root: Word,
55 tx_commitment: Word,
56 validator_config: ValidatorConfig,
57 fee_parameters: FeeParameters,
58 protocol_config_commitment: Word,
59 next_protocol_config: Option<NextProtocolConfig>,
60 sub_commitment: Word,
61 commitment: Word,
62}
63
64impl BlockHeader {
65 const VERSION_1: u8 = 1;
72
73 const NUM_SUB_COMMITMENT_ELEMENTS: usize = 40;
75
76 #[allow(clippy::too_many_arguments)]
81 pub fn new(
82 prev_block_commitment: Word,
83 block_num: BlockNumber,
84 chain_commitment: Word,
85 account_root: Word,
86 nullifier_root: Word,
87 note_root: Word,
88 tx_commitment: Word,
89 validator_config: ValidatorConfig,
90 fee_parameters: FeeParameters,
91 protocol_config_commitment: Word,
92 next_protocol_config: Option<NextProtocolConfig>,
93 timestamp: u32,
94 ) -> Self {
95 let version = Self::VERSION_1;
96
97 let sub_elements = Self::to_sub_elements(
98 version,
99 prev_block_commitment,
100 block_num,
101 chain_commitment,
102 account_root,
103 nullifier_root,
104 tx_commitment,
105 &validator_config,
106 &fee_parameters,
107 protocol_config_commitment,
108 next_protocol_config.as_ref(),
109 timestamp,
110 );
111 let sub_commitment = Hasher::hash_elements(&sub_elements);
112
113 let commitment = Hasher::merge(&[sub_commitment, note_root]);
118
119 Self {
120 version,
121 timestamp,
122 block_num,
123 prev_block_commitment,
124 chain_commitment,
125 account_root,
126 nullifier_root,
127 note_root,
128 tx_commitment,
129 validator_config,
130 fee_parameters,
131 protocol_config_commitment,
132 next_protocol_config,
133 sub_commitment,
134 commitment,
135 }
136 }
137
138 pub fn version(&self) -> u8 {
143 self.version
144 }
145
146 pub fn commitment(&self) -> Word {
148 self.commitment
149 }
150
151 pub fn sub_commitment(&self) -> Word {
158 self.sub_commitment
159 }
160
161 pub fn prev_block_commitment(&self) -> Word {
163 self.prev_block_commitment
164 }
165
166 pub fn block_num(&self) -> BlockNumber {
168 self.block_num
169 }
170
171 pub fn block_epoch(&self) -> u16 {
175 self.block_num.block_epoch()
176 }
177
178 pub fn chain_commitment(&self) -> Word {
180 self.chain_commitment
181 }
182
183 pub fn account_root(&self) -> Word {
185 self.account_root
186 }
187
188 pub fn nullifier_root(&self) -> Word {
190 self.nullifier_root
191 }
192
193 pub fn note_root(&self) -> Word {
195 self.note_root
196 }
197
198 pub fn validator_config(&self) -> &ValidatorConfig {
203 &self.validator_config
204 }
205
206 pub fn tx_commitment(&self) -> Word {
212 self.tx_commitment
213 }
214
215 pub fn fee_parameters(&self) -> &FeeParameters {
217 &self.fee_parameters
218 }
219
220 pub fn protocol_config_commitment(&self) -> Word {
223 self.protocol_config_commitment
224 }
225
226 pub fn next_protocol_config(&self) -> Option<&NextProtocolConfig> {
228 self.next_protocol_config.as_ref()
229 }
230
231 pub fn next_protocol_config_commitment(&self) -> Word {
234 Self::compute_next_protocol_config_commitment(self.next_protocol_config.as_ref())
235 }
236
237 pub fn timestamp(&self) -> u32 {
239 self.timestamp
240 }
241
242 pub fn epoch_block_num(&self) -> BlockNumber {
244 BlockNumber::from_epoch(self.block_epoch())
245 }
246
247 pub fn to_elements(&self) -> Vec<Felt> {
279 let mut elements = Self::to_sub_elements(
280 self.version,
281 self.prev_block_commitment,
282 self.block_num,
283 self.chain_commitment,
284 self.account_root,
285 self.nullifier_root,
286 self.tx_commitment,
287 &self.validator_config,
288 &self.fee_parameters,
289 self.protocol_config_commitment,
290 self.next_protocol_config.as_ref(),
291 self.timestamp,
292 );
293 elements.extend_from_slice(self.note_root.as_elements());
294 elements
295 }
296
297 pub(crate) fn validate_against_parent(
312 &self,
313 parent: &BlockHeader,
314 signatures: &BlockSignatures,
315 ) -> Result<(), ParentValidationError> {
316 let Some(expected_parent_num) = self.block_num().checked_sub(1) else {
318 return Err(ParentValidationError::GenesisBlockHasNoParent {
319 parent: parent.block_num(),
320 });
321 };
322
323 if expected_parent_num != parent.block_num() {
325 return Err(ParentValidationError::ParentNumberMismatch {
326 expected: expected_parent_num,
327 parent: parent.block_num(),
328 });
329 }
330
331 let expected_prev_commitment = self.prev_block_commitment();
333 if expected_prev_commitment != parent.commitment() {
334 return Err(ParentValidationError::ParentCommitmentMismatch {
335 expected: expected_prev_commitment,
336 parent: parent.commitment(),
337 });
338 }
339
340 signatures
343 .verify_against(self.commitment(), parent.validator_config())
344 .map_err(|err| match err {
345 SignatureVerificationError::SignatureCountMismatch { expected, actual } => {
346 ParentValidationError::SignatureCountMismatch { expected, actual }
347 },
348 SignatureVerificationError::InvalidSignatureAtPosition { position } => {
349 ParentValidationError::InvalidSignatureAtPosition { position }
350 },
351 })?;
352
353 Ok(())
354 }
355
356 #[allow(clippy::too_many_arguments)]
364 fn to_sub_elements(
365 version: u8,
366 prev_block_commitment: Word,
367 block_num: BlockNumber,
368 chain_commitment: Word,
369 account_root: Word,
370 nullifier_root: Word,
371 tx_commitment: Word,
372 validator_config: &ValidatorConfig,
373 fee_parameters: &FeeParameters,
374 protocol_config_commitment: Word,
375 next_protocol_config: Option<&NextProtocolConfig>,
376 timestamp: u32,
377 ) -> Vec<Felt> {
378 let mut elements: Vec<Felt> = Vec::with_capacity(Self::NUM_SUB_COMMITMENT_ELEMENTS);
379 elements.extend([Felt::from(version), block_num.into(), Felt::from(timestamp), ZERO]);
380 elements.extend_from_slice(prev_block_commitment.as_elements());
381 elements.extend_from_slice(chain_commitment.as_elements());
382 elements.extend_from_slice(account_root.as_elements());
383 elements.extend_from_slice(nullifier_root.as_elements());
384 elements.extend_from_slice(tx_commitment.as_elements());
385 elements.extend_from_slice(protocol_config_commitment.as_elements());
386 elements.extend(validator_config.to_commitment());
387 elements.extend(Self::compute_next_protocol_config_commitment(next_protocol_config));
388 elements.extend([Felt::from(fee_parameters.verification_base_fee()), ZERO, ZERO, ZERO]);
389 elements
390 }
391
392 fn compute_next_protocol_config_commitment(
395 next_protocol_config: Option<&NextProtocolConfig>,
396 ) -> Word {
397 next_protocol_config.map_or(Word::empty(), NextProtocolConfig::to_commitment)
398 }
399
400 #[cfg(test)]
410 pub(crate) fn new_dummy(
411 block_num: u32,
412 prev_block_commitment: Word,
413 validator_config: ValidatorConfig,
414 ) -> Self {
415 use crate::block::{BlockBody, FeeParameters};
416 use crate::transaction::OrderedTransactionHeaders;
417
418 let body = BlockBody::new_unchecked(
419 Vec::new(),
420 Vec::new(),
421 Vec::new(),
422 OrderedTransactionHeaders::new_unchecked(Vec::new()),
423 );
424 let note_root = body.compute_block_note_tree().root();
425 let tx_commitment = body.transactions().commitment();
426
427 BlockHeader::new(
428 prev_block_commitment,
429 BlockNumber::from(block_num),
430 Word::empty(),
431 Word::empty(),
432 Word::empty(),
433 note_root,
434 tx_commitment,
435 validator_config,
436 FeeParameters::new(500),
437 Word::empty(),
438 None,
439 0,
440 )
441 }
442}
443
444impl Serializable for BlockHeader {
448 fn write_into<W: ByteWriter>(&self, target: &mut W) {
449 let Self {
450 version,
451 prev_block_commitment,
452 block_num,
453 chain_commitment,
454 account_root,
455 nullifier_root,
456 note_root,
457 tx_commitment,
458 validator_config,
459 fee_parameters,
460 protocol_config_commitment,
461 next_protocol_config,
462 timestamp,
463 sub_commitment: _,
465 commitment: _,
466 } = self;
467
468 version.write_into(target);
469 prev_block_commitment.write_into(target);
470 block_num.write_into(target);
471 chain_commitment.write_into(target);
472 account_root.write_into(target);
473 nullifier_root.write_into(target);
474 note_root.write_into(target);
475 tx_commitment.write_into(target);
476 validator_config.write_into(target);
477 fee_parameters.write_into(target);
478 protocol_config_commitment.write_into(target);
479 next_protocol_config.write_into(target);
480 timestamp.write_into(target);
481 }
482}
483
484impl Deserializable for BlockHeader {
485 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
486 let version = u8::read_from(source)?;
487
488 if version != Self::VERSION_1 {
489 return Err(DeserializationError::InvalidValue(format!(
490 "block version is {} but only version {} is supported",
491 version,
492 Self::VERSION_1,
493 )));
494 }
495
496 let prev_block_commitment = source.read()?;
497 let block_num = source.read()?;
498 let chain_commitment = source.read()?;
499 let account_root = source.read()?;
500 let nullifier_root = source.read()?;
501 let note_root = source.read()?;
502 let tx_commitment = source.read()?;
503 let validator_config = source.read()?;
504 let fee_parameters = source.read()?;
505 let protocol_config_commitment = source.read()?;
506 let next_protocol_config = <Option<NextProtocolConfig>>::read_from(source)?;
507 let timestamp = source.read()?;
508
509 Ok(Self::new(
510 prev_block_commitment,
511 block_num,
512 chain_commitment,
513 account_root,
514 nullifier_root,
515 note_root,
516 tx_commitment,
517 validator_config,
518 fee_parameters,
519 protocol_config_commitment,
520 next_protocol_config,
521 timestamp,
522 ))
523 }
524}
525
526#[derive(Debug, Clone, PartialEq, Eq)]
534pub struct FeeParameters {
535 verification_base_fee: u32,
537}
538
539impl FeeParameters {
540 pub fn new(verification_base_fee: u32) -> Self {
545 Self { verification_base_fee }
546 }
547
548 pub fn verification_base_fee(&self) -> u32 {
553 self.verification_base_fee
554 }
555}
556
557impl Serializable for FeeParameters {
558 fn write_into<W: ByteWriter>(&self, target: &mut W) {
559 let Self { verification_base_fee } = self;
560
561 verification_base_fee.write_into(target);
562 }
563}
564
565impl Deserializable for FeeParameters {
566 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
567 let verification_base_fee = source.read()?;
568
569 Ok(Self::new(verification_base_fee))
570 }
571}
572
573#[derive(Debug)]
582pub(crate) enum ParentValidationError {
583 SignatureCountMismatch {
584 expected: usize,
585 actual: usize,
586 },
587 InvalidSignatureAtPosition {
588 position: usize,
589 },
590 ParentNumberMismatch {
591 expected: BlockNumber,
592 parent: BlockNumber,
593 },
594 ParentCommitmentMismatch {
595 expected: Word,
596 parent: Word,
597 },
598 GenesisBlockHasNoParent {
599 parent: BlockNumber,
600 },
601}
602
603#[cfg(test)]
607mod tests {
608 use assert_matches::assert_matches;
609 use miden_core::Word;
610 use miden_crypto::rand::test_utils::rand_value;
611
612 use super::*;
613
614 #[test]
615 fn block_header_deserialization_rejects_unsupported_version() {
616 let error = BlockHeader::read_from_bytes(&[0]).unwrap_err();
617
618 assert_matches!(error, DeserializationError::InvalidValue(message) => {
619 assert!(message.contains("block version is 0"));
620 });
621 }
622
623 #[test]
624 fn test_serde() {
625 let chain_commitment = rand_value::<Word>();
626 let note_root = rand_value::<Word>();
627 let header = BlockHeader::mock(0, Some(chain_commitment), Some(note_root), &[]);
628 let serialized = header.to_bytes();
629 let deserialized = BlockHeader::read_from_bytes(&serialized).unwrap();
630
631 assert_eq!(deserialized, header);
632 }
633
634 fn with_scheduled_upgrade(header: &BlockHeader) -> BlockHeader {
636 let next_protocol_config =
637 NextProtocolConfig::new(BlockNumber::from(42u32), rand_value::<Word>()).unwrap();
638
639 BlockHeader::new(
640 header.prev_block_commitment(),
641 header.block_num(),
642 header.chain_commitment(),
643 header.account_root(),
644 header.nullifier_root(),
645 header.note_root(),
646 header.tx_commitment(),
647 header.validator_config().clone(),
648 header.fee_parameters().clone(),
649 header.protocol_config_commitment(),
650 Some(next_protocol_config),
651 header.timestamp(),
652 )
653 }
654
655 #[test]
656 fn scheduled_upgrade_round_trips_and_changes_the_commitment() {
657 let without_upgrade = BlockHeader::mock(0, None, None, &[]);
658 let with_upgrade = with_scheduled_upgrade(&without_upgrade);
659
660 assert_eq!(without_upgrade.next_protocol_config_commitment(), Word::empty());
661 assert_ne!(with_upgrade.commitment(), without_upgrade.commitment());
662
663 let deserialized = BlockHeader::read_from_bytes(&with_upgrade.to_bytes()).unwrap();
664 assert_eq!(deserialized, with_upgrade);
665 }
666
667 #[test]
670 fn to_elements_is_the_sub_commitment_preimage_plus_the_note_root() {
671 let header = BlockHeader::mock(0, None, None, &[]);
672 let elements = header.to_elements();
673
674 let (sub_elements, note_root) = elements.split_at(BlockHeader::NUM_SUB_COMMITMENT_ELEMENTS);
675 assert_eq!(Hasher::hash_elements(sub_elements), header.sub_commitment());
676 assert_eq!(note_root, header.note_root().as_elements());
677 }
678
679 fn child_of(parent: &BlockHeader, child_num: u32, next_count: usize) -> BlockHeader {
682 let (_, next_keys) = ValidatorConfig::random_with_signers(next_count);
683 BlockHeader::new_dummy(child_num, parent.commitment(), next_keys)
684 }
685
686 #[test]
687 fn validate_against_parent_accepts_all_signatures() {
688 let (signers, keys) = ValidatorConfig::random_with_signers(5);
689 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
690 let child = child_of(&parent, 1, 5);
691 let signatures = keys.sign_all(&signers, child.commitment());
692
693 child.validate_against_parent(&parent, &signatures).unwrap();
694 }
695
696 #[test]
697 fn validate_against_parent_accepts_single_validator() {
698 let (signers, keys) = ValidatorConfig::random_with_signers(1);
699 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
700 let child = child_of(&parent, 1, 1);
701 let signatures = keys.sign_all(&signers, child.commitment());
702
703 child.validate_against_parent(&parent, &signatures).unwrap();
704 }
705
706 #[test]
707 fn validate_against_parent_rejects_incomplete_signatures() {
708 let (signers, keys) = ValidatorConfig::random_with_signers(3);
709 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
710 let child = child_of(&parent, 1, 3);
711 let signatures =
715 BlockSignatures::new(alloc::vec![signers[0].sign(child.commitment())]).unwrap();
716
717 let result = child.validate_against_parent(&parent, &signatures);
718 assert!(matches!(
719 result,
720 Err(ParentValidationError::SignatureCountMismatch { expected: 3, actual: 1 })
721 ));
722 }
723
724 #[test]
725 fn validate_against_parent_rejects_signature_count_mismatch() {
726 let (_, keys) = ValidatorConfig::random_with_signers(3);
727 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
728 let child = child_of(&parent, 1, 3);
729
730 let (other_signers, other_keys) = ValidatorConfig::random_with_signers(4);
733 let signatures = other_keys.sign_all(&other_signers, child.commitment());
734 let bytes = signatures.to_bytes();
735 let deserialized = BlockSignatures::read_from_bytes(&bytes).unwrap();
736
737 let result = child.validate_against_parent(&parent, &deserialized);
738 assert!(matches!(
739 result,
740 Err(ParentValidationError::SignatureCountMismatch { expected: 3, actual: 4 })
741 ));
742 }
743
744 #[test]
745 fn validate_against_parent_rejects_uncommitted_signatures() {
746 let (_, keys) = ValidatorConfig::random_with_signers(3);
747 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
748 let child = child_of(&parent, 1, 3);
749
750 let (impostor_signers, impostor_keys) = ValidatorConfig::random_with_signers(3);
753 let signatures = impostor_keys.sign_all(&impostor_signers, child.commitment());
754
755 let result = child.validate_against_parent(&parent, &signatures);
756 assert!(matches!(result, Err(ParentValidationError::InvalidSignatureAtPosition { .. })));
757 }
758
759 #[test]
760 fn validate_against_parent_rejects_genesis() {
761 let (signers, keys) = ValidatorConfig::random_with_signers(3);
762 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
763 let child = BlockHeader::new_dummy(
765 0,
766 parent.commitment(),
767 ValidatorConfig::random_with_signers(3).1,
768 );
769 let signatures = keys.sign_all(&signers, child.commitment());
770
771 let result = child.validate_against_parent(&parent, &signatures);
772 assert!(matches!(result, Err(ParentValidationError::GenesisBlockHasNoParent { .. })));
773 }
774
775 #[test]
776 fn validate_against_parent_rejects_wrong_parent_number() {
777 let (signers, keys) = ValidatorConfig::random_with_signers(3);
778 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
779 let child = child_of(&parent, 2, 3);
781 let signatures = keys.sign_all(&signers, child.commitment());
782
783 let result = child.validate_against_parent(&parent, &signatures);
784 assert!(matches!(result, Err(ParentValidationError::ParentNumberMismatch { .. })));
785 }
786
787 #[test]
788 fn validate_against_parent_rejects_wrong_parent_commitment() {
789 let (signers, keys) = ValidatorConfig::random_with_signers(3);
790 let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
791 let child =
793 BlockHeader::new_dummy(1, Word::empty(), ValidatorConfig::random_with_signers(3).1);
794 let signatures = keys.sign_all(&signers, child.commitment());
795
796 let result = child.validate_against_parent(&parent, &signatures);
797 assert!(matches!(result, Err(ParentValidationError::ParentCommitmentMismatch { .. })));
798 }
799}