miden_objects/block/
header.rs

1use alloc::string::ToString;
2use alloc::vec::Vec;
3
4use crate::account::{AccountId, AccountType};
5use crate::block::BlockNumber;
6use crate::utils::serde::{
7    ByteReader,
8    ByteWriter,
9    Deserializable,
10    DeserializationError,
11    Serializable,
12};
13use crate::{FeeError, Felt, Hasher, Word, ZERO};
14
15/// The header of a block. It contains metadata about the block, commitments to the current
16/// state of the chain and the hash of the proof that attests to the integrity of the chain.
17///
18/// A block header includes the following fields:
19///
20/// - `version` specifies the version of the protocol.
21/// - `prev_block_commitment` is the hash of the previous block header.
22/// - `block_num` is a unique sequential number of the current block.
23/// - `chain_commitment` is a commitment to an MMR of the entire chain where each block is a leaf.
24/// - `account_root` is a commitment to account database.
25/// - `nullifier_root` is a commitment to the nullifier database.
26/// - `note_root` is a commitment to all notes created in the current block.
27/// - `tx_commitment` is a commitment to the set of transaction IDs which affected accounts in the
28///   block.
29/// - `tx_kernel_commitment` a commitment to all transaction kernels supported by this block.
30/// - `proof_commitment` is the commitment of the block's STARK proof attesting to the correct state
31///   transition.
32/// - `fee_parameters` are the parameters defining the base fees and the native asset, see
33///   [`FeeParameters`] for more details.
34/// - `timestamp` is the time when the block was created, in seconds since UNIX epoch. Current
35///   representation is sufficient to represent time up to year 2106.
36/// - `sub_commitment` is a sequential hash of all fields except the note_root.
37/// - `commitment` is a 2-to-1 hash of the sub_commitment and the note_root.
38#[derive(Debug, Eq, PartialEq, Clone)]
39pub struct BlockHeader {
40    version: u32,
41    prev_block_commitment: Word,
42    block_num: BlockNumber,
43    chain_commitment: Word,
44    account_root: Word,
45    nullifier_root: Word,
46    note_root: Word,
47    tx_commitment: Word,
48    tx_kernel_commitment: Word,
49    proof_commitment: Word,
50    fee_parameters: FeeParameters,
51    timestamp: u32,
52    sub_commitment: Word,
53    commitment: Word,
54}
55
56impl BlockHeader {
57    /// Creates a new block header.
58    #[allow(clippy::too_many_arguments)]
59    pub fn new(
60        version: u32,
61        prev_block_commitment: Word,
62        block_num: BlockNumber,
63        chain_commitment: Word,
64        account_root: Word,
65        nullifier_root: Word,
66        note_root: Word,
67        tx_commitment: Word,
68        tx_kernel_commitment: Word,
69        proof_commitment: Word,
70        fee_parameters: FeeParameters,
71        timestamp: u32,
72    ) -> Self {
73        // compute block sub commitment
74        let sub_commitment = Self::compute_sub_commitment(
75            version,
76            prev_block_commitment,
77            chain_commitment,
78            account_root,
79            nullifier_root,
80            tx_commitment,
81            tx_kernel_commitment,
82            proof_commitment,
83            &fee_parameters,
84            timestamp,
85            block_num,
86        );
87
88        // The sub commitment is merged with the note_root - hash(sub_commitment, note_root) to
89        // produce the final hash. This is done to make the note_root easily accessible
90        // without having to unhash the entire header. Having the note_root easily
91        // accessible is useful when authenticating notes.
92        let commitment = Hasher::merge(&[sub_commitment, note_root]);
93
94        Self {
95            version,
96            prev_block_commitment,
97            block_num,
98            chain_commitment,
99            account_root,
100            nullifier_root,
101            note_root,
102            tx_commitment,
103            tx_kernel_commitment,
104            proof_commitment,
105            fee_parameters,
106            timestamp,
107            sub_commitment,
108            commitment,
109        }
110    }
111
112    // ACCESSORS
113    // --------------------------------------------------------------------------------------------
114
115    /// Returns the protocol version.
116    pub fn version(&self) -> u32 {
117        self.version
118    }
119
120    /// Returns the commitment of the block header.
121    pub fn commitment(&self) -> Word {
122        self.commitment
123    }
124
125    /// Returns the sub commitment of the block header.
126    ///
127    /// The sub commitment is a sequential hash of all block header fields except the note root.
128    /// This is used in the block commitment computation which is a 2-to-1 hash of the sub
129    /// commitment and the note root [hash(sub_commitment, note_root)]. This procedure is used to
130    /// make the note root easily accessible without having to unhash the entire header.
131    pub fn sub_commitment(&self) -> Word {
132        self.sub_commitment
133    }
134
135    /// Returns the commitment to the previous block header.
136    pub fn prev_block_commitment(&self) -> Word {
137        self.prev_block_commitment
138    }
139
140    /// Returns the block number.
141    pub fn block_num(&self) -> BlockNumber {
142        self.block_num
143    }
144
145    /// Returns the epoch to which this block belongs.
146    ///
147    /// This is the block number shifted right by [`BlockNumber::EPOCH_LENGTH_EXPONENT`].
148    pub fn block_epoch(&self) -> u16 {
149        self.block_num.block_epoch()
150    }
151
152    /// Returns the chain commitment.
153    pub fn chain_commitment(&self) -> Word {
154        self.chain_commitment
155    }
156
157    /// Returns the account database root.
158    pub fn account_root(&self) -> Word {
159        self.account_root
160    }
161
162    /// Returns the nullifier database root.
163    pub fn nullifier_root(&self) -> Word {
164        self.nullifier_root
165    }
166
167    /// Returns the note root.
168    pub fn note_root(&self) -> Word {
169        self.note_root
170    }
171
172    /// Returns the commitment to all transactions in this block.
173    ///
174    /// The commitment is computed as sequential hash of (`transaction_id`, `account_id`) tuples.
175    /// This makes it possible for the verifier to link transaction IDs to the accounts which
176    /// they were executed against.
177    pub fn tx_commitment(&self) -> Word {
178        self.tx_commitment
179    }
180
181    /// Returns the transaction kernel commitment.
182    ///
183    /// The transaction kernel commitment is computed as a sequential hash of all transaction kernel
184    /// hashes.
185    pub fn tx_kernel_commitment(&self) -> Word {
186        self.tx_kernel_commitment
187    }
188
189    /// Returns the proof commitment.
190    pub fn proof_commitment(&self) -> Word {
191        self.proof_commitment
192    }
193
194    /// Returns a reference to the [`FeeParameters`] in this header.
195    pub fn fee_parameters(&self) -> &FeeParameters {
196        &self.fee_parameters
197    }
198
199    /// Returns the timestamp at which the block was created, in seconds since UNIX epoch.
200    pub fn timestamp(&self) -> u32 {
201        self.timestamp
202    }
203
204    /// Returns the block number of the epoch block to which this block belongs.
205    pub fn epoch_block_num(&self) -> BlockNumber {
206        BlockNumber::from_epoch(self.block_epoch())
207    }
208
209    // HELPERS
210    // --------------------------------------------------------------------------------------------
211
212    /// Computes the sub commitment of the block header.
213    ///
214    /// The sub commitment is computed as a sequential hash of the following fields:
215    /// `prev_block_commitment`, `chain_commitment`, `account_root`, `nullifier_root`, `note_root`,
216    /// `tx_commitment`, `tx_kernel_commitment`, `proof_commitment`, `version`, `timestamp`,
217    /// `block_num`, `native_asset_id`, `verification_base_fee` (all fields except the `note_root`).
218    #[allow(clippy::too_many_arguments)]
219    fn compute_sub_commitment(
220        version: u32,
221        prev_block_commitment: Word,
222        chain_commitment: Word,
223        account_root: Word,
224        nullifier_root: Word,
225        tx_commitment: Word,
226        tx_kernel_commitment: Word,
227        proof_commitment: Word,
228        fee_parameters: &FeeParameters,
229        timestamp: u32,
230        block_num: BlockNumber,
231    ) -> Word {
232        let mut elements: Vec<Felt> = Vec::with_capacity(40);
233        elements.extend_from_slice(prev_block_commitment.as_elements());
234        elements.extend_from_slice(chain_commitment.as_elements());
235        elements.extend_from_slice(account_root.as_elements());
236        elements.extend_from_slice(nullifier_root.as_elements());
237        elements.extend_from_slice(tx_commitment.as_elements());
238        elements.extend_from_slice(tx_kernel_commitment.as_elements());
239        elements.extend_from_slice(proof_commitment.as_elements());
240        elements.extend([block_num.into(), version.into(), timestamp.into(), ZERO]);
241        elements.extend([
242            fee_parameters.native_asset_id().suffix(),
243            fee_parameters.native_asset_id().prefix().as_felt(),
244            fee_parameters.verification_base_fee().into(),
245            ZERO,
246        ]);
247        elements.extend([ZERO, ZERO, ZERO, ZERO]);
248        Hasher::hash_elements(&elements)
249    }
250}
251
252// SERIALIZATION
253// ================================================================================================
254
255impl Serializable for BlockHeader {
256    fn write_into<W: ByteWriter>(&self, target: &mut W) {
257        self.version.write_into(target);
258        self.prev_block_commitment.write_into(target);
259        self.block_num.write_into(target);
260        self.chain_commitment.write_into(target);
261        self.account_root.write_into(target);
262        self.nullifier_root.write_into(target);
263        self.note_root.write_into(target);
264        self.tx_commitment.write_into(target);
265        self.tx_kernel_commitment.write_into(target);
266        self.proof_commitment.write_into(target);
267        self.fee_parameters.write_into(target);
268        self.timestamp.write_into(target);
269    }
270}
271
272impl Deserializable for BlockHeader {
273    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
274        let version = source.read()?;
275        let prev_block_commitment = source.read()?;
276        let block_num = source.read()?;
277        let chain_commitment = source.read()?;
278        let account_root = source.read()?;
279        let nullifier_root = source.read()?;
280        let note_root = source.read()?;
281        let tx_commitment = source.read()?;
282        let tx_kernel_commitment = source.read()?;
283        let proof_commitment = source.read()?;
284        let fee_parameters = source.read()?;
285        let timestamp = source.read()?;
286
287        Ok(Self::new(
288            version,
289            prev_block_commitment,
290            block_num,
291            chain_commitment,
292            account_root,
293            nullifier_root,
294            note_root,
295            tx_commitment,
296            tx_kernel_commitment,
297            proof_commitment,
298            fee_parameters,
299            timestamp,
300        ))
301    }
302}
303
304// FEE PARAMETERS
305// ================================================================================================
306
307/// The fee-related parameters of a block.
308///
309/// This defines how to compute the fees of a transaction and which asset fees can be paid in.
310#[derive(Debug, Clone, PartialEq, Eq)]
311pub struct FeeParameters {
312    /// The [`AccountId`] of the fungible faucet whose assets are accepted for fee payments in the
313    /// transaction kernel, or in other words, the native asset of the blockchain.
314    native_asset_id: AccountId,
315    /// The base fee (in base units) capturing the cost for the verification of a transaction.
316    verification_base_fee: u32,
317}
318
319impl FeeParameters {
320    // CONSTRUCTORS
321    // --------------------------------------------------------------------------------------------
322
323    /// Creates a new [`FeeParameters`] from the provided inputs.
324    ///
325    /// # Errors
326    ///
327    /// Returns an error if:
328    /// - the provided native asset ID is not a fungible faucet account ID.
329    pub fn new(native_asset_id: AccountId, verification_base_fee: u32) -> Result<Self, FeeError> {
330        if !matches!(native_asset_id.account_type(), AccountType::FungibleFaucet) {
331            return Err(FeeError::NativeAssetIdNotFungible {
332                account_type: native_asset_id.account_type(),
333            });
334        }
335
336        Ok(Self { native_asset_id, verification_base_fee })
337    }
338
339    // PUBLIC ACCESSORS
340    // --------------------------------------------------------------------------------------------
341
342    /// Returns the [`AccountId`] of the faucet whose assets are accepted for fee payments in the
343    /// transaction kernel, or in other words, the native asset of the blockchain.
344    pub fn native_asset_id(&self) -> AccountId {
345        self.native_asset_id
346    }
347
348    /// Returns the base fee capturing the cost for the verification of a transaction.
349    pub fn verification_base_fee(&self) -> u32 {
350        self.verification_base_fee
351    }
352}
353
354// SERIALIZATION
355// ================================================================================================
356
357impl Serializable for FeeParameters {
358    fn write_into<W: ByteWriter>(&self, target: &mut W) {
359        self.native_asset_id.write_into(target);
360        self.verification_base_fee.write_into(target);
361    }
362}
363
364impl Deserializable for FeeParameters {
365    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
366        let native_asset_id = source.read()?;
367        let verification_base_fee = source.read()?;
368
369        Self::new(native_asset_id, verification_base_fee)
370            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
371    }
372}
373// TESTS
374// ================================================================================================
375
376#[cfg(test)]
377mod tests {
378    use assert_matches::assert_matches;
379    use miden_core::Word;
380    use winter_rand_utils::rand_value;
381
382    use super::*;
383    use crate::testing::account_id::ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET;
384
385    #[test]
386    fn test_serde() {
387        let chain_commitment = rand_value::<Word>();
388        let note_root = rand_value::<Word>();
389        let tx_kernel_commitment = rand_value::<Word>();
390        let header = BlockHeader::mock(
391            0,
392            Some(chain_commitment),
393            Some(note_root),
394            &[],
395            tx_kernel_commitment,
396        );
397        let serialized = header.to_bytes();
398        let deserialized = BlockHeader::read_from_bytes(&serialized).unwrap();
399
400        assert_eq!(deserialized, header);
401    }
402
403    /// Tests that the fee parameters constructor fails when the provided account ID is not a
404    /// fungible faucet.
405    #[test]
406    fn fee_parameters_fail_when_native_asset_is_not_fungible() {
407        assert_matches!(
408            FeeParameters::new(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET.try_into().unwrap(), 0)
409                .unwrap_err(),
410            FeeError::NativeAssetIdNotFungible { .. }
411        );
412    }
413}