miden_protocol/batch/
output.rs1use alloc::vec::Vec;
2
3use crate::block::BlockNumber;
4use crate::errors::BatchOutputError;
5use crate::vm::StackOutputs;
6use crate::{Felt, Word};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct BatchOutputs {
16 input_notes_commitment: Word,
18 batch_note_tree_root: Word,
21 batch_expiration_block_num: BlockNumber,
23}
24
25impl BatchOutputs {
26 pub const INPUT_NOTES_COMMITMENT_WORD_IDX: usize = 0;
31 pub const BATCH_NOTE_TREE_ROOT_WORD_IDX: usize = 4;
33 pub const BATCH_EXPIRATION_BLOCK_NUM_ELEMENT_IDX: usize = 8;
35
36 pub fn new(
41 input_notes_commitment: Word,
42 batch_note_tree_root: Word,
43 batch_expiration_block_num: BlockNumber,
44 ) -> Self {
45 Self {
46 input_notes_commitment,
47 batch_note_tree_root,
48 batch_expiration_block_num,
49 }
50 }
51
52 pub fn parse(stack: &StackOutputs) -> Result<Self, BatchOutputError> {
66 let input_notes_commitment =
67 stack.get_word(Self::INPUT_NOTES_COMMITMENT_WORD_IDX).ok_or_else(|| {
68 BatchOutputError::OutputStackInvalid(
69 "input notes commitment word missing from output stack".into(),
70 )
71 })?;
72 let batch_note_tree_root =
73 stack.get_word(Self::BATCH_NOTE_TREE_ROOT_WORD_IDX).ok_or_else(|| {
74 BatchOutputError::OutputStackInvalid(
75 "batch note tree root word missing from output stack".into(),
76 )
77 })?;
78
79 let expiration_felt =
80 stack.get_element(Self::BATCH_EXPIRATION_BLOCK_NUM_ELEMENT_IDX).ok_or_else(|| {
81 BatchOutputError::OutputStackInvalid(
82 "batch expiration block number missing from output stack".into(),
83 )
84 })?;
85
86 if stack[Self::BATCH_EXPIRATION_BLOCK_NUM_ELEMENT_IDX + 1..]
88 .iter()
89 .any(|&felt| felt != Felt::ZERO)
90 {
91 return Err(BatchOutputError::OutputStackInvalid(
92 "batch_expiration_block_num must be followed by zero padding".into(),
93 ));
94 }
95
96 let batch_expiration_block_num = u32::try_from(expiration_felt.as_canonical_u64())
97 .map_err(|_| BatchOutputError::ExpirationBlockNumberTooLarge(expiration_felt))?
98 .into();
99
100 Ok(Self::new(
101 input_notes_commitment,
102 batch_note_tree_root,
103 batch_expiration_block_num,
104 ))
105 }
106
107 pub fn input_notes_commitment(&self) -> Word {
112 self.input_notes_commitment
113 }
114
115 pub fn batch_note_tree_root(&self) -> Word {
117 self.batch_note_tree_root
118 }
119
120 pub fn batch_expiration_block_num(&self) -> BlockNumber {
122 self.batch_expiration_block_num
123 }
124
125 pub fn into_stack_outputs(self) -> StackOutputs {
136 let mut outputs: Vec<Felt> = Vec::with_capacity(9);
137 outputs.extend_from_slice(self.input_notes_commitment.as_elements());
138 outputs.extend_from_slice(self.batch_note_tree_root.as_elements());
139 outputs.push(Felt::from(self.batch_expiration_block_num));
140
141 StackOutputs::new(&outputs).expect("number of stack outputs should be <= 16")
142 }
143}
144
145#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn parse_returns_outputs_for_well_formed_stack() {
154 let input_notes_commitment =
155 Word::from([Felt::from(1u32), Felt::from(2u32), Felt::from(3u32), Felt::from(4u32)]);
156 let batch_note_tree_root =
157 Word::from([Felt::from(5u32), Felt::from(6u32), Felt::from(7u32), Felt::from(8u32)]);
158 let elements = [
159 Felt::from(1u32),
160 Felt::from(2u32),
161 Felt::from(3u32),
162 Felt::from(4u32),
163 Felt::from(5u32),
164 Felt::from(6u32),
165 Felt::from(7u32),
166 Felt::from(8u32),
167 Felt::from(1234u32),
168 ];
169 let stack = StackOutputs::new(&elements).unwrap();
170
171 let outputs = BatchOutputs::parse(&stack).unwrap();
172
173 assert_eq!(outputs.input_notes_commitment(), input_notes_commitment);
174 assert_eq!(outputs.batch_note_tree_root(), batch_note_tree_root);
175 assert_eq!(outputs.batch_expiration_block_num(), BlockNumber::from(1234u32));
176 }
177
178 #[test]
179 fn parse_rejects_non_zero_padding() {
180 let elements = [
182 Felt::ZERO,
183 Felt::ZERO,
184 Felt::ZERO,
185 Felt::ZERO,
186 Felt::ZERO,
187 Felt::ZERO,
188 Felt::ZERO,
189 Felt::ZERO,
190 Felt::from(7u32),
191 Felt::from(1u32),
192 ];
193 let stack = StackOutputs::new(&elements).unwrap();
194
195 assert!(matches!(
196 BatchOutputs::parse(&stack),
197 Err(BatchOutputError::OutputStackInvalid(_))
198 ));
199 }
200
201 #[test]
202 fn parse_rejects_oversized_expiration() {
203 let oversized = Felt::from(u32::MAX) + Felt::from(1u32);
205 let elements = [
206 Felt::ZERO,
207 Felt::ZERO,
208 Felt::ZERO,
209 Felt::ZERO,
210 Felt::ZERO,
211 Felt::ZERO,
212 Felt::ZERO,
213 Felt::ZERO,
214 oversized,
215 ];
216 let stack = StackOutputs::new(&elements).unwrap();
217
218 assert!(matches!(
219 BatchOutputs::parse(&stack),
220 Err(BatchOutputError::ExpirationBlockNumberTooLarge(_))
221 ));
222 }
223}