1use alloc::{
8 collections::{BTreeMap, BTreeSet},
9 format,
10 sync::Arc,
11 vec::Vec,
12};
13
14use super::{
15 DataChunk, DeferredState, Digest, MAX_DEFERRED_ELEMENTS, Node, NodeType, PrecompileError,
16 PrecompileRegistry, TRUE_DIGEST, Tag, node::hash_payload,
17};
18use crate::{
19 Felt, ZERO,
20 serde::{
21 BudgetedReader, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
22 SliceReader, validate_bounded_len,
23 },
24};
25
26const TRUE_INDEX: u32 = 0;
31
32const MAX_WIRE_ENTRIES: usize = MAX_DEFERRED_ELEMENTS / Tag::FELT_LEN;
33
34fn reserve_wire_elements(
35 remaining_elements: &mut usize,
36 requested_elements: usize,
37) -> Result<(), DeserializationError> {
38 *remaining_elements = remaining_elements.checked_sub(requested_elements).ok_or_else(|| {
39 DeserializationError::InvalidValue(format!(
40 "deferred wire exceeds the {MAX_DEFERRED_ELEMENTS} element limit"
41 ))
42 })?;
43 Ok(())
44}
45
46fn reserve_wire_payload(
47 remaining_elements: &mut usize,
48 payload_count: usize,
49) -> Result<(), DeserializationError> {
50 let payload_elements =
51 payload_count.checked_mul(Node::DATA_CHUNK_FELT_LEN).ok_or_else(|| {
52 DeserializationError::InvalidValue("deferred wire element count overflow".into())
53 })?;
54 reserve_wire_elements(remaining_elements, payload_elements)
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum WireEntry {
67 Data { tag: Tag, chunks: Vec<DataChunk> },
72 Join { tag: Tag, lhs: u32, rhs: u32 },
74 PairList { tag: Tag, pairs: Vec<(u32, u32)> },
76}
77
78impl WireEntry {
79 fn min_serialized_size() -> usize {
81 1 + Tag::min_serialized_size() + 2 * u32::min_serialized_size()
82 }
83
84 pub fn tag(&self) -> Tag {
86 match self {
87 Self::Data { tag, .. } | Self::Join { tag, .. } | Self::PairList { tag, .. } => *tag,
88 }
89 }
90
91 fn children(&self) -> impl DoubleEndedIterator<Item = u32> + '_ {
92 let (join, pairs) = match self {
93 Self::Join { lhs, rhs, .. } => (Some([*lhs, *rhs]), &[][..]),
94 Self::PairList { pairs, .. } => (None, pairs.as_slice()),
95 Self::Data { .. } => (None, &[][..]),
96 };
97 join.into_iter()
98 .flatten()
99 .chain(pairs.iter().flat_map(|&(lhs, rhs)| [lhs, rhs]))
100 }
101
102 pub fn digest(&self, digests: &[Digest]) -> Result<Digest, IntegrityError> {
107 if digests.first() != Some(&TRUE_DIGEST) {
108 return Err(IntegrityError::InvalidStructure);
109 }
110 let tag = self.tag();
111 if tag.is_framework_reserved()
112 && !matches!(self, Self::Data { tag, .. } if *tag == Tag::CHUNKS)
113 && !matches!(self, Self::Join { tag, .. } if *tag == Tag::AND)
114 {
115 return Err(IntegrityError::InvalidStructure);
116 }
117 if self.children().any(|index| index as usize >= digests.len()) {
118 return Err(IntegrityError::InvalidStructure);
119 }
120 let pair = |lhs: u32, rhs: u32| {
121 let lhs = digests[lhs as usize].into_elements();
122 let rhs = digests[rhs as usize].into_elements();
123 [lhs[0], lhs[1], lhs[2], lhs[3], rhs[0], rhs[1], rhs[2], rhs[3]]
124 };
125 match self {
126 Self::Data { chunks, .. } if !chunks.is_empty() => {
127 Ok(hash_payload(tag, chunks.iter().copied()))
128 },
129 Self::Join { lhs, rhs, .. } => Ok(hash_payload(tag, [pair(*lhs, *rhs)])),
130 Self::PairList { pairs, .. } if !pairs.is_empty() => {
131 Ok(hash_payload(tag, pairs.iter().map(|&(lhs, rhs)| pair(lhs, rhs))))
132 },
133 _ => Err(IntegrityError::InvalidStructure),
134 }
135 }
136}
137
138#[derive(Debug, Clone, PartialEq, Eq)]
147pub struct PrecompileWitness {
148 entries: Vec<WireEntry>,
149 root: Digest,
150}
151
152impl PrecompileWitness {
153 pub const WIRE_VERSION: u8 = 1;
155
156 pub fn from_entries(entries: Vec<WireEntry>) -> Result<Self, IntegrityError> {
158 let mut wire = Self { entries, root: TRUE_DIGEST };
159 wire.root = wire.validate_structure()?;
160 if wire.root == TRUE_DIGEST {
161 return Err(IntegrityError::InvalidStructure);
162 }
163 Ok(wire)
164 }
165
166 pub fn root_unchecked(&self) -> Digest {
170 self.root
171 }
172
173 pub fn compute_root(
184 &self,
185 registry: Arc<PrecompileRegistry>,
186 ) -> Result<Digest, PrecompileError> {
187 if self.entries.is_empty() {
188 return Err(PrecompileError::InvalidNode);
189 }
190 let mut state = DeferredState::new(registry)?;
191 let mut digests = Vec::with_capacity(self.entries.len() + 1);
192 digests.push(TRUE_DIGEST);
193 for entry in &self.entries {
194 let child = |index: u32| {
195 digests.get(index as usize).copied().ok_or(PrecompileError::InvalidNode)
196 };
197 let node = match entry {
198 WireEntry::Data { tag, chunks } => {
199 if *tag == Tag::CHUNKS {
200 Node::chunks(chunks.clone())?
201 } else {
202 Node::try_data(*tag, chunks.clone())?
203 }
204 },
205 WireEntry::Join { tag, lhs, rhs } => {
206 let (lhs, rhs) = (child(*lhs)?, child(*rhs)?);
207 if *tag == Tag::AND {
208 Node::and(lhs, rhs)
209 } else {
210 Node::join(*tag, lhs, rhs)?
211 }
212 },
213 WireEntry::PairList { tag, pairs } => {
214 let pairs = pairs
215 .iter()
216 .map(|&(lhs, rhs)| Ok((child(lhs)?, child(rhs)?)))
217 .collect::<Result<Vec<_>, PrecompileError>>()?;
218 Node::try_pair_list(*tag, pairs)?
219 },
220 };
221 digests.push(state.register(node)?);
222 }
223 let root = *digests.last().expect("TRUE seeds the digest table");
224 if state.evaluate_digest(root)? != TRUE_DIGEST {
225 return Err(PrecompileError::AssertionFailed);
226 }
227 Ok(root)
228 }
229
230 pub fn entries(&self) -> &[WireEntry] {
232 &self.entries
233 }
234
235 fn validate_structure(&self) -> Result<Digest, IntegrityError> {
236 self.validate_element_limit()?;
237 let mut digests = Vec::with_capacity(self.entries.len() + 1);
238 let mut seen_digests = BTreeSet::new();
239 digests.push(TRUE_DIGEST);
240 seen_digests.insert(TRUE_DIGEST);
241 for entry in &self.entries {
242 let digest = entry.digest(&digests)?;
243 if !seen_digests.insert(digest) {
244 return Err(IntegrityError::InvalidStructure);
245 }
246 digests.push(digest);
247 }
248
249 let mut seen = alloc::vec![false; digests.len()];
252 let mut pending = alloc::vec![(self.entries.len(), false)];
253 let mut next_index = 1;
254 while let Some((index, emit)) = pending.pop() {
255 if index == 0 {
256 continue;
257 }
258 if emit {
259 if index != next_index {
260 return Err(IntegrityError::InvalidStructure);
261 }
262 next_index += 1;
263 } else if !core::mem::replace(&mut seen[index], true) {
264 pending.push((index, true));
265 pending.extend(
266 self.entries[index - 1].children().rev().map(|child| (child as usize, false)),
267 );
268 }
269 }
270 if next_index != digests.len() {
271 return Err(IntegrityError::InvalidStructure);
272 }
273 Ok(*digests.last().expect("TRUE seeds the digest table"))
274 }
275
276 pub(crate) fn from_state(state: &DeferredState) -> Result<Self, IntegrityError> {
278 let mut build = WireEncoder::default();
279 build.visit_state_digest(state, state.root())?;
280 Ok(Self {
281 entries: build.entries,
282 root: state.root(),
283 })
284 }
285
286 fn validate_element_limit(&self) -> Result<(), IntegrityError> {
287 let mut remaining_elements = MAX_DEFERRED_ELEMENTS;
288 for entry in &self.entries {
289 let payload_count = match entry {
290 WireEntry::Data { chunks, .. } => chunks.len(),
291 WireEntry::Join { .. } => 1,
292 WireEntry::PairList { pairs, .. } => pairs.len(),
293 };
294 let payload_elements = payload_count
295 .checked_mul(Node::DATA_CHUNK_FELT_LEN)
296 .and_then(|elements| Tag::FELT_LEN.checked_add(elements))
297 .ok_or(IntegrityError::InvalidStructure)?;
298 remaining_elements = remaining_elements.checked_sub(payload_elements).ok_or(
299 IntegrityError::DeferredStateTooLarge {
300 num_elements: payload_elements,
301 max: remaining_elements,
302 },
303 )?;
304 }
305 Ok(())
306 }
307}
308
309#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
314pub enum IntegrityError {
315 #[error("invalid or non-canonical precompile witness structure")]
317 InvalidStructure,
318 #[error("deferred insertion requires {num_elements} elements but only {max} remain")]
320 DeferredStateTooLarge { num_elements: usize, max: usize },
321}
322
323#[derive(Default)]
328struct WireEncoder {
329 seen: BTreeSet<Digest>,
330 by_digest: BTreeMap<Digest, u32>,
331 entries: Vec<WireEntry>,
332}
333
334impl WireEncoder {
335 fn visit_state_digest(
336 &mut self,
337 state: &DeferredState,
338 digest: Digest,
339 ) -> Result<(), IntegrityError> {
340 let mut pending = Vec::new();
341 pending.push(WireEncodeStep::Visit(digest));
342
343 while let Some(step) = pending.pop() {
344 match step {
345 WireEncodeStep::Visit(digest) => {
346 self.schedule_digest(state, digest, &mut pending)?
347 },
348 WireEncodeStep::Emit(digest) => {
349 let entry = self.entry_for_digest(state, digest)?;
350 self.push_entry(digest, entry)?;
351 },
352 }
353 }
354
355 Ok(())
356 }
357
358 fn schedule_digest(
359 &mut self,
360 state: &DeferredState,
361 digest: Digest,
362 pending: &mut Vec<WireEncodeStep>,
363 ) -> Result<(), IntegrityError> {
364 if digest == TRUE_DIGEST || !self.seen.insert(digest) {
365 return Ok(());
366 }
367
368 let node = self.validated_node(state, digest)?;
369 pending.push(WireEncodeStep::Emit(digest));
370
371 match self.node_type(state, node)? {
372 NodeType::Data => {},
373 NodeType::Join => {
374 let (lhs, rhs) =
375 node.payload().as_join().map_err(|_| IntegrityError::InvalidStructure)?;
376 pending.push(WireEncodeStep::Visit(rhs));
377 pending.push(WireEncodeStep::Visit(lhs));
378 },
379 NodeType::PairList => {
380 let pairs =
381 node.payload().as_pair_list().map_err(|_| IntegrityError::InvalidStructure)?;
382 for (lhs, rhs) in pairs.iter().rev() {
383 pending.push(WireEncodeStep::Visit(*rhs));
384 pending.push(WireEncodeStep::Visit(*lhs));
385 }
386 },
387 NodeType::True => return Err(IntegrityError::InvalidStructure),
388 };
389
390 Ok(())
391 }
392
393 fn entry_for_digest(
394 &self,
395 state: &DeferredState,
396 digest: Digest,
397 ) -> Result<WireEntry, IntegrityError> {
398 let node = self.validated_node(state, digest)?;
399
400 Ok(match self.node_type(state, node)? {
401 NodeType::Data => WireEntry::Data {
402 tag: node.tag(),
403 chunks: node
404 .payload()
405 .as_data()
406 .map_err(|_| IntegrityError::InvalidStructure)?
407 .to_vec(),
408 },
409 NodeType::Join => {
410 let (lhs, rhs) =
411 node.payload().as_join().map_err(|_| IntegrityError::InvalidStructure)?;
412 let lhs = self.index_for(lhs)?;
413 let rhs = self.index_for(rhs)?;
414 WireEntry::Join { tag: node.tag(), lhs, rhs }
415 },
416 NodeType::PairList => {
417 let pairs =
418 node.payload().as_pair_list().map_err(|_| IntegrityError::InvalidStructure)?;
419 let pairs = pairs
420 .iter()
421 .map(|(lhs, rhs)| Ok((self.index_for(*lhs)?, self.index_for(*rhs)?)))
422 .collect::<Result<Vec<_>, IntegrityError>>()?;
423 WireEntry::PairList { tag: node.tag(), pairs }
424 },
425 NodeType::True => return Err(IntegrityError::InvalidStructure),
426 })
427 }
428
429 fn validated_node<'a>(
430 &self,
431 state: &'a DeferredState,
432 digest: Digest,
433 ) -> Result<&'a Node, IntegrityError> {
434 let node = state.get_node(&digest).ok_or(IntegrityError::InvalidStructure)?;
435 self.node_type(state, node)?
436 .validate_node(node)
437 .map_err(|_| IntegrityError::InvalidStructure)?;
438 Ok(node)
439 }
440
441 fn node_type(&self, state: &DeferredState, node: &Node) -> Result<NodeType, IntegrityError> {
442 state
443 .registry()
444 .decode_node_type(node.tag())
445 .map_err(|_| IntegrityError::InvalidStructure)
446 }
447
448 fn index_for(&self, digest: Digest) -> Result<u32, IntegrityError> {
449 if digest == TRUE_DIGEST {
450 return Ok(TRUE_INDEX);
451 }
452 self.by_digest.get(&digest).copied().ok_or(IntegrityError::InvalidStructure)
453 }
454
455 fn push_entry(&mut self, digest: Digest, entry: WireEntry) -> Result<(), IntegrityError> {
456 let next_index =
457 self.entries.len().checked_add(1).ok_or(IntegrityError::InvalidStructure)?;
458 let next_index = u32::try_from(next_index).map_err(|_| IntegrityError::InvalidStructure)?;
459 self.entries.push(entry);
460 self.by_digest.insert(digest, next_index);
461 Ok(())
462 }
463}
464
465enum WireEncodeStep {
466 Visit(Digest),
467 Emit(Digest),
468}
469
470impl Serializable for WireEntry {
474 fn write_into<W: ByteWriter>(&self, target: &mut W) {
475 match self {
476 Self::Data { tag, chunks } => {
477 target.write_u8(0);
478 tag.write_into(target);
479 target.write_usize(chunks.len());
480 for chunk in chunks {
481 for felt in chunk {
482 felt.write_into(target);
483 }
484 }
485 },
486 Self::Join { tag, lhs, rhs } => {
487 target.write_u8(1);
488 tag.write_into(target);
489 target.write_u32(*lhs);
490 target.write_u32(*rhs);
491 },
492 Self::PairList { tag, pairs } => {
493 target.write_u8(2);
494 tag.write_into(target);
495 target.write_usize(pairs.len());
496 for (lhs, rhs) in pairs {
497 target.write_u32(*lhs);
498 target.write_u32(*rhs);
499 }
500 },
501 }
502 }
503}
504
505struct WirePair((u32, u32));
506
507impl Deserializable for WirePair {
508 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
509 Ok(Self((source.read_u32()?, source.read_u32()?)))
510 }
511
512 fn min_serialized_size() -> usize {
513 u32::min_serialized_size() * 2
514 }
515}
516
517struct WireDataChunk(DataChunk);
518
519impl Deserializable for WireDataChunk {
520 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
521 let mut chunk = [ZERO; Node::DATA_CHUNK_FELT_LEN];
522 for felt in &mut chunk {
523 *felt = Felt::read_from(source)?;
524 }
525 Ok(Self(chunk))
526 }
527
528 fn min_serialized_size() -> usize {
529 Node::DATA_CHUNK_FELT_LEN * Felt::min_serialized_size()
530 }
531}
532
533impl Serializable for PrecompileWitness {
534 fn write_into<W: ByteWriter>(&self, target: &mut W) {
535 target.write_u8(Self::WIRE_VERSION);
536 target.write_usize(self.entries.len());
537 for entry in &self.entries {
538 entry.write_into(target);
539 }
540 }
541}
542
543impl Deserializable for PrecompileWitness {
544 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
545 let version = source.read_u8()?;
546 if version != Self::WIRE_VERSION {
547 return Err(DeserializationError::InvalidValue(format!(
548 "unsupported precompile witness version {version} (expected {})",
549 Self::WIRE_VERSION
550 )));
551 }
552 let entry_count =
553 read_len(source, "precompile witness entry", WireEntry::min_serialized_size())?;
554 if entry_count == 0 || entry_count > MAX_WIRE_ENTRIES {
555 return Err(DeserializationError::InvalidValue(format!(
556 "precompile witness contains {entry_count} entries, expected 1..={MAX_WIRE_ENTRIES}"
557 )));
558 }
559
560 let mut remaining_elements = MAX_DEFERRED_ELEMENTS;
561 let mut entries = Vec::with_capacity(entry_count);
562 for _ in 0..entry_count {
563 entries.push(read_wire_entry(source, &mut remaining_elements)?);
564 }
565 Self::from_entries(entries).map_err(|error| {
566 DeserializationError::InvalidValue(format!("invalid precompile witness: {error}"))
567 })
568 }
569
570 fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
571 let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
572 let wire = Self::read_from(&mut reader)?;
573 if reader.has_more_bytes() {
574 return Err(DeserializationError::InvalidValue(
575 "trailing bytes after deferred witness".into(),
576 ));
577 }
578 Ok(wire)
579 }
580
581 fn min_serialized_size() -> usize {
582 1 + usize::min_serialized_size() + WireEntry::min_serialized_size()
583 }
584}
585
586fn read_len<R: ByteReader>(
588 source: &mut R,
589 label: &str,
590 min_element_size: usize,
591) -> Result<usize, DeserializationError> {
592 let encoded_len = source.peek_u8()?.trailing_zeros() as usize + 1;
593 let len = source.read_usize()?;
594 if encoded_len != len.get_size_hint() {
596 return Err(DeserializationError::InvalidValue(format!("noncanonical {label} length")));
597 }
598 validate_bounded_len(source, label, len, min_element_size)?;
599 Ok(len)
600}
601
602fn read_wire_entry<R: ByteReader>(
603 source: &mut R,
604 remaining_elements: &mut usize,
605) -> Result<WireEntry, DeserializationError> {
606 let discriminant = source.read_u8()?;
607 match discriminant {
608 0 => {
609 reserve_wire_elements(remaining_elements, Tag::FELT_LEN)?;
610 let tag = Tag::read_from(source)?;
611 let chunk_count = read_len(source, "data chunk", WireDataChunk::min_serialized_size())?;
612 reserve_wire_payload(remaining_elements, chunk_count)?;
613 let chunks = source
614 .read_many_iter::<WireDataChunk>(chunk_count)?
615 .map(|chunk| chunk.map(|chunk| chunk.0))
616 .collect::<Result<_, _>>()?;
617 Ok(WireEntry::Data { tag, chunks })
618 },
619 1 => {
620 reserve_wire_elements(remaining_elements, Tag::FELT_LEN + Node::DATA_CHUNK_FELT_LEN)?;
621 let tag = Tag::read_from(source)?;
622 let lhs = source.read_u32()?;
623 let rhs = source.read_u32()?;
624 Ok(WireEntry::Join { tag, lhs, rhs })
625 },
626 2 => {
627 reserve_wire_elements(remaining_elements, Tag::FELT_LEN)?;
628 let tag = Tag::read_from(source)?;
629 let pair_count = read_len(source, "child pair", WirePair::min_serialized_size())?;
630 reserve_wire_payload(remaining_elements, pair_count)?;
631 let pairs = source
632 .read_many_iter::<WirePair>(pair_count)?
633 .map(|pair| pair.map(|pair| pair.0))
634 .collect::<Result<_, _>>()?;
635 Ok(WireEntry::PairList { tag, pairs })
636 },
637 other => Err(DeserializationError::InvalidValue(format!(
638 "invalid deferred wire entry discriminant: {other}"
639 ))),
640 }
641}
642
643#[cfg(test)]
644mod tests {
645 use super::*;
646
647 fn felts(seed: u64) -> DataChunk {
648 core::array::from_fn(|i| Felt::new_unchecked(seed + i as u64))
649 }
650
651 fn tag(seed: u64) -> Tag {
652 Tag::from_word([Felt::new_unchecked(seed + 100), ZERO, ZERO, ZERO])
653 }
654
655 fn encoded_entries(entries: &[WireEntry]) -> Vec<u8> {
656 let mut bytes = alloc::vec![PrecompileWitness::WIRE_VERSION];
657 bytes.write_usize(entries.len());
658 for entry in entries {
659 entry.write_into(&mut bytes);
660 }
661 bytes
662 }
663
664 #[test]
665 fn portable_structure_is_independent_of_operation_support() {
666 let entries = alloc::vec![
667 WireEntry::Data {
668 tag: tag(1),
669 chunks: alloc::vec![felts(10)]
670 },
671 WireEntry::Data {
672 tag: tag(2),
673 chunks: alloc::vec![felts(20), felts(30)]
674 },
675 WireEntry::Join { tag: tag(3), lhs: 1, rhs: 1 },
676 WireEntry::PairList {
677 tag: tag(4),
678 pairs: alloc::vec![(1, 2), (3, 3)]
679 },
680 ];
681 let witness = PrecompileWitness::from_entries(entries).unwrap();
682 let left = Node::value(tag(1), felts(10)).unwrap().digest();
683 let right = Node::try_data(tag(2), alloc::vec![felts(20), felts(30)]).unwrap().digest();
684 let claim = Node::join(tag(3), left, left).unwrap().digest();
685 assert_eq!(
686 witness.root_unchecked(),
687 Node::try_pair_list(tag(4), alloc::vec![(left, right), (claim, claim)])
688 .unwrap()
689 .digest()
690 );
691 assert_eq!(PrecompileWitness::read_from_bytes(&witness.to_bytes()).unwrap(), witness);
692 }
693
694 #[test]
695 fn portable_structure_rejects_noncanonical_and_malformed_graphs() {
696 let leaf = || WireEntry::Data {
697 tag: tag(1),
698 chunks: alloc::vec![felts(10)],
699 };
700 let other = || WireEntry::Data {
701 tag: tag(1),
702 chunks: alloc::vec![felts(20)],
703 };
704 let malformed_and = Tag::from_word([Tag::AND.id(), Felt::new_unchecked(1), ZERO, ZERO]);
705 let cases = [
706 Vec::new(),
707 alloc::vec![WireEntry::Data { tag: Tag::CHUNKS, chunks: Vec::new() }],
708 alloc::vec![WireEntry::PairList { tag: tag(1), pairs: Vec::new() }],
709 alloc::vec![WireEntry::Data {
710 tag: Tag::TRUE,
711 chunks: alloc::vec![felts(10)]
712 }],
713 alloc::vec![WireEntry::Join { tag: Tag::CHUNKS, lhs: 0, rhs: 0 }],
714 alloc::vec![WireEntry::Join { tag: malformed_and, lhs: 0, rhs: 0 }],
715 alloc::vec![WireEntry::Join { tag: Tag::AND, lhs: 0, rhs: 1 }],
716 alloc::vec![leaf(), leaf(), WireEntry::Join { tag: Tag::AND, lhs: 1, rhs: 2 }],
717 alloc::vec![leaf(), other()],
718 alloc::vec![leaf(), other(), WireEntry::Join { tag: Tag::AND, lhs: 2, rhs: 1 }],
719 ];
720 for entries in cases {
721 let bytes = encoded_entries(&entries);
722 assert!(PrecompileWitness::from_entries(entries).is_err());
723 assert!(PrecompileWitness::read_from_bytes(&bytes).is_err());
724 }
725 }
726
727 #[test]
728 fn compute_root_ignores_cached_root_and_checks_entries() {
729 let mut witness = PrecompileWitness::from_entries(alloc::vec![WireEntry::Join {
730 tag: Tag::AND,
731 lhs: 0,
732 rhs: 0,
733 }])
734 .unwrap();
735 let expected = witness.root_unchecked();
736 witness.root = TRUE_DIGEST;
737 let registry = Arc::new(PrecompileRegistry::new());
738 assert_eq!(witness.compute_root(registry.clone()).unwrap(), expected);
739
740 for entries in [
741 Vec::new(),
742 alloc::vec![WireEntry::Join { tag: Tag::AND, lhs: 0, rhs: 1 }],
743 alloc::vec![
744 WireEntry::Join { tag: Tag::AND, lhs: 0, rhs: 2 },
745 WireEntry::Join { tag: Tag::AND, lhs: 0, rhs: 0 },
746 ],
747 alloc::vec![WireEntry::PairList { tag: tag(1), pairs: alloc::vec![(0, 1)] }],
748 ] {
749 let malformed = PrecompileWitness { entries, root: expected };
750 assert!(matches!(
751 malformed.compute_root(registry.clone()),
752 Err(PrecompileError::InvalidNode)
753 ));
754 }
755 let value = PrecompileWitness::from_entries(alloc::vec![WireEntry::Data {
756 tag: Tag::CHUNKS,
757 chunks: alloc::vec![felts(10)],
758 }])
759 .unwrap();
760 assert!(matches!(value.compute_root(registry), Err(PrecompileError::AssertionFailed)));
761 }
762
763 #[test]
764 fn export_omits_unreachable_state_and_retains_logged_true() {
765 let mut empty = DeferredState::default();
766 empty.register(Node::chunks(alloc::vec![felts(10)]).unwrap()).unwrap();
767 assert!(empty.into_witness().unwrap().is_none());
768 let mut state = DeferredState::default();
769 state.register(Node::chunks(alloc::vec![felts(10)]).unwrap()).unwrap();
770 state.log_statement(TRUE_DIGEST).unwrap();
771 let root = state.root();
772 let witness = state.into_witness().unwrap().unwrap();
773 assert_eq!(witness.root_unchecked(), root);
774 assert_eq!(witness.entries(), &[WireEntry::Join { tag: Tag::AND, lhs: 0, rhs: 0 }]);
775 }
776
777 #[test]
778 fn deep_shared_graph_exports_and_decodes_without_expansion() {
779 let mut state = DeferredState::default();
780 let mut statement = TRUE_DIGEST;
781 for _ in 0..4_096 {
782 statement = state.register(Node::and(statement, statement)).unwrap();
783 }
784 state.log_statement(statement).unwrap();
785 let root = state.root();
786 let witness = state.into_witness().unwrap().unwrap();
787 assert_eq!(witness.entries().len(), 4_097);
788 assert_eq!(witness.compute_root(Arc::new(PrecompileRegistry::new())).unwrap(), root);
789 assert_eq!(witness.root_unchecked(), root);
790 assert_eq!(PrecompileWitness::from_entries(witness.entries().to_vec()).unwrap(), witness);
791 assert_eq!(PrecompileWitness::read_from_bytes(&witness.to_bytes()).unwrap(), witness);
792 }
793
794 #[test]
795 fn standalone_witness_rejects_unsupported_versions_and_trailing_bytes() {
796 let witness = PrecompileWitness::from_entries(alloc::vec![WireEntry::Join {
797 tag: Tag::AND,
798 lhs: 0,
799 rhs: 0
800 }])
801 .unwrap();
802 let bytes = witness.to_bytes();
803 for version in [0, PrecompileWitness::WIRE_VERSION + 1] {
804 let mut unsupported = bytes.clone();
805 unsupported[0] = version;
806 assert!(PrecompileWitness::read_from_bytes(&unsupported).is_err());
807 }
808 let mut trailing = bytes;
809 trailing.push(0);
810 assert!(PrecompileWitness::read_from_bytes(&trailing).is_err());
811 }
812
813 #[test]
814 fn decoder_rejects_overlong_entry_and_payload_lengths() {
815 let entries = [
816 WireEntry::Data {
817 tag: Tag::CHUNKS,
818 chunks: alloc::vec![felts(10)],
819 },
820 WireEntry::PairList { tag: tag(1), pairs: alloc::vec![(0, 0)] },
821 ];
822 for entry in entries {
823 let witness = PrecompileWitness::from_entries(alloc::vec![entry]).unwrap();
824 let bytes = witness.to_bytes();
825 for offset in [1, 3 + Tag::min_serialized_size()] {
827 assert_eq!(bytes[offset], 3, "one uses the one-byte vint64 encoding");
828 let mut noncanonical = bytes[..offset].to_vec();
829 noncanonical.extend_from_slice(&[6, 0]);
830 noncanonical.extend_from_slice(&bytes[offset + 1..]);
831 assert!(PrecompileWitness::read_from_bytes(&noncanonical).is_err());
832 }
833 }
834 }
835
836 #[test]
837 fn singleton_vector_decodes_with_exact_byte_budget() {
838 let witness = PrecompileWitness::from_entries(alloc::vec![WireEntry::Join {
839 tag: Tag::AND,
840 lhs: 0,
841 rhs: 0
842 }])
843 .unwrap();
844 assert_eq!(PrecompileWitness::min_serialized_size(), witness.to_bytes().len());
845 let witnesses = alloc::vec![witness.clone(), witness];
846 let bytes = witnesses.to_bytes();
847 assert_eq!(
848 Vec::<PrecompileWitness>::read_from_bytes_with_budget(&bytes, bytes.len()).unwrap(),
849 witnesses
850 );
851 }
852
853 #[test]
854 fn wire_element_budget_accepts_exact_limit_and_rejects_one_more() {
855 let mut remaining = MAX_DEFERRED_ELEMENTS;
856 reserve_wire_elements(&mut remaining, MAX_DEFERRED_ELEMENTS).unwrap();
857 assert_eq!(remaining, 0);
858 assert!(reserve_wire_elements(&mut remaining, 1).is_err());
859 let mut overflow_budget = MAX_DEFERRED_ELEMENTS;
860 assert!(reserve_wire_payload(&mut overflow_budget, usize::MAX).is_err());
861 }
862
863 #[test]
864 fn in_memory_entries_enforce_the_execution_element_limit() {
865 let join = WireEntry::Join { tag: Tag::AND, lhs: 0, rhs: 0 };
866 let mut entries =
867 alloc::vec![join; MAX_DEFERRED_ELEMENTS / (Tag::FELT_LEN + Node::DATA_CHUNK_FELT_LEN)];
868 entries.push(WireEntry::Data {
870 tag: Tag::CHUNKS,
871 chunks: alloc::vec![felts(10)],
872 });
873 assert!(matches!(
874 PrecompileWitness::from_entries(entries),
875 Err(IntegrityError::DeferredStateTooLarge { .. })
876 ));
877 }
878
879 #[test]
880 fn decoder_rejects_oversized_and_truncated_lengths_before_payload_allocation() {
881 for count in [MAX_WIRE_ENTRIES, MAX_WIRE_ENTRIES + 1, usize::MAX] {
882 let mut bytes = alloc::vec![PrecompileWitness::WIRE_VERSION];
883 bytes.write_usize(count);
884 assert!(PrecompileWitness::read_from_bytes(&bytes).is_err());
885 }
886 for discriminant in [0, 2] {
887 let mut bytes = alloc::vec![PrecompileWitness::WIRE_VERSION];
888 bytes.write_usize(1);
889 bytes.write_u8(discriminant);
890 tag(1).write_into(&mut bytes);
891 bytes.write_usize(usize::MAX);
892 assert!(PrecompileWitness::read_from_bytes(&bytes).is_err());
893 }
894 }
895}