Skip to main content

miden_protocol/block/
block_outputs.rs

1use alloc::vec::Vec;
2
3use crate::errors::BlockOutputError;
4use crate::vm::StackOutputs;
5use crate::{Felt, Word};
6
7// BLOCK OUTPUTS
8// ================================================================================================
9
10/// The public outputs produced by the block kernel.
11///
12/// This is the parsed, typed form of the kernel's output stack.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct BlockOutputs {
15    /// The commitment of the block header created by the block kernel.
16    block_commitment: Word,
17    /// The commitment to the set of nullifiers created in the block.
18    nullifier_commitment: Word,
19}
20
21impl BlockOutputs {
22    // OUTPUT STACK LAYOUT
23    // --------------------------------------------------------------------------------------------
24
25    /// The element index at which the block commitment word starts on the output stack.
26    pub const BLOCK_COMMITMENT_WORD_IDX: usize = 0;
27    /// The element index at which the nullifier commitment word starts on the output stack.
28    pub const NULLIFIER_COMMITMENT_WORD_IDX: usize = 4;
29
30    /// The number of elements the block kernel's outputs occupy on the stack.
31    const NUM_OUTPUT_ELEMENTS: usize = 8;
32
33    // CONSTRUCTOR
34    // --------------------------------------------------------------------------------------------
35
36    /// Returns a new [`BlockOutputs`] instantiated from the provided data.
37    pub fn new(block_commitment: Word, nullifier_commitment: Word) -> Self {
38        Self { block_commitment, nullifier_commitment }
39    }
40
41    // PARSER
42    // --------------------------------------------------------------------------------------------
43
44    /// Parses the block kernel's output stack into a [`BlockOutputs`].
45    ///
46    /// # Errors
47    ///
48    /// Returns [`BlockOutputError::PaddingNotZero`] if the cells following the nullifier
49    /// commitment (positions 8..16) are not all zero.
50    pub fn parse(stack: &StackOutputs) -> Result<Self, BlockOutputError> {
51        let block_commitment = stack
52            .get_word(Self::BLOCK_COMMITMENT_WORD_IDX)
53            .expect("block commitment word should be within the output stack");
54
55        let nullifier_commitment = stack
56            .get_word(Self::NULLIFIER_COMMITMENT_WORD_IDX)
57            .expect("nullifier commitment word should be within the output stack");
58
59        // Every cell after the nullifier commitment must be zero padding.
60        if let Some(index) = stack[Self::NUM_OUTPUT_ELEMENTS..]
61            .iter()
62            .position(|&felt| felt != Felt::ZERO)
63            .map(|offset| offset + Self::NUM_OUTPUT_ELEMENTS)
64        {
65            return Err(BlockOutputError::PaddingNotZero { index });
66        }
67
68        Ok(Self::new(block_commitment, nullifier_commitment))
69    }
70
71    // PUBLIC ACCESSORS
72    // --------------------------------------------------------------------------------------------
73
74    /// Returns the commitment of the block header created by the block kernel.
75    pub fn block_commitment(&self) -> Word {
76        self.block_commitment
77    }
78
79    /// Returns the commitment to the set of nullifiers created in the block.
80    pub fn nullifier_commitment(&self) -> Word {
81        self.nullifier_commitment
82    }
83
84    // CONVERSIONS
85    // --------------------------------------------------------------------------------------------
86
87    /// Encodes these [`BlockOutputs`] into the block kernel's output stack.
88    ///
89    /// This is the inverse of [`BlockOutputs::parse`]; the resulting stack is laid out as:
90    ///
91    /// ```text
92    /// [BLOCK_COMMITMENT, NULLIFIER_COMMITMENT]
93    /// ```
94    pub fn into_stack_outputs(self) -> StackOutputs {
95        let mut outputs: Vec<Felt> = Vec::with_capacity(Self::NUM_OUTPUT_ELEMENTS);
96        outputs.extend_from_slice(self.block_commitment.as_elements());
97        outputs.extend_from_slice(self.nullifier_commitment.as_elements());
98
99        StackOutputs::new(&outputs).expect("number of stack outputs should be <= 16")
100    }
101}
102
103// TESTS
104// ================================================================================================
105
106#[cfg(test)]
107mod tests {
108    use assert_matches::assert_matches;
109
110    use super::*;
111
112    #[test]
113    fn parse_returns_outputs_for_well_formed_stack() {
114        let block_commitment =
115            Word::from([Felt::from(1u32), Felt::from(2u32), Felt::from(3u32), Felt::from(4u32)]);
116        let nullifier_commitment =
117            Word::from([Felt::from(5u32), Felt::from(6u32), Felt::from(7u32), Felt::from(8u32)]);
118        let stack = BlockOutputs::new(block_commitment, nullifier_commitment).into_stack_outputs();
119
120        let outputs = BlockOutputs::parse(&stack).unwrap();
121
122        assert_eq!(outputs.block_commitment(), block_commitment);
123        assert_eq!(outputs.nullifier_commitment(), nullifier_commitment);
124    }
125
126    #[test]
127    fn parse_reports_the_index_of_the_first_non_zero_padding_cell() {
128        // Leave the first padding cell zero so the reported index is not simply the first one.
129        let mut elements = [Felt::ZERO; 12];
130        elements[11] = Felt::from(1u32);
131        let stack = StackOutputs::new(&elements).unwrap();
132
133        assert_matches!(
134            BlockOutputs::parse(&stack),
135            Err(BlockOutputError::PaddingNotZero { index: 11 })
136        );
137    }
138}