miden_objects/block/
header.rs

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