miden_protocol/block/
header.rs

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