miden_objects/block/
header.rs

1use alloc::vec::Vec;
2
3use crate::{
4    Digest, Felt, Hasher, ZERO,
5    account::AccountId,
6    block::BlockNumber,
7    transaction::TransactionId,
8    utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
9};
10
11/// The header of a block. It contains metadata about the block, commitments to the current
12/// state of the chain and the hash of the proof that attests to the integrity of the chain.
13///
14/// A block header includes the following fields:
15///
16/// - `version` specifies the version of the protocol.
17/// - `prev_block_commitment` is the hash of the previous block header.
18/// - `block_num` is a unique sequential number of the current block.
19/// - `chain_commitment` is a commitment to an MMR of the entire chain where each block is a leaf.
20/// - `account_root` is a commitment to account database.
21/// - `nullifier_root` is a commitment to the nullifier database.
22/// - `note_root` is a commitment to all notes created in the current block.
23/// - `tx_commitment` is a commitment to the set of transaction IDs which affected accounts in the
24///   block.
25/// - `tx_kernel_commitment` a commitment to all transaction kernels supported by this block.
26/// - `proof_commitment` is the commitment of the block's STARK proof attesting to the correct state
27///   transition.
28/// - `timestamp` is the time when the block was created, in seconds since UNIX epoch. Current
29///   representation is sufficient to represent time up to year 2106.
30/// - `sub_commitment` is a sequential hash of all fields except the note_root.
31/// - `commitment` is a 2-to-1 hash of the sub_commitment and the note_root.
32#[derive(Debug, Eq, PartialEq, Clone)]
33pub struct BlockHeader {
34    version: u32,
35    prev_block_commitment: Digest,
36    block_num: BlockNumber,
37    chain_commitment: Digest,
38    account_root: Digest,
39    nullifier_root: Digest,
40    note_root: Digest,
41    tx_commitment: Digest,
42    tx_kernel_commitment: Digest,
43    proof_commitment: Digest,
44    timestamp: u32,
45    sub_commitment: Digest,
46    commitment: Digest,
47}
48
49impl BlockHeader {
50    /// Creates a new block header.
51    #[allow(clippy::too_many_arguments)]
52    pub fn new(
53        version: u32,
54        prev_block_commitment: Digest,
55        block_num: BlockNumber,
56        chain_commitment: Digest,
57        account_root: Digest,
58        nullifier_root: Digest,
59        note_root: Digest,
60        tx_commitment: Digest,
61        tx_kernel_commitment: Digest,
62        proof_commitment: Digest,
63        timestamp: u32,
64    ) -> Self {
65        // compute block sub commitment
66        let sub_commitment = Self::compute_sub_commitment(
67            version,
68            prev_block_commitment,
69            chain_commitment,
70            account_root,
71            nullifier_root,
72            tx_commitment,
73            tx_kernel_commitment,
74            proof_commitment,
75            timestamp,
76            block_num,
77        );
78
79        // The sub commitment is merged with the note_root - hash(sub_commitment, note_root) to
80        // produce the final hash. This is done to make the note_root easily accessible
81        // without having to unhash the entire header. Having the note_root easily
82        // accessible is useful when authenticating notes.
83        let commitment = Hasher::merge(&[sub_commitment, note_root]);
84
85        Self {
86            version,
87            prev_block_commitment,
88            block_num,
89            chain_commitment,
90            account_root,
91            nullifier_root,
92            note_root,
93            tx_commitment,
94            tx_kernel_commitment,
95            proof_commitment,
96            timestamp,
97            sub_commitment,
98            commitment,
99        }
100    }
101
102    // ACCESSORS
103    // --------------------------------------------------------------------------------------------
104
105    /// Returns the protocol version.
106    pub fn version(&self) -> u32 {
107        self.version
108    }
109
110    /// Returns the commitment of the block header.
111    pub fn commitment(&self) -> Digest {
112        self.commitment
113    }
114
115    /// Returns the sub commitment of the block header.
116    ///
117    /// The sub commitment is a sequential hash of all block header fields except the note root.
118    /// This is used in the block commitment computation which is a 2-to-1 hash of the sub
119    /// commitment and the note root [hash(sub_commitment, note_root)]. This procedure is used to
120    /// make the note root easily accessible without having to unhash the entire header.
121    pub fn sub_commitment(&self) -> Digest {
122        self.sub_commitment
123    }
124
125    /// Returns the commitment to the previous block header.
126    pub fn prev_block_commitment(&self) -> Digest {
127        self.prev_block_commitment
128    }
129
130    /// Returns the block number.
131    pub fn block_num(&self) -> BlockNumber {
132        self.block_num
133    }
134
135    /// Returns the epoch to which this block belongs.
136    ///
137    /// This is the block number shifted right by [`BlockNumber::EPOCH_LENGTH_EXPONENT`].
138    pub fn block_epoch(&self) -> u16 {
139        self.block_num.block_epoch()
140    }
141
142    /// Returns the chain commitment.
143    pub fn chain_commitment(&self) -> Digest {
144        self.chain_commitment
145    }
146
147    /// Returns the account database root.
148    pub fn account_root(&self) -> Digest {
149        self.account_root
150    }
151
152    /// Returns the nullifier database root.
153    pub fn nullifier_root(&self) -> Digest {
154        self.nullifier_root
155    }
156
157    /// Returns the note root.
158    pub fn note_root(&self) -> Digest {
159        self.note_root
160    }
161
162    /// Returns the commitment to all transactions in this block.
163    ///
164    /// The commitment is computed as sequential hash of (`transaction_id`, `account_id`) tuples.
165    /// This makes it possible for the verifier to link transaction IDs to the accounts which
166    /// they were executed against.
167    pub fn tx_commitment(&self) -> Digest {
168        self.tx_commitment
169    }
170
171    /// Returns the transaction kernel commitment.
172    ///
173    /// The transaction kernel commitment is computed as a sequential hash of all transaction kernel
174    /// hashes.
175    pub fn tx_kernel_commitment(&self) -> Digest {
176        self.tx_kernel_commitment
177    }
178
179    /// Returns the proof commitment.
180    pub fn proof_commitment(&self) -> Digest {
181        self.proof_commitment
182    }
183
184    /// Returns the timestamp at which the block was created, in seconds since UNIX epoch.
185    pub fn timestamp(&self) -> u32 {
186        self.timestamp
187    }
188
189    /// Returns the block number of the epoch block to which this block belongs.
190    pub fn epoch_block_num(&self) -> BlockNumber {
191        BlockNumber::from_epoch(self.block_epoch())
192    }
193
194    // HELPERS
195    // --------------------------------------------------------------------------------------------
196
197    /// Computes the sub commitment of the block header.
198    ///
199    /// The sub commitment is computed as a sequential hash of the following fields:
200    /// `prev_block_commitment`, `chain_commitment`, `account_root`, `nullifier_root`, `note_root`,
201    /// `tx_commitment`, `tx_kernel_commitment`, `proof_commitment`, `version`, `timestamp`,
202    /// `block_num` (all fields except the `note_root`).
203    #[allow(clippy::too_many_arguments)]
204    fn compute_sub_commitment(
205        version: u32,
206        prev_block_commitment: Digest,
207        chain_commitment: Digest,
208        account_root: Digest,
209        nullifier_root: Digest,
210        tx_commitment: Digest,
211        tx_kernel_commitment: Digest,
212        proof_commitment: Digest,
213        timestamp: u32,
214        block_num: BlockNumber,
215    ) -> Digest {
216        let mut elements: Vec<Felt> = Vec::with_capacity(32);
217        elements.extend_from_slice(prev_block_commitment.as_elements());
218        elements.extend_from_slice(chain_commitment.as_elements());
219        elements.extend_from_slice(account_root.as_elements());
220        elements.extend_from_slice(nullifier_root.as_elements());
221        elements.extend_from_slice(tx_commitment.as_elements());
222        elements.extend_from_slice(tx_kernel_commitment.as_elements());
223        elements.extend_from_slice(proof_commitment.as_elements());
224        elements.extend([block_num.into(), version.into(), timestamp.into(), ZERO]);
225        Hasher::hash_elements(&elements)
226    }
227
228    /// Computes a commitment to the provided list of transactions.
229    pub fn compute_tx_commitment(
230        updated_accounts: impl Iterator<Item = (TransactionId, AccountId)>,
231    ) -> Digest {
232        let mut elements = vec![];
233        for (transaction_id, account_id) in updated_accounts {
234            let [account_id_prefix, account_id_suffix] = <[Felt; 2]>::from(account_id);
235            elements.extend_from_slice(transaction_id.as_elements());
236            elements.extend_from_slice(&[account_id_prefix, account_id_suffix, ZERO, ZERO]);
237        }
238
239        Hasher::hash_elements(&elements)
240    }
241}
242
243// SERIALIZATION
244// ================================================================================================
245
246impl Serializable for BlockHeader {
247    fn write_into<W: ByteWriter>(&self, target: &mut W) {
248        self.version.write_into(target);
249        self.prev_block_commitment.write_into(target);
250        self.block_num.write_into(target);
251        self.chain_commitment.write_into(target);
252        self.account_root.write_into(target);
253        self.nullifier_root.write_into(target);
254        self.note_root.write_into(target);
255        self.tx_commitment.write_into(target);
256        self.tx_kernel_commitment.write_into(target);
257        self.proof_commitment.write_into(target);
258        self.timestamp.write_into(target);
259    }
260}
261
262impl Deserializable for BlockHeader {
263    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
264        let version = source.read()?;
265        let prev_block_commitment = source.read()?;
266        let block_num = source.read()?;
267        let chain_commitment = source.read()?;
268        let account_root = source.read()?;
269        let nullifier_root = source.read()?;
270        let note_root = source.read()?;
271        let tx_commitment = source.read()?;
272        let tx_kernel_commitment = source.read()?;
273        let proof_commitment = source.read()?;
274        let timestamp = source.read()?;
275
276        Ok(Self::new(
277            version,
278            prev_block_commitment,
279            block_num,
280            chain_commitment,
281            account_root,
282            nullifier_root,
283            note_root,
284            tx_commitment,
285            tx_kernel_commitment,
286            proof_commitment,
287            timestamp,
288        ))
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use vm_core::Word;
295    use winter_rand_utils::rand_array;
296
297    use super::*;
298
299    #[test]
300    fn test_serde() {
301        let chain_commitment: Word = rand_array();
302        let note_root: Word = rand_array();
303        let tx_kernel_commitment: Word = rand_array();
304        let header = BlockHeader::mock(
305            0,
306            Some(chain_commitment.into()),
307            Some(note_root.into()),
308            &[],
309            tx_kernel_commitment.into(),
310        );
311        let serialized = header.to_bytes();
312        let deserialized = BlockHeader::read_from_bytes(&serialized).unwrap();
313
314        assert_eq!(deserialized, header);
315    }
316}