Skip to main content

miden_protocol/block/
header.rs

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// BLOCK HEADER
15// ================================================================================================
16
17/// The header of a block. It contains metadata about the block, commitments to the current state of
18/// the chain and the hash of the proof that attests to the integrity of the chain.
19///
20/// A block header includes the following fields:
21///
22/// - `version` specifies the version of the protocol.
23/// - `prev_block_commitment` is the hash of the previous block header.
24/// - `block_num` is a unique sequential number of the current block.
25/// - `chain_commitment` is a commitment to an MMR of the entire chain where each block is a leaf.
26/// - `account_root` is a commitment to account database.
27/// - `nullifier_root` is a commitment to the nullifier database.
28/// - `note_root` is a commitment to all notes created in the current block.
29/// - `tx_commitment` is a commitment to the set of transaction IDs which affected accounts in the
30///   block.
31/// - `tx_kernel_commitment` a commitment to all transaction kernels supported by this block.
32/// - `validator_keys` is the set of validator public keys authorized to sign the *next* block.
33/// - `fee_parameters` are the parameters defining the base fees and the fee faucet ID, see
34///   [`FeeParameters`] for more details.
35/// - `timestamp` is the time when the block was created, in seconds since UNIX epoch. Current
36///   representation is sufficient to represent time up to year 2106.
37/// - `sub_commitment` is a sequential hash of all fields except the note_root.
38/// - `commitment` is a 2-to-1 hash of the sub_commitment and the note_root.
39#[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    /// Creates a new block header.
59    #[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        // Compute block sub commitment.
75        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        // The sub commitment is merged with the note_root - hash(sub_commitment, note_root) to
90        // produce the final hash. This is done to make the note_root easily accessible
91        // without having to unhash the entire header. Having the note_root easily
92        // accessible is useful when authenticating notes.
93        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    // ACCESSORS
114    // --------------------------------------------------------------------------------------------
115
116    /// Returns the protocol version.
117    pub fn version(&self) -> u32 {
118        self.version
119    }
120
121    /// Returns the commitment of the block header.
122    pub fn commitment(&self) -> Word {
123        self.commitment
124    }
125
126    /// Returns the sub commitment of the block header.
127    ///
128    /// The sub commitment is a sequential hash of all block header fields except the note root.
129    /// This is used in the block commitment computation which is a 2-to-1 hash of the sub
130    /// commitment and the note root [hash(sub_commitment, note_root)]. This procedure is used to
131    /// make the note root easily accessible without having to unhash the entire header.
132    pub fn sub_commitment(&self) -> Word {
133        self.sub_commitment
134    }
135
136    /// Returns the commitment to the previous block header.
137    pub fn prev_block_commitment(&self) -> Word {
138        self.prev_block_commitment
139    }
140
141    /// Returns the block number.
142    pub fn block_num(&self) -> BlockNumber {
143        self.block_num
144    }
145
146    /// Returns the epoch to which this block belongs.
147    ///
148    /// This is the block number shifted right by [`BlockNumber::EPOCH_LENGTH_EXPONENT`].
149    pub fn block_epoch(&self) -> u16 {
150        self.block_num.block_epoch()
151    }
152
153    /// Returns the chain commitment.
154    pub fn chain_commitment(&self) -> Word {
155        self.chain_commitment
156    }
157
158    /// Returns the account database root.
159    pub fn account_root(&self) -> Word {
160        self.account_root
161    }
162
163    /// Returns the nullifier database root.
164    pub fn nullifier_root(&self) -> Word {
165        self.nullifier_root
166    }
167
168    /// Returns the note root.
169    pub fn note_root(&self) -> Word {
170        self.note_root
171    }
172
173    /// Returns the set of validator public keys authorized to sign the *next* block.
174    ///
175    /// A block's signatures are verified against the `validator_keys` committed to by its parent
176    /// block, not against this field. See the [`BlockHeader`] docs for details.
177    pub fn validator_keys(&self) -> &ValidatorKeys {
178        &self.validator_keys
179    }
180
181    /// Returns the commitment to all transactions in this block.
182    ///
183    /// The commitment is computed as sequential hash of (`transaction_id`, `account_id`) tuples.
184    /// This makes it possible for the verifier to link transaction IDs to the accounts which
185    /// they were executed against.
186    pub fn tx_commitment(&self) -> Word {
187        self.tx_commitment
188    }
189
190    /// Returns the transaction kernel commitment.
191    ///
192    /// The transaction kernel commitment is computed as a sequential hash of all transaction kernel
193    /// hashes.
194    pub fn tx_kernel_commitment(&self) -> Word {
195        self.tx_kernel_commitment
196    }
197
198    /// Returns a reference to the [`FeeParameters`] in this header.
199    pub fn fee_parameters(&self) -> &FeeParameters {
200        &self.fee_parameters
201    }
202
203    /// Returns the timestamp at which the block was created, in seconds since UNIX epoch.
204    pub fn timestamp(&self) -> u32 {
205        self.timestamp
206    }
207
208    /// Returns the block number of the epoch block to which this block belongs.
209    pub fn epoch_block_num(&self) -> BlockNumber {
210        BlockNumber::from_epoch(self.block_epoch())
211    }
212
213    // VALIDATION
214    // --------------------------------------------------------------------------------------------
215
216    /// Validates that `parent` precedes and authorizes this block.
217    ///
218    /// The `signatures` are positional with respect to the validator set committed to by `parent`
219    /// (see [`ValidatorKeys`]): the signature at index `i` must verify against the parent's
220    /// validator key at index `i`. Every validator in the parent's set must have signed.
221    ///
222    /// # Errors
223    ///
224    /// Returns an error if the block is the genesis block (no parent), the parent's number or
225    /// commitment do not match, the number of signatures does not match the parent's validator
226    /// count, or a signature does not verify against its validator key.
227    pub(crate) fn validate_against_parent(
228        &self,
229        parent: &BlockHeader,
230        signatures: &BlockSignatures,
231    ) -> Result<(), ParentValidationError> {
232        // Block 0 does not have a parent.
233        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        // Check block numbers.
240        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        // Check commitments.
248        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        // Verify the signatures positionally against the parent's validator set using the shared,
257        // canonical verifier, which also enforces that every validator signed.
258        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    // HELPERS
273    // --------------------------------------------------------------------------------------------
274
275    /// Computes the sub commitment of the block header.
276    ///
277    /// The sub commitment is computed as a sequential hash of the following fields:
278    /// `prev_block_commitment`, `chain_commitment`, `account_root`, `nullifier_root`, `note_root`,
279    /// `tx_commitment`, `tx_kernel_commitment`, `validator_keys_commitment`, `version`,
280    /// `timestamp`, `block_num`, `fee_faucet_id`, `verification_base_fee` (all fields except
281    /// the `note_root`).
282    #[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    // TEST HELPERS
316    // --------------------------------------------------------------------------------------------
317
318    /// Builds a minimal block header with a controllable block number, previous-block commitment,
319    /// and validator key set.
320    ///
321    /// The remaining roots are zeroed except the note root and transaction commitment, which match
322    /// the empty [`BlockBody`](super::BlockBody) the block tests pair this header with, so the
323    /// self-consistency checks in `SignedBlock::validate` and  `ProvenBlock::validate` pass.
324    #[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
362// SERIALIZATION
363// ================================================================================================
364
365impl 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            // Don't serialize sub commitment and commitment as they can be derived.
381            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// FEE PARAMETERS
433// ================================================================================================
434
435/// The fee-related parameters of a block.
436///
437/// This defines how to compute the fees of a transaction and which asset fees can be paid in.
438///
439/// The fee asset is assumed to be a fungible asset
440/// ([`AssetComposition::Fungible`](crate::asset::AssetComposition::Fungible)).
441#[derive(Debug, Clone, PartialEq, Eq)]
442pub struct FeeParameters {
443    /// The [`AccountId`] of the faucet whose assets are accepted for fee payments in the
444    /// transaction kernel, or in other words, the fee faucet of the blockchain.
445    fee_faucet_id: AccountId,
446
447    /// The base fee (in base units) capturing the cost for the verification of a transaction.
448    verification_base_fee: u32,
449}
450
451impl FeeParameters {
452    // CONSTRUCTORS
453    // --------------------------------------------------------------------------------------------
454
455    /// Creates [`FeeParameters`] from the provided inputs.
456    pub fn new(fee_faucet_id: AccountId, verification_base_fee: u32) -> Self {
457        Self { fee_faucet_id, verification_base_fee }
458    }
459
460    // PUBLIC ACCESSORS
461    // --------------------------------------------------------------------------------------------
462
463    /// Returns the [`AccountId`] of the faucet whose assets are accepted for fee payments in the
464    /// transaction kernel, or in other words, the fee faucet of the blockchain.
465    pub fn fee_faucet_id(&self) -> AccountId {
466        self.fee_faucet_id
467    }
468
469    /// Returns the base fee capturing the cost for the verification of a transaction.
470    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// PARENT VALIDATION ERROR
492// ================================================================================================
493
494/// Error returned when a block fails validation against its parent block.
495///
496/// This is the shared, block-type-agnostic error produced by
497/// [`BlockHeader::validate_against_parent`]. Each block type maps it into its own error enum via
498/// `From`, which preserves that type's specific error messages.
499#[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// TESTS
522// ================================================================================================
523
524#[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    /// Builds a child of `parent` committing a fresh validator set of `next_count` validators as
551    /// the signer of the *next* block.
552    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        // Only one of three validators signs, so the resulting set is too short to align
583        // positionally with the parent's validator keys. `BlockSignatures::new` does not check
584        // this -- only `verify_against` (called by `validate_against_parent`) does.
585        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        // A block signed by a validator set of a different size cannot align positionally with the
602        // parent's committed set. Deserialization does not check this, so build it directly.
603        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        // The child is signed by a full, valid validator set of the same size the parent never
622        // committed, so the signatures do not verify against the parent's validator keys.
623        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        // Block 0 has no parent to anchor against.
635        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        // Child claims to be block 2, but the parent is block 0.
647        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        // Child does not link to the parent's commitment.
659        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}