1use alloc::{
9 collections::{BTreeMap, BTreeSet},
10 format,
11 sync::Arc,
12 vec::Vec,
13};
14
15use miden_crypto::hash::eidos::{EidosDomain, EidosFrame};
16
17use super::{
18 DEFERRED_AND_FRAME, DataChunk, DeferredError, DeferredState, Digest,
19 MAX_DEFERRED_WIRE_ELEMENTS, Node, NodeType, PrecompileError, PrecompileLimits,
20 PrecompileRegistry, PrecompileWork, TRUE_DIGEST,
21};
22use crate::{
23 Felt, Word, ZERO,
24 program::domain::DeferredChunksDomain,
25 serde::{
26 BudgetedReader, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
27 SliceReader, validate_bounded_len,
28 },
29};
30
31const TRUE_INDEX: u32 = 0;
36
37const MAX_WIRE_ENTRIES: usize = MAX_DEFERRED_WIRE_ELEMENTS / EidosFrame::FELT_LEN;
38
39fn reserve_wire_elements(
40 remaining_elements: &mut usize,
41 requested_elements: usize,
42) -> Result<(), DeserializationError> {
43 *remaining_elements = remaining_elements.checked_sub(requested_elements).ok_or_else(|| {
44 DeserializationError::InvalidValue(format!(
45 "deferred wire exceeds the {MAX_DEFERRED_WIRE_ELEMENTS} element limit"
46 ))
47 })?;
48 Ok(())
49}
50
51fn reserve_wire_payload(
52 remaining_elements: &mut usize,
53 payload_count: usize,
54) -> Result<(), DeserializationError> {
55 let payload_elements =
56 payload_count.checked_mul(Node::DATA_CHUNK_FELT_LEN).ok_or_else(|| {
57 DeserializationError::InvalidValue("deferred wire element count overflow".into())
58 })?;
59 reserve_wire_elements(remaining_elements, payload_elements)
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
71pub enum WireEntry {
72 Data {
77 frame: EidosFrame,
78 chunks: Vec<DataChunk>,
79 },
80 Join { frame: EidosFrame, lhs: u32, rhs: u32 },
82 PairList {
84 frame: EidosFrame,
85 pairs: Vec<(u32, u32)>,
86 },
87}
88
89impl WireEntry {
90 fn min_serialized_size() -> usize {
92 1 + Word::min_serialized_size() + usize::min_serialized_size()
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
105#[cfg_attr(
106 all(feature = "arbitrary", test),
107 miden_test_serialization_macros::serialization_test
108)]
109pub struct PrecompileWitness {
110 entries: Vec<WireEntry>,
111}
112
113impl PrecompileWitness {
114 pub const WIRE_VERSION: u8 = 2;
116
117 pub fn from_entries(entries: Vec<WireEntry>) -> Result<Self, IntegrityError> {
119 let wire = Self { entries };
120 wire.validate_element_limit()?;
121 Ok(wire)
122 }
123
124 pub fn prepare(
128 self,
129 registry: Arc<PrecompileRegistry>,
130 verification_limits: &PrecompileLimits,
131 ) -> Result<PreparedWitness, PreparationError> {
132 if self.entries.is_empty() {
133 return Err(IntegrityError::InvalidStructure.into());
134 }
135 let mut indices = BTreeMap::new();
136 let mut nodes: Vec<PreparedNode> = Vec::with_capacity(self.entries.len());
137 let mut work = PrecompileWork::default();
138 indices.insert(TRUE_DIGEST, 0);
139
140 for entry in self.entries {
141 let child = |index: u32| match index {
142 TRUE_INDEX => Ok(TRUE_DIGEST),
143 index => nodes
144 .get(index as usize - 1)
145 .map(PreparedNode::digest)
146 .ok_or(IntegrityError::InvalidStructure),
147 };
148 let node = match entry {
149 WireEntry::Data { frame, chunks } => {
150 if frame.domain() == DeferredChunksDomain::TAG {
151 if Node::chunks_frame_for_len(chunks.len())
152 .map_err(|_| IntegrityError::InvalidStructure)?
153 != frame
154 {
155 return Err(IntegrityError::InvalidStructure.into());
156 }
157 Node::chunks(chunks)
158 } else {
159 Node::try_data(frame, chunks)
160 }
161 },
162 WireEntry::Join { frame, lhs, rhs } => {
163 let (lhs, rhs) = (child(lhs)?, child(rhs)?);
164 if frame == DEFERRED_AND_FRAME {
165 Ok(Node::and(lhs, rhs))
166 } else {
167 Node::join(frame, lhs, rhs)
168 }
169 },
170 WireEntry::PairList { frame, pairs } => {
171 let pairs = pairs
172 .into_iter()
173 .map(|(lhs, rhs)| Ok((child(lhs)?, child(rhs)?)))
174 .collect::<Result<Vec<_>, IntegrityError>>()?;
175 Node::try_pair_list(frame, pairs)
176 },
177 }
178 .map_err(|_| IntegrityError::InvalidStructure)?;
179
180 registry.validate_node(&node)?;
181 work.charge_node(&node, ®istry, verification_limits)?;
182 let prepared = PreparedNode::new(node);
183 let digest = prepared.digest();
184 if indices.insert(digest, nodes.len() + 1).is_some() {
185 return Err(IntegrityError::InvalidStructure.into());
186 }
187 nodes.push(prepared);
188 }
189
190 Self::validate_canonical_order(&nodes, &indices)?;
191 let root = nodes.last().expect("the witness is nonempty").digest();
192 if root == TRUE_DIGEST {
193 return Err(IntegrityError::InvalidStructure.into());
194 }
195
196 Ok(PreparedWitness { registry, nodes, root, work })
197 }
198
199 pub fn entries(&self) -> &[WireEntry] {
201 &self.entries
202 }
203
204 fn validate_canonical_order(
205 nodes: &[PreparedNode],
206 indices: &BTreeMap<Digest, usize>,
207 ) -> Result<(), IntegrityError> {
208 let mut seen = alloc::vec![false; nodes.len() + 1];
211 let mut pending = alloc::vec![(nodes.len(), false)];
212 let mut next_index = 1;
213 while let Some((index, emit)) = pending.pop() {
214 if index == 0 {
215 continue;
216 }
217 if emit {
218 if index != next_index {
219 return Err(IntegrityError::InvalidStructure);
220 }
221 next_index += 1;
222 } else if !core::mem::replace(&mut seen[index], true) {
223 pending.push((index, true));
224 pending.extend(
225 nodes[index - 1].node().children().rev().map(|child| (indices[&child], false)),
226 );
227 }
228 }
229 if next_index != nodes.len() + 1 {
230 return Err(IntegrityError::InvalidStructure);
231 }
232 Ok(())
233 }
234
235 pub(crate) fn from_state(
237 state: &DeferredState,
238 root: Digest,
239 verification_limits: &PrecompileLimits,
240 ) -> Result<Self, PrecompileError> {
241 if root == TRUE_DIGEST {
242 return Err(PrecompileError::InvalidNode);
243 }
244 let mut build = WireEncoder::default();
245 build.visit_state_digest(state, root, verification_limits)?;
246 Ok(Self { entries: build.entries })
247 }
248
249 fn validate_element_limit(&self) -> Result<(), IntegrityError> {
250 let mut remaining_elements = MAX_DEFERRED_WIRE_ELEMENTS;
251 for entry in &self.entries {
252 let payload_count = match entry {
253 WireEntry::Data { chunks, .. } => chunks.len(),
254 WireEntry::Join { .. } => 1,
255 WireEntry::PairList { pairs, .. } => pairs.len(),
256 };
257 let payload_elements = payload_count
258 .checked_mul(Node::DATA_CHUNK_FELT_LEN)
259 .and_then(|elements| EidosFrame::FELT_LEN.checked_add(elements))
260 .ok_or(IntegrityError::InvalidStructure)?;
261 remaining_elements = remaining_elements.checked_sub(payload_elements).ok_or(
262 IntegrityError::DeferredStateTooLarge {
263 num_elements: payload_elements,
264 max: remaining_elements,
265 },
266 )?;
267 }
268 Ok(())
269 }
270}
271
272#[derive(Debug, Clone, PartialEq, Eq)]
280pub struct PreparedNode {
281 node: Node,
282 digest: Digest,
283}
284
285impl PreparedNode {
286 pub(super) fn new(node: Node) -> Self {
287 let digest = node.digest();
288 Self { node, digest }
289 }
290
291 pub(super) fn into_parts(self) -> (Digest, Node) {
292 (self.digest, self.node)
293 }
294
295 pub const fn node(&self) -> &Node {
297 &self.node
298 }
299
300 pub const fn digest(&self) -> Digest {
302 self.digest
303 }
304}
305
306#[derive(Debug, Clone)]
311pub struct PreparedWitness {
312 registry: Arc<PrecompileRegistry>,
313 nodes: Vec<PreparedNode>,
314 root: Digest,
315 work: PrecompileWork,
316}
317
318impl PreparedWitness {
319 pub const fn root(&self) -> Digest {
321 self.root
322 }
323
324 pub const fn work(&self) -> &PrecompileWork {
326 &self.work
327 }
328
329 pub fn into_nodes(self) -> impl ExactSizeIterator<Item = PreparedNode> {
333 self.nodes.into_iter()
334 }
335
336 pub fn evaluate(self) -> Result<(), PrecompileError> {
339 let Self { registry, nodes, root: _, work: _ } = self;
340 let mut evaluator = super::state::DeferredEvaluator::new(registry)?;
341 let mut digests = Vec::with_capacity(nodes.len());
342 for prepared in nodes {
343 digests.push(evaluator.insert_node(prepared)?);
344 }
345 let root_digest = digests.pop().ok_or(PrecompileError::MissingNode)?;
346 for digest in digests {
347 evaluator.evaluate_digest(digest)?;
348 }
349 if evaluator.evaluate_digest(root_digest)? != TRUE_DIGEST {
350 return Err(PrecompileError::AssertionFailed);
351 }
352 Ok(())
353 }
354}
355
356#[derive(Debug, Clone, thiserror::Error)]
358pub enum PreparationError {
359 #[error(transparent)]
360 Integrity(#[from] IntegrityError),
361 #[error(transparent)]
362 Precompile(#[from] PrecompileError),
363}
364
365#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
370pub enum IntegrityError {
371 #[error("invalid or non-canonical precompile witness structure")]
373 InvalidStructure,
374 #[error("deferred insertion requires {num_elements} elements but only {max} remain")]
376 DeferredStateTooLarge { num_elements: usize, max: usize },
377}
378
379#[derive(Default)]
384struct WireEncoder {
385 seen: BTreeSet<Digest>,
386 by_digest: BTreeMap<Digest, u32>,
387 entries: Vec<WireEntry>,
388}
389
390impl WireEncoder {
391 fn visit_state_digest(
392 &mut self,
393 state: &DeferredState,
394 digest: Digest,
395 verification_limits: &PrecompileLimits,
396 ) -> Result<(), PrecompileError> {
397 let mut pending = Vec::new();
398 let mut work = PrecompileWork::default();
399 pending.push(WireEncodeStep::Visit(digest));
400
401 while let Some(step) = pending.pop() {
402 match step {
403 WireEncodeStep::Visit(digest) => self.schedule_digest(
404 state,
405 digest,
406 &mut pending,
407 &mut work,
408 verification_limits,
409 )?,
410 WireEncodeStep::Emit(digest, node_type) => {
411 let entry = self.entry_for_digest(state, digest, node_type)?;
412 self.push_entry(digest, entry)?;
413 },
414 }
415 }
416
417 Ok(())
418 }
419
420 fn schedule_digest(
421 &mut self,
422 state: &DeferredState,
423 digest: Digest,
424 pending: &mut Vec<WireEncodeStep>,
425 work: &mut PrecompileWork,
426 verification_limits: &PrecompileLimits,
427 ) -> Result<(), PrecompileError> {
428 if digest == TRUE_DIGEST || self.seen.contains(&digest) {
429 return Ok(());
430 }
431
432 let (node_type, node) = self.validated_node(state, digest)?;
433 work.charge_node(node, state.registry(), verification_limits)?;
434 if work.elements() > MAX_DEFERRED_WIRE_ELEMENTS as u64 {
435 return Err(DeferredError::DeferredStateTooLarge {
436 num_elements: usize::try_from(work.elements()).unwrap_or(usize::MAX),
437 max: MAX_DEFERRED_WIRE_ELEMENTS,
438 }
439 .into());
440 }
441 self.seen.insert(digest);
442 pending.push(WireEncodeStep::Emit(digest, node_type));
443
444 match node_type {
445 NodeType::Data => {},
446 NodeType::Join => {
447 let (lhs, rhs) = node.payload().as_join()?;
448 pending.push(WireEncodeStep::Visit(rhs));
449 pending.push(WireEncodeStep::Visit(lhs));
450 },
451 NodeType::PairList => {
452 let pairs = node.payload().as_pair_list()?;
453 for (lhs, rhs) in pairs.iter().rev() {
454 pending.push(WireEncodeStep::Visit(*rhs));
455 pending.push(WireEncodeStep::Visit(*lhs));
456 }
457 },
458 NodeType::True => return Err(PrecompileError::InvalidNode),
459 };
460
461 Ok(())
462 }
463
464 fn entry_for_digest(
465 &self,
466 state: &DeferredState,
467 digest: Digest,
468 node_type: NodeType,
469 ) -> Result<WireEntry, PrecompileError> {
470 let node = state.get_node(&digest).ok_or(PrecompileError::MissingNode)?;
471
472 Ok(match node_type {
473 NodeType::Data => WireEntry::Data {
474 frame: node.frame().ok_or(PrecompileError::InvalidNode)?,
475 chunks: node.payload().as_data()?.to_vec(),
476 },
477 NodeType::Join => {
478 let (lhs, rhs) = node.payload().as_join()?;
479 let lhs = self.index_for(lhs)?;
480 let rhs = self.index_for(rhs)?;
481 WireEntry::Join {
482 frame: node.frame().ok_or(PrecompileError::InvalidNode)?,
483 lhs,
484 rhs,
485 }
486 },
487 NodeType::PairList => {
488 let pairs = node.payload().as_pair_list()?;
489 let pairs = pairs
490 .iter()
491 .map(|(lhs, rhs)| Ok((self.index_for(*lhs)?, self.index_for(*rhs)?)))
492 .collect::<Result<Vec<_>, PrecompileError>>()?;
493 WireEntry::PairList {
494 frame: node.frame().ok_or(PrecompileError::InvalidNode)?,
495 pairs,
496 }
497 },
498 NodeType::True => return Err(PrecompileError::InvalidNode),
499 })
500 }
501
502 fn validated_node<'a>(
503 &self,
504 state: &'a DeferredState,
505 digest: Digest,
506 ) -> Result<(NodeType, &'a Node), PrecompileError> {
507 let node = state.get_node(&digest).ok_or(PrecompileError::MissingNode)?;
508 let node_type = state.registry().validate_node(node)?;
509 Ok((node_type, node))
510 }
511
512 fn index_for(&self, digest: Digest) -> Result<u32, PrecompileError> {
513 if digest == TRUE_DIGEST {
514 return Ok(TRUE_INDEX);
515 }
516 self.by_digest.get(&digest).copied().ok_or(PrecompileError::MissingNode)
517 }
518
519 fn push_entry(&mut self, digest: Digest, entry: WireEntry) -> Result<(), PrecompileError> {
520 let next_index = self.entries.len().checked_add(1).ok_or(PrecompileError::InvalidNode)?;
521 let next_index = u32::try_from(next_index).map_err(|_| PrecompileError::InvalidNode)?;
522 self.entries.push(entry);
523 self.by_digest.insert(digest, next_index);
524 Ok(())
525 }
526}
527
528enum WireEncodeStep {
529 Visit(Digest),
530 Emit(Digest, NodeType),
531}
532
533impl Serializable for WireEntry {
537 fn write_into<W: ByteWriter>(&self, target: &mut W) {
538 match self {
539 Self::Data { frame, chunks } => {
540 target.write_u8(0);
541 frame.as_word().write_into(target);
542 target.write_usize(chunks.len());
543 for chunk in chunks {
544 for felt in chunk {
545 felt.write_into(target);
546 }
547 }
548 },
549 Self::Join { frame, lhs, rhs } => {
550 target.write_u8(1);
551 frame.as_word().write_into(target);
552 target.write_u32(*lhs);
553 target.write_u32(*rhs);
554 },
555 Self::PairList { frame, pairs } => {
556 target.write_u8(2);
557 frame.as_word().write_into(target);
558 target.write_usize(pairs.len());
559 for (lhs, rhs) in pairs {
560 target.write_u32(*lhs);
561 target.write_u32(*rhs);
562 }
563 },
564 }
565 }
566}
567
568struct WirePair((u32, u32));
569
570impl Deserializable for WirePair {
571 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
572 Ok(Self((source.read_u32()?, source.read_u32()?)))
573 }
574
575 fn min_serialized_size() -> usize {
576 u32::min_serialized_size() * 2
577 }
578}
579
580struct WireDataChunk(DataChunk);
581
582impl Deserializable for WireDataChunk {
583 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
584 let mut chunk = [ZERO; Node::DATA_CHUNK_FELT_LEN];
585 for felt in &mut chunk {
586 *felt = Felt::read_from(source)?;
587 }
588 Ok(Self(chunk))
589 }
590
591 fn min_serialized_size() -> usize {
592 Node::DATA_CHUNK_FELT_LEN * Felt::min_serialized_size()
593 }
594}
595
596impl Serializable for PrecompileWitness {
597 fn write_into<W: ByteWriter>(&self, target: &mut W) {
598 target.write_u8(Self::WIRE_VERSION);
599 target.write_usize(self.entries.len());
600 for entry in &self.entries {
601 entry.write_into(target);
602 }
603 }
604}
605
606impl Deserializable for PrecompileWitness {
607 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
608 let version = source.read_u8()?;
609 if version != Self::WIRE_VERSION {
610 return Err(DeserializationError::InvalidValue(format!(
611 "unsupported precompile witness version {version} (expected {})",
612 Self::WIRE_VERSION
613 )));
614 }
615 let entry_count =
616 read_len(source, "precompile witness entry", WireEntry::min_serialized_size())?;
617 if entry_count > MAX_WIRE_ENTRIES {
618 return Err(DeserializationError::InvalidValue(format!(
619 "precompile witness contains {entry_count} entries, maximum is {MAX_WIRE_ENTRIES}"
620 )));
621 }
622
623 let mut remaining_elements = MAX_DEFERRED_WIRE_ELEMENTS;
624 let mut entries = Vec::with_capacity(entry_count);
625 for _ in 0..entry_count {
626 entries.push(read_wire_entry(source, &mut remaining_elements)?);
627 }
628 Ok(Self { entries })
629 }
630
631 fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
632 let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
633 let wire = Self::read_from(&mut reader)?;
634 if reader.has_more_bytes() {
635 return Err(DeserializationError::InvalidValue(
636 "trailing bytes after deferred witness".into(),
637 ));
638 }
639 Ok(wire)
640 }
641
642 fn min_serialized_size() -> usize {
643 1 + usize::min_serialized_size()
644 }
645}
646
647fn read_len<R: ByteReader>(
649 source: &mut R,
650 label: &str,
651 min_element_size: usize,
652) -> Result<usize, DeserializationError> {
653 let encoded_len = source.peek_u8()?.trailing_zeros() as usize + 1;
654 let len = source.read_usize()?;
655 if encoded_len != len.get_size_hint() {
657 return Err(DeserializationError::InvalidValue(format!("noncanonical {label} length")));
658 }
659 validate_bounded_len(source, label, len, min_element_size)?;
660 Ok(len)
661}
662
663fn read_wire_entry<R: ByteReader>(
664 source: &mut R,
665 remaining_elements: &mut usize,
666) -> Result<WireEntry, DeserializationError> {
667 let discriminant = source.read_u8()?;
668 match discriminant {
669 0 => {
670 reserve_wire_elements(remaining_elements, EidosFrame::FELT_LEN)?;
671 let frame = read_frame(source)?;
672 let chunk_count = read_len(source, "data chunk", WireDataChunk::min_serialized_size())?;
673 reserve_wire_payload(remaining_elements, chunk_count)?;
674 let chunks = source
675 .read_many_iter::<WireDataChunk>(chunk_count)?
676 .map(|chunk| chunk.map(|chunk| chunk.0))
677 .collect::<Result<_, _>>()?;
678 Ok(WireEntry::Data { frame, chunks })
679 },
680 1 => {
681 reserve_wire_elements(
682 remaining_elements,
683 EidosFrame::FELT_LEN + Node::DATA_CHUNK_FELT_LEN,
684 )?;
685 let frame = read_frame(source)?;
686 let lhs = source.read_u32()?;
687 let rhs = source.read_u32()?;
688 Ok(WireEntry::Join { frame, lhs, rhs })
689 },
690 2 => {
691 reserve_wire_elements(remaining_elements, EidosFrame::FELT_LEN)?;
692 let frame = read_frame(source)?;
693 let pair_count = read_len(source, "child pair", WirePair::min_serialized_size())?;
694 reserve_wire_payload(remaining_elements, pair_count)?;
695 let pairs = source
696 .read_many_iter::<WirePair>(pair_count)?
697 .map(|pair| pair.map(|pair| pair.0))
698 .collect::<Result<_, _>>()?;
699 Ok(WireEntry::PairList { frame, pairs })
700 },
701 other => Err(DeserializationError::InvalidValue(format!(
702 "invalid deferred wire entry discriminant: {other}"
703 ))),
704 }
705}
706
707fn read_frame<R: ByteReader>(source: &mut R) -> Result<EidosFrame, DeserializationError> {
708 EidosFrame::from_word(Word::read_from(source)?)
709 .ok_or_else(|| DeserializationError::InvalidValue("invalid deferred Eidos frame".into()))
710}
711
712#[cfg(test)]
713mod tests {
714 use super::*;
715 use crate::deferred::{
716 PrecompileLimitError, deferred_chunks_frame, precompile::test_precompile_domain_tag,
717 };
718
719 fn felts(seed: u64) -> DataChunk {
720 core::array::from_fn(|i| Felt::new_unchecked(seed + i as u64))
721 }
722
723 fn frame(seed: u8) -> EidosFrame {
724 EidosFrame::new(test_precompile_domain_tag(seed), [u32::from(seed), 0, 0])
725 }
726
727 fn encoded_entries(entries: &[WireEntry]) -> Vec<u8> {
728 let mut bytes = alloc::vec![PrecompileWitness::WIRE_VERSION];
729 bytes.write_usize(entries.len());
730 for entry in entries {
731 entry.write_into(&mut bytes);
732 }
733 bytes
734 }
735
736 fn framework_limits() -> PrecompileLimits {
737 PrecompileLimits::new(MAX_DEFERRED_WIRE_ELEMENTS as u64)
738 }
739
740 fn prepare_framework(witness: PrecompileWitness) -> Result<PreparedWitness, PreparationError> {
741 witness.prepare(Arc::new(PrecompileRegistry::new()), &framework_limits())
742 }
743
744 #[test]
745 fn portable_structure_is_independent_of_operation_support() {
746 let entries = alloc::vec![
747 WireEntry::Data {
748 frame: frame(1),
749 chunks: alloc::vec![felts(10)]
750 },
751 WireEntry::Data {
752 frame: frame(2),
753 chunks: alloc::vec![felts(20), felts(30)]
754 },
755 WireEntry::Join { frame: frame(3), lhs: 1, rhs: 1 },
756 WireEntry::PairList {
757 frame: frame(4),
758 pairs: alloc::vec![(1, 2), (3, 3)]
759 },
760 ];
761 let witness = PrecompileWitness::from_entries(entries).unwrap();
762 let left = Node::value(frame(1), felts(10)).unwrap().digest();
763 let right = Node::try_data(frame(2), alloc::vec![felts(20), felts(30)]).unwrap().digest();
764 let claim = Node::join(frame(3), left, left).unwrap().digest();
765 let expected = Node::try_pair_list(frame(4), alloc::vec![(left, right), (claim, claim)])
766 .unwrap()
767 .digest();
768 assert_ne!(expected, TRUE_DIGEST);
769 assert_eq!(PrecompileWitness::read_from_bytes(&witness.to_bytes()).unwrap(), witness);
770 assert!(matches!(
771 prepare_framework(witness),
772 Err(PreparationError::Precompile(PrecompileError::InvalidNode))
773 ));
774 }
775
776 #[test]
777 fn portable_decode_defers_graph_validation_until_preparation() {
778 let leaf = || WireEntry::Data {
779 frame: deferred_chunks_frame(1),
780 chunks: alloc::vec![felts(10)],
781 };
782 let other = || WireEntry::Data {
783 frame: deferred_chunks_frame(1),
784 chunks: alloc::vec![felts(20)],
785 };
786 let malformed_and = EidosFrame::new(DEFERRED_AND_FRAME.domain(), [1, 0, 0]);
787 let graph_invalid = [
788 Vec::new(),
789 alloc::vec![WireEntry::Data {
790 frame: deferred_chunks_frame(1),
791 chunks: Vec::new()
792 }],
793 alloc::vec![WireEntry::PairList { frame: frame(1), pairs: Vec::new() }],
794 alloc::vec![WireEntry::Data {
795 frame: DEFERRED_AND_FRAME,
796 chunks: alloc::vec![felts(10)]
797 }],
798 alloc::vec![WireEntry::Data {
799 frame: deferred_chunks_frame(2),
800 chunks: alloc::vec![felts(10)]
801 }],
802 alloc::vec![WireEntry::Join {
803 frame: deferred_chunks_frame(1),
804 lhs: 0,
805 rhs: 0
806 }],
807 alloc::vec![WireEntry::Join { frame: malformed_and, lhs: 0, rhs: 0 }],
808 alloc::vec![WireEntry::Join {
809 frame: DEFERRED_AND_FRAME,
810 lhs: 0,
811 rhs: 1
812 }],
813 alloc::vec![
814 leaf(),
815 leaf(),
816 WireEntry::Join {
817 frame: DEFERRED_AND_FRAME,
818 lhs: 1,
819 rhs: 2
820 }
821 ],
822 alloc::vec![leaf(), other()],
823 alloc::vec![
824 leaf(),
825 other(),
826 WireEntry::Join {
827 frame: DEFERRED_AND_FRAME,
828 lhs: 2,
829 rhs: 1
830 }
831 ],
832 alloc::vec![
833 WireEntry::Join {
834 frame: DEFERRED_AND_FRAME,
835 lhs: 0,
836 rhs: 2
837 },
838 WireEntry::Join {
839 frame: DEFERRED_AND_FRAME,
840 lhs: 0,
841 rhs: 0
842 },
843 ],
844 alloc::vec![WireEntry::PairList {
845 frame: frame(1),
846 pairs: alloc::vec![(0, 1)]
847 }],
848 ];
849 for entries in graph_invalid {
850 let bytes = encoded_entries(&entries);
851 let witness = PrecompileWitness::from_entries(entries).unwrap();
852 assert_eq!(PrecompileWitness::read_from_bytes(&bytes).unwrap(), witness);
853 assert!(matches!(
854 prepare_framework(witness),
855 Err(PreparationError::Integrity(IntegrityError::InvalidStructure))
856 ));
857 }
858 }
859
860 #[test]
861 fn export_omits_unreachable_state_and_retains_logged_true() {
862 let mut empty = DeferredState::default();
863 let value = empty.register(Node::chunks(alloc::vec![felts(10)]).unwrap()).unwrap();
864 let witness = empty.witness_for(value, &framework_limits()).unwrap();
866 assert_eq!(
867 witness
868 .prepare(Arc::new(PrecompileRegistry::new()), &framework_limits())
869 .unwrap()
870 .root(),
871 value
872 );
873 assert!(matches!(
874 empty.witness_for(value, &PrecompileLimits::new(0)),
875 Err(PrecompileError::Limit(PrecompileLimitError::Elements { max: 0, .. }))
876 ));
877 assert_eq!(empty.root(), TRUE_DIGEST);
878 assert!(empty.get_node(&value).is_some());
879 for root in [TRUE_DIGEST, Word::new([Felt::from_u32(99); 4])] {
880 assert!(empty.witness_for(root, &framework_limits()).is_err());
881 }
882 assert!(empty.into_witness(&framework_limits()).unwrap().is_none());
883 let mut state = DeferredState::default();
884 state.register(Node::chunks(alloc::vec![felts(10)]).unwrap()).unwrap();
885 state.log_statement(TRUE_DIGEST).unwrap();
886 let root = state.root();
887 let expected_elements = Node::and(TRUE_DIGEST, TRUE_DIGEST).felt_len() as u64;
888 let verification_limits = PrecompileLimits::new(expected_elements);
889 let registry = Arc::new(PrecompileRegistry::new());
890 let witness = state.clone().into_witness(&verification_limits).unwrap().unwrap();
891 assert_eq!(
892 witness.entries(),
893 &[WireEntry::Join {
894 frame: DEFERRED_AND_FRAME,
895 lhs: 0,
896 rhs: 0
897 }]
898 );
899 let prepared = witness
900 .prepare(registry, &verification_limits)
901 .expect("exported work must prepare under the same verification policy");
902 assert_eq!(prepared.root(), root);
903 assert_eq!(prepared.work().nodes(), 1);
904 assert_eq!(prepared.work().elements(), expected_elements);
905 prepared.evaluate().expect("prepared evaluation is semantic only");
906
907 let too_small = verification_limits.with_max_elements(expected_elements - 1);
908 assert!(matches!(
909 state.into_witness(&too_small),
910 Err(PrecompileError::Limit(PrecompileLimitError::Elements {
911 actual,
912 max,
913 })) if actual == expected_elements && max == expected_elements - 1
914 ));
915 }
916
917 #[test]
918 fn deep_shared_graph_exports_and_decodes_without_expansion() {
919 let mut state = DeferredState::default();
920 let mut statement = TRUE_DIGEST;
921 for _ in 0..4_096 {
922 statement = state.register(Node::and(statement, statement)).unwrap();
923 }
924 state.log_statement(statement).unwrap();
925 let root = state.root();
926 let witness = state.into_witness(&framework_limits()).unwrap().unwrap();
927 assert_eq!(witness.entries().len(), 4_097);
928 assert_eq!(PrecompileWitness::from_entries(witness.entries().to_vec()).unwrap(), witness);
929 assert_eq!(PrecompileWitness::read_from_bytes(&witness.to_bytes()).unwrap(), witness);
930 assert_eq!(prepare_framework(witness).unwrap().root(), root);
931 }
932
933 #[test]
934 fn standalone_witness_rejects_legacy_tag_encoding_before_body() {
935 const LEGACY_WITNESS: &[u8] = &[
937 1, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
944 let mut reader = SliceReader::new(LEGACY_WITNESS);
945 assert_eq!(
946 PrecompileWitness::read_from(&mut reader),
947 Err(DeserializationError::InvalidValue(
948 "unsupported precompile witness version 1 (expected 2)".into()
949 ))
950 );
951 assert_eq!(reader.read_slice(LEGACY_WITNESS.len() - 1).unwrap(), &LEGACY_WITNESS[1..]);
952 }
953
954 #[test]
955 fn standalone_witness_rejects_unsupported_versions_and_trailing_bytes() {
956 let witness = PrecompileWitness::from_entries(alloc::vec![WireEntry::Join {
957 frame: DEFERRED_AND_FRAME,
958 lhs: 0,
959 rhs: 0
960 }])
961 .unwrap();
962 let bytes = witness.to_bytes();
963 assert_eq!(bytes[0], 2);
964 for version in [0, 1, PrecompileWitness::WIRE_VERSION + 1] {
965 let mut unsupported = bytes.clone();
966 unsupported[0] = version;
967 assert_eq!(
968 PrecompileWitness::read_from_bytes(&unsupported),
969 Err(DeserializationError::InvalidValue(format!(
970 "unsupported precompile witness version {version} (expected 2)"
971 )))
972 );
973 }
974 let mut trailing = bytes;
975 trailing.push(0);
976 assert!(PrecompileWitness::read_from_bytes(&trailing).is_err());
977 }
978
979 #[test]
980 fn decoder_rejects_malformed_frame_words_for_every_entry_shape() {
981 let entries = [
982 WireEntry::Data {
983 frame: frame(1),
984 chunks: alloc::vec![felts(10)],
985 },
986 WireEntry::Join { frame: frame(1), lhs: 0, rhs: 0 },
987 WireEntry::PairList {
988 frame: frame(1),
989 pairs: alloc::vec![(0, 0)],
990 },
991 ];
992 let mut invalid_frames = alloc::vec![Word::default()];
993 for index in 0..EidosFrame::FELT_LEN {
994 let mut elements = frame(1).as_word().into_elements();
995 elements[index] = Felt::new_unchecked(u32::MAX as u64 + 1);
996 invalid_frames.push(Word::new(elements));
997 }
998 for entry in entries {
999 let bytes = encoded_entries(&[entry]);
1000 for invalid_frame in &invalid_frames {
1001 let mut malformed = bytes.clone();
1002 malformed[3..3 + Word::min_serialized_size()]
1004 .copy_from_slice(&invalid_frame.to_bytes());
1005 assert_eq!(
1006 PrecompileWitness::read_from_bytes(&malformed),
1007 Err(DeserializationError::InvalidValue("invalid deferred Eidos frame".into()))
1008 );
1009 }
1010 }
1011 }
1012
1013 #[test]
1014 fn decoder_rejects_overlong_entry_and_payload_lengths() {
1015 let entries = [
1016 WireEntry::Data {
1017 frame: deferred_chunks_frame(1),
1018 chunks: alloc::vec![felts(10)],
1019 },
1020 WireEntry::PairList {
1021 frame: frame(1),
1022 pairs: alloc::vec![(0, 0)],
1023 },
1024 ];
1025 for entry in entries {
1026 let witness = PrecompileWitness::from_entries(alloc::vec![entry]).unwrap();
1027 let bytes = witness.to_bytes();
1028 for offset in [1, 3 + Word::min_serialized_size()] {
1030 assert_eq!(bytes[offset], 3, "one uses the one-byte vint64 encoding");
1031 let mut noncanonical = bytes[..offset].to_vec();
1032 noncanonical.extend_from_slice(&[6, 0]);
1033 noncanonical.extend_from_slice(&bytes[offset + 1..]);
1034 assert!(PrecompileWitness::read_from_bytes(&noncanonical).is_err());
1035 }
1036 }
1037 }
1038
1039 #[test]
1040 fn singleton_vector_decodes_with_exact_byte_budget() {
1041 let witness = PrecompileWitness::from_entries(alloc::vec![WireEntry::Join {
1042 frame: DEFERRED_AND_FRAME,
1043 lhs: 0,
1044 rhs: 0
1045 }])
1046 .unwrap();
1047 assert_eq!(PrecompileWitness::min_serialized_size(), 2);
1048 assert!(PrecompileWitness::min_serialized_size() <= witness.to_bytes().len());
1049 let witnesses = alloc::vec![witness.clone(), witness];
1050 let bytes = witnesses.to_bytes();
1051 assert_eq!(
1052 Vec::<PrecompileWitness>::read_from_bytes_with_budget(&bytes, bytes.len()).unwrap(),
1053 witnesses
1054 );
1055 }
1056
1057 #[test]
1058 fn wire_element_budget_accepts_exact_limit_and_rejects_one_more() {
1059 let mut remaining = MAX_DEFERRED_WIRE_ELEMENTS;
1060 reserve_wire_elements(&mut remaining, MAX_DEFERRED_WIRE_ELEMENTS).unwrap();
1061 assert_eq!(remaining, 0);
1062 assert!(reserve_wire_elements(&mut remaining, 1).is_err());
1063 let mut overflow_budget = MAX_DEFERRED_WIRE_ELEMENTS;
1064 assert!(reserve_wire_payload(&mut overflow_budget, usize::MAX).is_err());
1065 }
1066
1067 #[test]
1068 fn in_memory_entries_enforce_the_wire_element_limit() {
1069 let join = WireEntry::Join {
1070 frame: DEFERRED_AND_FRAME,
1071 lhs: 0,
1072 rhs: 0,
1073 };
1074 let mut entries = alloc::vec![join; MAX_DEFERRED_WIRE_ELEMENTS / (EidosFrame::FELT_LEN + Node::DATA_CHUNK_FELT_LEN)];
1075 entries.push(WireEntry::Data {
1077 frame: deferred_chunks_frame(1),
1078 chunks: alloc::vec![felts(10)],
1079 });
1080 assert!(matches!(
1081 PrecompileWitness::from_entries(entries),
1082 Err(IntegrityError::DeferredStateTooLarge { .. })
1083 ));
1084 }
1085
1086 #[test]
1087 fn decoder_rejects_oversized_and_truncated_lengths_before_payload_allocation() {
1088 for count in [MAX_WIRE_ENTRIES, MAX_WIRE_ENTRIES + 1, usize::MAX] {
1089 let mut bytes = alloc::vec![PrecompileWitness::WIRE_VERSION];
1090 bytes.write_usize(count);
1091 assert!(PrecompileWitness::read_from_bytes(&bytes).is_err());
1092 }
1093 for discriminant in [0, 2] {
1094 let mut bytes = alloc::vec![PrecompileWitness::WIRE_VERSION];
1095 bytes.write_usize(1);
1096 bytes.write_u8(discriminant);
1097 frame(1).as_word().write_into(&mut bytes);
1098 bytes.write_usize(usize::MAX);
1099 assert!(PrecompileWitness::read_from_bytes(&bytes).is_err());
1100 }
1101 }
1102}
1103
1104#[cfg(any(test, feature = "arbitrary"))]
1108mod wire_arbitrary {
1109 use alloc::vec::Vec;
1110
1111 use miden_crypto::hash::eidos::{EidosDomain, EidosFrame};
1112 use proptest::prelude::*;
1113
1114 use super::{PrecompileWitness, WireEntry};
1115 use crate::program::domain::{
1116 CurvePrecompileDomain, Keccak256PrecompileDomain, Sha256PrecompileDomain,
1117 Sha512PrecompileDomain, Uint256PrecompileDomain,
1118 };
1119
1120 impl Arbitrary for PrecompileWitness {
1121 type Parameters = ();
1122 type Strategy = BoxedStrategy<Self>;
1123
1124 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
1125 (1usize..=4)
1138 .prop_flat_map(|len| {
1139 (
1140 proptest::collection::vec(
1141 (
1142 prop_oneof![
1143 Just(Keccak256PrecompileDomain::TAG),
1144 Just(Uint256PrecompileDomain::TAG),
1145 Just(CurvePrecompileDomain::TAG),
1146 Just(Sha256PrecompileDomain::TAG),
1147 Just(Sha512PrecompileDomain::TAG),
1148 ],
1149 any::<[u32; 3]>(),
1150 )
1151 .prop_map(|(domain, params)| EidosFrame::new(domain, params)),
1152 len,
1153 ),
1154 proptest::collection::vec(0u8..5, len),
1155 proptest::collection::vec(
1156 proptest::collection::vec(any::<crate::Felt>(), 8),
1157 1..=3,
1158 ),
1159 )
1160 })
1161 .prop_map(|(frames, kinds, data_chunks)| {
1162 let n = frames.len();
1168 let mut entries = Vec::with_capacity(n);
1169 for (i, (frame, kind)) in frames.into_iter().zip(kinds).enumerate() {
1170 if i == 0 && kind == 4 && n >= 2 {
1171 let chunks = data_chunks
1172 .iter()
1173 .map(|chunk| chunk.clone().try_into().expect("8 felts per chunk"))
1174 .collect();
1175 entries.push(WireEntry::Data { frame, chunks });
1176 continue;
1177 }
1178 let prev = i as u32; match kind % 4 {
1180 0 => entries.push(WireEntry::Join { frame, lhs: 0, rhs: prev }),
1181 1 if i >= 2 => {
1183 entries.push(WireEntry::Join { frame, lhs: prev - 1, rhs: prev });
1184 },
1185 1 => entries.push(WireEntry::Join { frame, lhs: 0, rhs: prev }),
1186 2 => entries.push(WireEntry::PairList {
1188 frame,
1189 pairs: alloc::vec![(0, prev), (prev, prev)],
1190 }),
1191 _ => {
1192 entries.push(WireEntry::PairList {
1193 frame,
1194 pairs: alloc::vec![(0, prev)],
1195 });
1196 },
1197 }
1198 }
1199 PrecompileWitness::from_entries(entries)
1200 .expect("a mixed-kind witness DAG is canonical")
1201 })
1202 .boxed()
1203 }
1204 }
1205}