1use alloc::{sync::Arc, vec::Vec};
4use core::mem::size_of;
5
6use miden_crypto::{ONE, ZERO, hash::poseidon2::Poseidon2};
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10use super::DeferredError;
11use crate::{
12 Felt, Word,
13 serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
14 utils::bytes_to_packed_u32_elements,
15};
16
17pub type Digest = Word;
19
20pub type DataChunk = [Felt; 8];
22
23pub const TRUE_DIGEST: Digest = Word::new([ZERO; 4]);
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
39#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
40pub struct Tag {
41 id: Felt,
42 args: [Felt; 3],
43}
44
45impl Tag {
46 pub(crate) const FELT_LEN: usize = 4;
47 const CHUNKS_ID: Felt = Felt::new_unchecked(2);
48
49 pub const TRUE: Tag = Tag { id: ZERO, args: [ZERO; 3] };
51
52 pub const AND: Tag = Tag { id: ONE, args: [ZERO; 3] };
54
55 pub const CHUNKS: Tag = Tag { id: Self::CHUNKS_ID, args: [ZERO; 3] };
57
58 pub(crate) fn is_framework_reserved_id(id: Felt) -> bool {
60 id == ZERO || id == ONE || id == Self::CHUNKS_ID
61 }
62
63 pub(crate) fn is_framework_reserved(&self) -> bool {
65 Self::is_framework_reserved_id(self.id)
66 }
67
68 pub fn precompile(id: Felt, args: [Felt; 3]) -> Result<Self, DeferredError> {
74 if Self::is_framework_reserved_id(id) {
75 return Err(DeferredError::InvalidTag);
76 }
77 Ok(Self { id, args })
78 }
79
80 pub const fn id(&self) -> Felt {
82 self.id
83 }
84
85 pub const fn args(&self) -> [Felt; 3] {
87 self.args
88 }
89
90 pub const fn as_word(&self) -> [Felt; 4] {
92 [self.id, self.args[0], self.args[1], self.args[2]]
93 }
94
95 pub const fn from_word(w: [Felt; 4]) -> Self {
97 Self { id: w[0], args: [w[1], w[2], w[3]] }
98 }
99}
100
101impl Serializable for Tag {
102 fn write_into<W: ByteWriter>(&self, target: &mut W) {
103 for felt in &self.as_word() {
104 felt.write_into(target);
105 }
106 }
107}
108
109impl Deserializable for Tag {
110 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
111 Ok(Self::from_word([
112 Felt::read_from(source)?,
113 Felt::read_from(source)?,
114 Felt::read_from(source)?,
115 Felt::read_from(source)?,
116 ]))
117 }
118
119 fn min_serialized_size() -> usize {
120 Self::FELT_LEN * Felt::min_serialized_size()
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct Payload(PayloadRepr);
140
141#[derive(Debug, Clone, PartialEq, Eq)]
142enum PayloadRepr {
143 True,
145 Data(Arc<[DataChunk]>),
147 Join(DataChunk),
149 PairList(Arc<[DataChunk]>),
151}
152
153impl Payload {
154 fn value(chunk: DataChunk) -> Self {
156 Self(PayloadRepr::Data(alloc::vec![chunk].into()))
157 }
158
159 fn try_data(chunks: impl Into<Arc<[DataChunk]>>) -> Result<Self, DeferredError> {
163 let chunks = chunks.into();
164 if chunks.is_empty() {
165 return Err(DeferredError::InvalidPayload);
166 }
167 Ok(Self(PayloadRepr::Data(chunks)))
168 }
169
170 fn join(lhs: Digest, rhs: Digest) -> Self {
172 let [l0, l1, l2, l3] = lhs.into_elements();
173 let [r0, r1, r2, r3] = rhs.into_elements();
174 Self(PayloadRepr::Join([l0, l1, l2, l3, r0, r1, r2, r3]))
175 }
176
177 fn try_pair_list(pairs: impl Into<Arc<[(Digest, Digest)]>>) -> Result<Self, DeferredError> {
181 let pairs = pairs.into();
182 let chunks = pairs
183 .iter()
184 .map(|(lhs, rhs)| Self::pair_to_chunk(*lhs, *rhs))
185 .collect::<Vec<_>>();
186 Self::try_pair_list_chunks(chunks)
187 }
188
189 fn try_pair_list_chunks(chunks: impl Into<Arc<[DataChunk]>>) -> Result<Self, DeferredError> {
193 let chunks = chunks.into();
194 if chunks.is_empty() {
195 return Err(DeferredError::InvalidPayload);
196 }
197 Ok(Self(PayloadRepr::PairList(chunks)))
198 }
199
200 fn pair_to_chunk(lhs: Digest, rhs: Digest) -> DataChunk {
201 let [l0, l1, l2, l3] = lhs.into_elements();
202 let [r0, r1, r2, r3] = rhs.into_elements();
203 [l0, l1, l2, l3, r0, r1, r2, r3]
204 }
205
206 fn chunk_to_pair([l0, l1, l2, l3, r0, r1, r2, r3]: DataChunk) -> (Digest, Digest) {
207 (Digest::new([l0, l1, l2, l3]), Digest::new([r0, r1, r2, r3]))
208 }
209
210 pub fn as_chunks(&self) -> &[DataChunk] {
217 match &self.0 {
218 PayloadRepr::True => &[],
219 PayloadRepr::Data(chunks) | PayloadRepr::PairList(chunks) => chunks,
220 PayloadRepr::Join(chunk) => core::slice::from_ref(chunk),
221 }
222 }
223
224 pub fn as_data(&self) -> Result<&[DataChunk], DeferredError> {
229 match &self.0 {
230 PayloadRepr::Data(chunks) => Ok(chunks),
231 PayloadRepr::True | PayloadRepr::Join(_) | PayloadRepr::PairList(_) => {
232 Err(DeferredError::InvalidPayload)
233 },
234 }
235 }
236
237 pub fn as_value(&self) -> Result<&DataChunk, DeferredError> {
242 match self.as_data()? {
243 [chunk] => Ok(chunk),
244 _ => Err(DeferredError::InvalidPayload),
245 }
246 }
247
248 pub fn as_join(&self) -> Result<(Digest, Digest), DeferredError> {
253 match &self.0 {
254 PayloadRepr::Join([l0, l1, l2, l3, r0, r1, r2, r3]) => {
255 Ok((Digest::new([*l0, *l1, *l2, *l3]), Digest::new([*r0, *r1, *r2, *r3])))
256 },
257 PayloadRepr::True | PayloadRepr::Data(_) | PayloadRepr::PairList(_) => {
258 Err(DeferredError::InvalidPayload)
259 },
260 }
261 }
262
263 fn pair_list_chunks(&self) -> Result<&[DataChunk], DeferredError> {
264 match &self.0 {
265 PayloadRepr::PairList(chunks) => Ok(chunks),
266 PayloadRepr::True | PayloadRepr::Data(_) | PayloadRepr::Join(_) => {
267 Err(DeferredError::InvalidPayload)
268 },
269 }
270 }
271
272 pub fn as_pair_list(&self) -> Result<Vec<(Digest, Digest)>, DeferredError> {
277 Ok(self
278 .pair_list_chunks()?
279 .iter()
280 .map(|chunk| Self::chunk_to_pair(*chunk))
281 .collect())
282 }
283
284 fn children(&self) -> Vec<Digest> {
290 match &self.0 {
291 PayloadRepr::Join([l0, l1, l2, l3, r0, r1, r2, r3]) => {
292 alloc::vec![Digest::new([*l0, *l1, *l2, *l3]), Digest::new([*r0, *r1, *r2, *r3]),]
293 },
294 PayloadRepr::PairList(chunks) => chunks
295 .iter()
296 .flat_map(|chunk| {
297 let (lhs, rhs) = Self::chunk_to_pair(*chunk);
298 [lhs, rhs]
299 })
300 .collect(),
301 PayloadRepr::True | PayloadRepr::Data(_) => Vec::new(),
302 }
303 }
304}
305
306#[derive(Clone, Debug, PartialEq, Eq)]
316pub struct Node {
317 tag: Tag,
318 payload: Payload,
319}
320
321impl Node {
322 pub(crate) const DATA_CHUNK_FELT_LEN: usize = 8;
323
324 pub const PACKED_BYTES_PER_CHUNK: usize = Self::DATA_CHUNK_FELT_LEN * size_of::<u32>();
328
329 pub const TRUE: Node = Node {
331 tag: Tag::TRUE,
332 payload: Payload(PayloadRepr::True),
333 };
334
335 pub fn value(tag: Tag, chunk: DataChunk) -> Result<Self, DeferredError> {
337 let tag = Self::require_precompile_tag(tag)?;
338 Ok(Self { tag, payload: Payload::value(chunk) })
339 }
340
341 pub fn try_data(tag: Tag, chunks: impl Into<Arc<[DataChunk]>>) -> Result<Self, DeferredError> {
346 let tag = Self::require_precompile_tag(tag)?;
347 Ok(Self { tag, payload: Payload::try_data(chunks)? })
348 }
349
350 pub fn chunks(chunks: impl Into<Arc<[DataChunk]>>) -> Result<Self, DeferredError> {
354 Ok(Self {
355 tag: Tag::CHUNKS,
356 payload: Payload::try_data(chunks)?,
357 })
358 }
359
360 pub fn chunks_from_bytes(bytes: &[u8]) -> Self {
366 let mut felts = bytes_to_packed_u32_elements(bytes);
367 let n_chunks = felts.len().div_ceil(Self::DATA_CHUNK_FELT_LEN).max(1);
368 felts.resize(n_chunks * Self::DATA_CHUNK_FELT_LEN, ZERO);
369 let chunks = felts
370 .as_chunks::<{ Self::DATA_CHUNK_FELT_LEN }>()
371 .0
372 .iter()
373 .map(|chunk| core::array::from_fn(|i| chunk[i]))
374 .collect::<Vec<_>>();
375 Self::chunks(chunks).expect("chunks_from_bytes always creates at least one chunk")
376 }
377
378 pub fn join(tag: Tag, lhs: Digest, rhs: Digest) -> Result<Self, DeferredError> {
380 let tag = Self::require_precompile_tag(tag)?;
381 Ok(Self { tag, payload: Payload::join(lhs, rhs) })
382 }
383
384 pub fn try_pair_list(
386 tag: Tag,
387 pairs: impl Into<Arc<[(Digest, Digest)]>>,
388 ) -> Result<Self, DeferredError> {
389 let tag = Self::require_precompile_tag(tag)?;
390 Ok(Self {
391 tag,
392 payload: Payload::try_pair_list(pairs)?,
393 })
394 }
395
396 pub fn try_pair_list_chunks(
398 tag: Tag,
399 chunks: impl Into<Arc<[DataChunk]>>,
400 ) -> Result<Self, DeferredError> {
401 let tag = Self::require_precompile_tag(tag)?;
402 Ok(Self {
403 tag,
404 payload: Payload::try_pair_list_chunks(chunks)?,
405 })
406 }
407
408 pub fn and(lhs: Digest, rhs: Digest) -> Self {
410 Self {
411 tag: Tag::AND,
412 payload: Payload::join(lhs, rhs),
413 }
414 }
415
416 fn require_precompile_tag(tag: Tag) -> Result<Tag, DeferredError> {
417 if tag.is_framework_reserved() {
418 return Err(DeferredError::InvalidTag);
419 }
420 Ok(tag)
421 }
422
423 pub fn tag(&self) -> Tag {
425 self.tag
426 }
427
428 pub fn payload(&self) -> &Payload {
430 &self.payload
431 }
432
433 pub(crate) fn children(&self) -> impl Iterator<Item = Digest> + '_ {
441 self.payload.children().into_iter()
442 }
443
444 pub fn payload_for_tag(&self, tag: Tag) -> Result<&Payload, DeferredError> {
446 if self.tag != tag {
447 return Err(DeferredError::InvalidPayload);
448 }
449 Ok(&self.payload)
450 }
451
452 pub fn is_true(&self) -> bool {
454 matches!(&self.payload.0, PayloadRepr::True) && self.tag == Tag::TRUE
455 }
456
457 pub fn felt_len(&self) -> usize {
459 Tag::FELT_LEN
460 .checked_add(
461 Self::DATA_CHUNK_FELT_LEN
462 .checked_mul(self.payload.as_chunks().len())
463 .expect("payload felt count overflow"),
464 )
465 .expect("node felt count overflow")
466 }
467
468 pub(crate) fn storage_felt_len(&self) -> usize {
470 if self.is_true() { 0 } else { self.felt_len() }
471 }
472
473 pub fn write_into_felts(&self, target: &mut Vec<Felt>) {
475 target.extend_from_slice(&self.tag.as_word());
476 for chunk in self.payload.as_chunks() {
477 target.extend_from_slice(chunk);
478 }
479 }
480
481 pub fn to_felts(&self) -> Vec<Felt> {
483 let mut felts = Vec::with_capacity(self.felt_len());
484 self.write_into_felts(&mut felts);
485 felts
486 }
487
488 pub fn digest(&self) -> Digest {
490 if matches!(&self.payload.0, PayloadRepr::True) {
491 assert_eq!(self.tag, Tag::TRUE, "TRUE payload is only valid for Node::TRUE");
492 return TRUE_DIGEST;
493 }
494
495 let mut state = [ZERO; 12];
496 state[Self::DATA_CHUNK_FELT_LEN..Self::DATA_CHUNK_FELT_LEN + Tag::FELT_LEN]
497 .copy_from_slice(&self.tag.as_word());
498 for chunk in self.payload.as_chunks() {
499 state[0..Self::DATA_CHUNK_FELT_LEN].copy_from_slice(chunk);
500 Poseidon2::apply_permutation(&mut state);
501 }
502 Word::new([state[0], state[1], state[2], state[3]])
503 }
504}
505
506#[derive(Debug, Clone, Copy, PartialEq, Eq)]
519pub enum NodeType {
520 True,
522 Data,
524 Join,
526 PairList,
528}
529
530impl NodeType {
531 pub(crate) fn validate_node(self, node: &Node) -> Result<(), DeferredError> {
533 match self {
534 Self::True if node.is_true() => Ok(()),
535 Self::Data if node.payload.as_data().is_ok() => Ok(()),
536 Self::Join if node.payload.as_join().is_ok() => Ok(()),
537 Self::PairList if node.payload.pair_list_chunks().is_ok() => Ok(()),
538 _ => Err(DeferredError::InvalidPayload),
539 }
540 }
541}
542
543#[cfg(test)]
544mod tests {
545 use alloc::vec::Vec;
546
547 use super::*;
548
549 const TAG_A: Tag = Tag::from_word([Felt::new_unchecked(42), ZERO, ZERO, ZERO]);
550 const TAG_B: Tag =
551 Tag::from_word([Felt::new_unchecked(42), ZERO, Felt::new_unchecked(1), ZERO]);
552
553 fn block(seed: u64) -> DataChunk {
554 core::array::from_fn(|i| Felt::new_unchecked(seed.wrapping_add(i as u64)))
555 }
556
557 #[test]
558 fn tag_precompile_rejects_framework_reserved_ids_but_from_word_is_raw() {
559 assert_eq!(Tag::precompile(Tag::TRUE.id(), [ZERO; 3]), Err(DeferredError::InvalidTag));
560 assert_eq!(Tag::precompile(Tag::AND.id(), [ZERO; 3]), Err(DeferredError::InvalidTag));
561 assert_eq!(Tag::precompile(Tag::CHUNKS.id(), [ZERO; 3]), Err(DeferredError::InvalidTag));
562 assert_eq!(
563 Tag::precompile(Tag::CHUNKS.id(), [Felt::new_unchecked(9), ZERO, ZERO]),
564 Err(DeferredError::InvalidTag)
565 );
566
567 let raw_true = Tag::from_word([ZERO, Felt::new_unchecked(9), ZERO, ZERO]);
568 assert_eq!(raw_true.id(), Tag::TRUE.id());
569 assert_eq!(raw_true.args(), [Felt::new_unchecked(9), ZERO, ZERO]);
570
571 let raw_chunks = Tag::from_word([Tag::CHUNKS.id(), Felt::new_unchecked(9), ZERO, ZERO]);
572 assert_eq!(raw_chunks.id(), Tag::CHUNKS.id());
573 assert_eq!(raw_chunks.args(), [Felt::new_unchecked(9), ZERO, ZERO]);
574 }
575
576 #[test]
577 fn public_node_constructors_reject_framework_reserved_tags() {
578 let chunk = block(1);
579 assert_eq!(Node::value(Tag::TRUE, chunk), Err(DeferredError::InvalidTag));
580 assert_eq!(Node::try_data(Tag::AND, alloc::vec![chunk]), Err(DeferredError::InvalidTag));
581 assert_eq!(Node::try_data(Tag::CHUNKS, alloc::vec![chunk]), Err(DeferredError::InvalidTag));
582 assert_eq!(Node::join(Tag::AND, TRUE_DIGEST, TRUE_DIGEST), Err(DeferredError::InvalidTag));
583 assert_eq!(
584 Node::try_pair_list(Tag::AND, alloc::vec![(TRUE_DIGEST, TRUE_DIGEST)]),
585 Err(DeferredError::InvalidTag)
586 );
587
588 let and = Node::and(TRUE_DIGEST, TRUE_DIGEST);
589 assert_eq!(and.tag(), Tag::AND);
590 assert_eq!(and.payload().as_join().unwrap(), (TRUE_DIGEST, TRUE_DIGEST));
591 }
592
593 #[test]
594 fn true_node_has_no_data_and_serializes_to_tag_only() {
595 assert_eq!(Tag::TRUE, Tag::from_word([ZERO, ZERO, ZERO, ZERO]));
596 assert_eq!(Tag::AND, Tag::from_word([ONE, ZERO, ZERO, ZERO]));
597 assert_eq!(Tag::CHUNKS, Tag::from_word([Felt::new_unchecked(2), ZERO, ZERO, ZERO]));
598 assert_eq!(Tag::TRUE.as_word(), [ZERO, ZERO, ZERO, ZERO]);
599 assert_eq!(Tag::AND.as_word(), [ONE, ZERO, ZERO, ZERO]);
600 assert_eq!(Tag::CHUNKS.as_word(), [Felt::new_unchecked(2), ZERO, ZERO, ZERO]);
601 assert_eq!(TRUE_DIGEST, Word::new([ZERO; 4]));
602
603 let true_node = Node::TRUE;
604 assert_eq!(true_node.tag(), Tag::TRUE);
605 assert!(true_node.is_true());
606 assert_eq!(true_node.digest(), TRUE_DIGEST);
607 assert_eq!(true_node.felt_len(), Tag::FELT_LEN);
608 assert_eq!(true_node.to_felts(), Tag::TRUE.as_word());
609 assert_eq!(true_node.storage_felt_len(), 0);
610 assert!(true_node.payload().as_data().is_err());
611 assert!(true_node.payload().as_value().is_err());
612 }
613
614 #[test]
615 fn data_is_non_empty() {
616 assert!(Payload::try_data(Vec::<DataChunk>::new()).is_err());
618 assert!(Node::try_data(TAG_A, Vec::<DataChunk>::new()).is_err());
619
620 let node = Node::try_data(TAG_A, alloc::vec![block(1), block(9)]).unwrap();
621 assert_eq!(node.payload().as_data().unwrap(), &[block(1), block(9)][..]);
622 assert!(NodeType::Data.validate_node(&node).is_ok());
623 }
624
625 #[test]
626 fn chunks_is_framework_data_and_non_empty() {
627 assert_eq!(Node::chunks(Vec::<DataChunk>::new()), Err(DeferredError::InvalidPayload));
628
629 let chunks = alloc::vec![block(1), block(9)];
630 let node = Node::chunks(chunks.clone()).unwrap();
631 assert_eq!(node.tag(), Tag::CHUNKS);
632 assert_eq!(node.payload().as_data().unwrap(), &chunks[..]);
633 assert!(NodeType::Data.validate_node(&node).is_ok());
634
635 let mut expected = Tag::CHUNKS.as_word().to_vec();
636 expected.extend_from_slice(&chunks[0]);
637 expected.extend_from_slice(&chunks[1]);
638 assert_eq!(node.to_felts(), expected);
639
640 let precompile_data = Node::try_data(TAG_A, chunks).unwrap();
641 assert_ne!(node.digest(), precompile_data.digest());
642 }
643
644 #[test]
645 fn chunks_from_bytes_packs_little_endian_u32s_and_zero_pads() {
646 assert_eq!(Node::PACKED_BYTES_PER_CHUNK, 32);
647
648 let empty = Node::chunks_from_bytes(&[]);
649 assert_eq!(empty.tag(), Tag::CHUNKS);
650 assert_eq!(empty.payload().as_data().unwrap(), &[[ZERO; 8]][..]);
651
652 let node = Node::chunks_from_bytes(&[1, 2, 3, 4, 5]);
653 let chunks = node.payload().as_data().unwrap();
654 assert_eq!(chunks.len(), 1);
655 assert_eq!(chunks[0][0], Felt::from_u32(u32::from_le_bytes([1, 2, 3, 4])));
656 assert_eq!(chunks[0][1], Felt::from_u32(5));
657 assert_eq!(&chunks[0][2..], &[ZERO; 6]);
658
659 let long_bytes = (0u8..33).collect::<Vec<_>>();
660 let long = Node::chunks_from_bytes(&long_bytes);
661 let chunks = long.payload().as_data().unwrap();
662 assert_eq!(chunks.len(), 2);
663 assert_eq!(chunks[0][0], Felt::from_u32(u32::from_le_bytes([0, 1, 2, 3])));
664 assert_eq!(chunks[0][7], Felt::from_u32(u32::from_le_bytes([28, 29, 30, 31])));
665 assert_eq!(chunks[1][0], Felt::from_u32(32));
666 assert_eq!(&chunks[1][1..], &[ZERO; 7]);
667 }
668
669 #[test]
670 fn value_is_data_one() {
671 let chunk = block(5);
672 let node = Node::value(TAG_A, chunk).unwrap();
673
674 assert_eq!(node.payload().as_data().unwrap().len(), 1);
676 assert_eq!(node.payload().as_value().unwrap(), &chunk);
677
678 assert_eq!(node.felt_len(), Tag::FELT_LEN + Node::DATA_CHUNK_FELT_LEN);
680 let mut expected = TAG_A.as_word().to_vec();
681 expected.extend_from_slice(&chunk);
682 assert_eq!(node.to_felts(), expected);
683
684 let multi = Node::try_data(TAG_A, alloc::vec![chunk]).unwrap();
686 assert_eq!(node.digest(), multi.digest());
687 }
688
689 #[test]
690 fn data_shape_does_not_imply_one_chunk() {
691 let node = Node::try_data(TAG_A, alloc::vec![block(1), block(9)]).unwrap();
692 assert!(NodeType::Data.validate_node(&node).is_ok());
693 assert!(node.payload().as_value().is_err());
694 assert_eq!(node.payload().as_data().unwrap().len(), 2);
695 assert_eq!(node.felt_len(), Tag::FELT_LEN + Node::DATA_CHUNK_FELT_LEN * 2);
696 }
697
698 #[test]
699 fn digest_binds_tag_and_payload() {
700 let chunk = block(7);
701 let same = Node::value(TAG_A, chunk).unwrap();
702 let different_tag = Node::value(TAG_B, chunk).unwrap();
703 let different_payload = Node::value(TAG_A, block(8)).unwrap();
704
705 assert_ne!(same.digest(), different_tag.digest());
706 assert_ne!(same.digest(), different_payload.digest());
707 }
708
709 #[test]
710 fn join_round_trips_children_and_serializes() {
711 let lhs = Node::value(TAG_A, block(1)).unwrap().digest();
712 let rhs = Node::value(TAG_A, block(2)).unwrap().digest();
713 let join = Node::join(TAG_B, lhs, rhs).unwrap();
714
715 assert_eq!(join.payload().as_join().unwrap(), (lhs, rhs));
716 assert!(join.payload().as_data().is_err());
717
718 let mut payload = [ZERO; Node::DATA_CHUNK_FELT_LEN];
719 payload[..Word::NUM_ELEMENTS].copy_from_slice(lhs.as_elements());
720 payload[Word::NUM_ELEMENTS..].copy_from_slice(rhs.as_elements());
721
722 assert_eq!(join.felt_len(), Tag::FELT_LEN + Node::DATA_CHUNK_FELT_LEN);
724 let mut expected = TAG_B.as_word().to_vec();
725 expected.extend_from_slice(&payload);
726 assert_eq!(join.to_felts(), expected);
727
728 assert_eq!(join.payload().as_chunks(), &[payload][..]);
729 }
730
731 #[test]
732 fn pair_list_is_non_empty() {
733 assert!(Payload::try_pair_list(Vec::<(Digest, Digest)>::new()).is_err());
734 assert!(Node::try_pair_list(TAG_A, Vec::<(Digest, Digest)>::new()).is_err());
735 assert!(Node::try_pair_list_chunks(TAG_A, Vec::<DataChunk>::new()).is_err());
736
737 let lhs = Node::value(TAG_A, block(1)).unwrap().digest();
738 let rhs = Node::value(TAG_A, block(2)).unwrap().digest();
739 let node = Node::try_pair_list(TAG_A, alloc::vec![(lhs, rhs)]).unwrap();
740 assert_eq!(node.payload().as_pair_list().unwrap(), alloc::vec![(lhs, rhs)]);
741 }
742
743 #[test]
744 fn pair_list_round_trips_pairs_children_and_serializes() {
745 let scalar_0 = Node::value(TAG_A, block(1)).unwrap().digest();
746 let point_0 = Node::value(TAG_A, block(2)).unwrap().digest();
747 let scalar_1 = Node::value(TAG_A, block(3)).unwrap().digest();
748 let point_1 = Node::value(TAG_A, block(4)).unwrap().digest();
749 let pairs = alloc::vec![(scalar_0, point_0), (scalar_1, point_1)];
750 let node = Node::try_pair_list(TAG_B, pairs.clone()).unwrap();
751
752 assert_eq!(node.payload().as_pair_list().unwrap(), pairs);
753 assert!(node.payload().as_data().is_err());
754 assert!(node.payload().as_join().is_err());
755 assert_eq!(
756 node.children().collect::<Vec<_>>(),
757 alloc::vec![scalar_0, point_0, scalar_1, point_1]
758 );
759
760 let mut chunk_0 = [ZERO; Node::DATA_CHUNK_FELT_LEN];
761 chunk_0[..Word::NUM_ELEMENTS].copy_from_slice(scalar_0.as_elements());
762 chunk_0[Word::NUM_ELEMENTS..].copy_from_slice(point_0.as_elements());
763 let mut chunk_1 = [ZERO; Node::DATA_CHUNK_FELT_LEN];
764 chunk_1[..Word::NUM_ELEMENTS].copy_from_slice(scalar_1.as_elements());
765 chunk_1[Word::NUM_ELEMENTS..].copy_from_slice(point_1.as_elements());
766
767 assert_eq!(node.felt_len(), Tag::FELT_LEN + Node::DATA_CHUNK_FELT_LEN * 2);
768 let mut expected = TAG_B.as_word().to_vec();
769 expected.extend_from_slice(&chunk_0);
770 expected.extend_from_slice(&chunk_1);
771 assert_eq!(node.to_felts(), expected);
772 assert_eq!(node.payload().as_chunks(), &[chunk_0, chunk_1][..]);
773
774 let data_node = Node::try_data(TAG_B, alloc::vec![chunk_0, chunk_1]).unwrap();
775 assert_eq!(node.digest(), data_node.digest(), "pair-list digest uses chunk hash layout");
776
777 assert!(NodeType::PairList.validate_node(&node).is_ok());
778 assert!(NodeType::Data.validate_node(&node).is_err());
779 }
780}