cairo_vm/vm/runners/
cairo_pie.rs

1use super::cairo_runner::ExecutionResources;
2use crate::stdlib::prelude::{String, Vec};
3use crate::types::builtin_name::BuiltinName;
4use crate::vm::errors::cairo_pie_errors::CairoPieValidationError;
5use crate::{
6    stdlib::{
7        collections::{BTreeMap, HashMap},
8        prelude::*,
9    },
10    types::relocatable::{MaybeRelocatable, Relocatable},
11    Felt252,
12};
13use num_traits::{One, Zero};
14use serde::{Deserialize, Serialize};
15#[cfg(feature = "std")]
16use {
17    std::{fs::File, io::Write, path::Path},
18    zip::ZipWriter,
19};
20
21const CAIRO_PIE_VERSION: &str = "1.1";
22
23#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
24pub struct SegmentInfo {
25    pub index: isize,
26    pub size: usize,
27}
28
29impl From<(isize, usize)> for SegmentInfo {
30    fn from(value: (isize, usize)) -> Self {
31        SegmentInfo {
32            index: value.0,
33            size: value.1,
34        }
35    }
36}
37
38// A simplified version of Memory, without any additional data besides its elements
39// Contains all addr-value pairs, ordered by index and offset
40// Allows practical serialization + conversion between CairoPieMemory & Memory
41#[derive(Serialize, Deserialize, Clone, Debug, Eq)]
42pub struct CairoPieMemory(
43    #[serde(serialize_with = "serde_impl::serialize_memory")]
44    pub  Vec<((usize, usize), MaybeRelocatable)>,
45);
46
47impl PartialEq for CairoPieMemory {
48    fn eq(&self, other: &Self) -> bool {
49        fn as_hashmap(
50            cairo_pie_memory: &CairoPieMemory,
51        ) -> HashMap<&(usize, usize), &MaybeRelocatable> {
52            cairo_pie_memory
53                .0
54                .iter()
55                .map(|tuple| (&tuple.0, &tuple.1))
56                .collect::<HashMap<&(usize, usize), &MaybeRelocatable>>()
57        }
58        as_hashmap(self) == as_hashmap(other)
59    }
60}
61
62#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
63pub struct PublicMemoryPage {
64    pub start: usize,
65    pub size: usize,
66}
67
68impl From<&Vec<usize>> for PublicMemoryPage {
69    fn from(vec: &Vec<usize>) -> Self {
70        Self {
71            start: vec[0],
72            size: vec[1],
73        }
74    }
75}
76
77// HashMap value based on starknet/core/os/output.cairo usage
78pub type Attributes = BTreeMap<String, Vec<usize>>;
79pub type Pages = BTreeMap<usize, PublicMemoryPage>;
80
81#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
82pub struct OutputBuiltinAdditionalData {
83    #[serde(with = "serde_impl::pages")]
84    pub pages: Pages,
85    pub attributes: Attributes,
86}
87
88#[derive(Serialize, Deserialize, Clone, Debug, Eq)]
89#[serde(untagged)]
90pub enum BuiltinAdditionalData {
91    // Catch empty lists under the `Empty` variant.
92    Empty([(); 0]),
93    // Contains verified addresses as contiguous index, value pairs
94    #[serde(with = "serde_impl::hash_additional_data")]
95    Hash(Vec<Relocatable>),
96    Output(OutputBuiltinAdditionalData),
97    // Signatures are composed of (r, s) tuples
98    #[serde(with = "serde_impl::signature_additional_data")]
99    Signature(BTreeMap<Relocatable, (Felt252, Felt252)>),
100    None,
101}
102
103impl BuiltinAdditionalData {
104    fn is_empty(&self) -> bool {
105        match self {
106            Self::Empty(_) => true,
107            Self::Hash(data) => data.is_empty(),
108            Self::Signature(data) => data.is_empty(),
109            Self::Output(_) => false,
110            Self::None => false,
111        }
112    }
113}
114
115impl PartialEq for BuiltinAdditionalData {
116    fn eq(&self, other: &BuiltinAdditionalData) -> bool {
117        match (self, other) {
118            (Self::Hash(data), Self::Hash(other_data)) => data == other_data,
119            (Self::Signature(data), Self::Signature(other_data)) => data == other_data,
120            (Self::Output(data), Self::Output(other_data)) => data == other_data,
121            (Self::None, Self::None) => true,
122            (Self::Empty(_), x) | (x, Self::Empty(_)) => x.is_empty(),
123            _ => false,
124        }
125    }
126}
127
128#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
129pub struct CairoPieAdditionalData(
130    #[serde(with = "crate::types::builtin_name::serde_generic_map_impl")]
131    pub  BTreeMap<BuiltinName, BuiltinAdditionalData>,
132);
133
134#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
135pub struct CairoPie {
136    pub metadata: CairoPieMetadata,
137    pub memory: CairoPieMemory,
138    pub execution_resources: ExecutionResources,
139    pub additional_data: CairoPieAdditionalData,
140    pub version: CairoPieVersion,
141}
142
143#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
144pub struct CairoPieMetadata {
145    pub program: StrippedProgram,
146    pub program_segment: SegmentInfo,
147    pub execution_segment: SegmentInfo,
148    pub ret_fp_segment: SegmentInfo,
149    pub ret_pc_segment: SegmentInfo,
150    #[serde(serialize_with = "serde_impl::serialize_builtin_segments")]
151    pub builtin_segments: BTreeMap<BuiltinName, SegmentInfo>,
152    pub extra_segments: Vec<SegmentInfo>,
153}
154
155#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
156pub struct StrippedProgram {
157    #[serde(with = "serde_impl::program_data")]
158    pub data: Vec<MaybeRelocatable>,
159    pub builtins: Vec<BuiltinName>,
160    pub main: usize,
161    // Dummy field
162    #[serde(with = "serde_impl::prime")]
163    pub prime: (),
164}
165
166#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
167pub struct CairoPieVersion {
168    // Dummy field
169    #[serde(with = "serde_impl::version")]
170    pub cairo_pie: (),
171}
172
173impl CairoPieMetadata {
174    pub(crate) fn run_validity_checks(&self) -> Result<(), CairoPieValidationError> {
175        if self.program.main > self.program.data.len() {
176            return Err(CairoPieValidationError::InvalidMainAddress);
177        }
178        if self.program.data.len() != self.program_segment.size {
179            return Err(CairoPieValidationError::ProgramLenVsSegmentSizeMismatch);
180        }
181        if self.builtin_segments.len() != self.program.builtins.len()
182            || !self
183                .program
184                .builtins
185                .iter()
186                .all(|b| self.builtin_segments.contains_key(b))
187        {
188            return Err(CairoPieValidationError::BuiltinListVsSegmentsMismatch);
189        }
190        if !self.ret_fp_segment.size.is_zero() {
191            return Err(CairoPieValidationError::InvalidRetFpSegmentSize);
192        }
193        if !self.ret_pc_segment.size.is_zero() {
194            return Err(CairoPieValidationError::InvalidRetPcSegmentSize);
195        }
196        self.validate_segment_order()
197    }
198
199    fn validate_segment_order(&self) -> Result<(), CairoPieValidationError> {
200        if !self.program_segment.index.is_zero() {
201            return Err(CairoPieValidationError::InvalidProgramSegmentIndex);
202        }
203        if !self.execution_segment.index.is_one() {
204            return Err(CairoPieValidationError::InvalidExecutionSegmentIndex);
205        }
206        for (i, builtin_name) in self.program.builtins.iter().enumerate() {
207            // We can safely index as run_validity_checks already ensures that the keys match
208            if self.builtin_segments[builtin_name].index != 2 + i as isize {
209                return Err(CairoPieValidationError::InvalidBuiltinSegmentIndex(
210                    *builtin_name,
211                ));
212            }
213        }
214        let n_builtins = self.program.builtins.len() as isize;
215        if self.ret_fp_segment.index != n_builtins + 2 {
216            return Err(CairoPieValidationError::InvalidRetFpSegmentIndex);
217        }
218        if self.ret_pc_segment.index != n_builtins + 3 {
219            return Err(CairoPieValidationError::InvalidRetPcSegmentIndex);
220        }
221        for (i, segment) in self.extra_segments.iter().enumerate() {
222            if segment.index != 4 + n_builtins + i as isize {
223                return Err(CairoPieValidationError::InvalidExtraSegmentIndex);
224            }
225        }
226        Ok(())
227    }
228}
229
230impl CairoPie {
231    /// Check that self is a valid Cairo PIE
232    pub fn run_validity_checks(&self) -> Result<(), CairoPieValidationError> {
233        self.metadata.run_validity_checks()?;
234        self.run_memory_validity_checks()?;
235        if self.execution_resources.builtin_instance_counter.len()
236            != self.metadata.program.builtins.len()
237            || !self.metadata.program.builtins.iter().all(|b| {
238                self.execution_resources
239                    .builtin_instance_counter
240                    .contains_key(b)
241            })
242        {
243            return Err(CairoPieValidationError::BuiltinListVsSegmentsMismatch);
244        }
245        Ok(())
246    }
247
248    fn run_memory_validity_checks(&self) -> Result<(), CairoPieValidationError> {
249        let mut segment_sizes = vec![
250            &self.metadata.program_segment,
251            &self.metadata.execution_segment,
252            &self.metadata.ret_fp_segment,
253            &self.metadata.ret_pc_segment,
254        ];
255        segment_sizes.extend(self.metadata.builtin_segments.values());
256        segment_sizes.extend(self.metadata.extra_segments.iter());
257        let segment_sizes: HashMap<isize, usize> =
258            HashMap::from_iter(segment_sizes.iter().map(|si| (si.index, si.size)));
259
260        let validate_addr = |addr: Relocatable| -> Result<(), CairoPieValidationError> {
261            if segment_sizes
262                .get(&addr.segment_index)
263                .is_none_or(|size| addr.offset > *size)
264            {
265                return Err(CairoPieValidationError::InvalidAddress);
266            }
267            Ok(())
268        };
269
270        for ((si, so), _) in self.memory.0.iter() {
271            validate_addr((*si as isize, *so).into())?;
272        }
273        Ok(())
274    }
275
276    /// Checks that the pie received is identical to self, skipping the fields execution_resources.n_steps, and additional_data[pedersen]
277    /// Stricter runs check more Pedersen addresses leading to different address lists
278    pub fn check_pie_compatibility(&self, pie: &CairoPie) -> Result<(), CairoPieValidationError> {
279        if self.metadata != pie.metadata {
280            return Err(CairoPieValidationError::DiffMetadata);
281        }
282        if self.memory != pie.memory {
283            return Err(CairoPieValidationError::DiffMemory);
284        }
285        if self.execution_resources.n_steps != pie.execution_resources.n_steps
286            || self.execution_resources.builtin_instance_counter
287                != pie.execution_resources.builtin_instance_counter
288        {
289            return Err(CairoPieValidationError::DiffExecutionResources);
290        }
291        if self.additional_data.0.len() != pie.additional_data.0.len() {
292            return Err(CairoPieValidationError::DiffAdditionalData);
293        }
294        for (name, data) in self.additional_data.0.iter() {
295            // As documented above, we skip the pedersen field when comparing.
296            if *name == BuiltinName::pedersen {
297                continue;
298            }
299            if !pie.additional_data.0.get(name).is_some_and(|d| d == data) {
300                return Err(CairoPieValidationError::DiffAdditionalDataForBuiltin(*name));
301            }
302        }
303        Ok(())
304    }
305
306    #[cfg(feature = "std")]
307    pub fn write_zip_file(
308        &self,
309        file_path: &Path,
310        merge_extra_segments: bool,
311    ) -> Result<(), std::io::Error> {
312        let mut metadata = self.metadata.clone();
313
314        let segment_offsets = if merge_extra_segments {
315            if let Some((segment, segment_offsets)) = self.merge_extra_segments() {
316                metadata.extra_segments = vec![segment];
317                Some(segment_offsets)
318            } else {
319                None
320            }
321        } else {
322            None
323        };
324
325        let file = File::create(file_path)?;
326        let mut zip_writer = ZipWriter::new(file);
327        let options = zip::write::FileOptions::default()
328            .compression_method(zip::CompressionMethod::Deflated)
329            .large_file(true);
330
331        zip_writer.start_file("version.json", options)?;
332        serde_json::to_writer(&mut zip_writer, &self.version)?;
333        zip_writer.start_file("metadata.json", options)?;
334        serde_json::to_writer(&mut zip_writer, &metadata)?;
335        zip_writer.start_file("memory.bin", options)?;
336        zip_writer.write_all(&self.memory.to_bytes(segment_offsets))?;
337        zip_writer.start_file("additional_data.json", options)?;
338        serde_json::to_writer(&mut zip_writer, &self.additional_data)?;
339        zip_writer.start_file("execution_resources.json", options)?;
340        serde_json::to_writer(&mut zip_writer, &self.execution_resources)?;
341        zip_writer.finish()?;
342        Ok(())
343    }
344
345    #[cfg(feature = "std")]
346    pub fn from_zip_archive<R: std::io::Read + std::io::Seek>(
347        mut zip_reader: zip::ZipArchive<R>,
348    ) -> Result<CairoPie, std::io::Error> {
349        use std::io::Read;
350
351        let version = match zip_reader.by_name("version.json") {
352            Ok(version_buffer) => {
353                let reader = std::io::BufReader::new(version_buffer);
354                serde_json::from_reader(reader)?
355            }
356            Err(_) => CairoPieVersion { cairo_pie: () },
357        };
358
359        let reader = std::io::BufReader::new(zip_reader.by_name("metadata.json")?);
360        let metadata: CairoPieMetadata = serde_json::from_reader(reader)?;
361
362        let mut memory = vec![];
363        zip_reader.by_name("memory.bin")?.read_to_end(&mut memory)?;
364        let memory = CairoPieMemory::from_bytes(&memory)
365            .ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData))?;
366
367        let reader = std::io::BufReader::new(zip_reader.by_name("execution_resources.json")?);
368        let execution_resources: ExecutionResources = serde_json::from_reader(reader)?;
369
370        let reader = std::io::BufReader::new(zip_reader.by_name("additional_data.json")?);
371        let additional_data: CairoPieAdditionalData = serde_json::from_reader(reader)?;
372
373        Ok(CairoPie {
374            metadata,
375            memory,
376            execution_resources,
377            additional_data,
378            version,
379        })
380    }
381
382    #[cfg(feature = "std")]
383    pub fn from_bytes(bytes: &[u8]) -> Result<Self, std::io::Error> {
384        let reader = std::io::Cursor::new(bytes);
385        let zip_archive = zip::ZipArchive::new(reader)?;
386
387        Self::from_zip_archive(zip_archive)
388    }
389
390    #[cfg(feature = "std")]
391    pub fn read_zip_file(path: &Path) -> Result<Self, std::io::Error> {
392        let file = File::open(path)?;
393        let zip = zip::ZipArchive::new(file)?;
394
395        Self::from_zip_archive(zip)
396    }
397
398    // Heavily inspired in:
399    // https://github.com/starkware-libs/cairo-lang/blob/8276ac35830148a397e1143389f23253c8b80e93/src/starkware/cairo/lang/vm/cairo_pie.py#L286-L306
400    /// Merges `extra_segments` to a single segment.
401    ///
402    /// Returns a tuple with the new `extra_segments` (containing just the merged segment)
403    /// and a HashMap with the old segment indices mapped to their new offset in the new segment
404    #[cfg(feature = "std")]
405    fn merge_extra_segments(&self) -> Option<(SegmentInfo, HashMap<usize, Relocatable>)> {
406        if self.metadata.extra_segments.is_empty() {
407            return None;
408        }
409
410        let new_index = self.metadata.extra_segments[0].index;
411        let mut accumulated_size = 0;
412        let offsets: HashMap<usize, Relocatable> = self
413            .metadata
414            .extra_segments
415            .iter()
416            .map(|seg| {
417                let value = (
418                    seg.index as usize,
419                    Relocatable {
420                        segment_index: new_index,
421                        offset: accumulated_size,
422                    },
423                );
424
425                accumulated_size += seg.size;
426
427                value
428            })
429            .collect();
430
431        Some((
432            SegmentInfo {
433                index: new_index,
434                size: accumulated_size,
435            },
436            offsets,
437        ))
438    }
439}
440
441pub(super) mod serde_impl {
442    use crate::stdlib::collections::{BTreeMap, HashMap};
443    use crate::types::builtin_name::BuiltinName;
444    use num_traits::Num;
445
446    use super::CAIRO_PIE_VERSION;
447    use super::{CairoPieMemory, Pages, PublicMemoryPage, SegmentInfo};
448    #[cfg(any(target_arch = "wasm32", not(feature = "std")))]
449    use crate::alloc::string::ToString;
450    use crate::stdlib::prelude::{String, Vec};
451    use crate::{
452        types::relocatable::{MaybeRelocatable, Relocatable},
453        utils::CAIRO_PRIME,
454        Felt252,
455    };
456    use num_bigint::BigUint;
457    use serde::{
458        de::Error, ser::SerializeMap, ser::SerializeSeq, Deserialize, Deserializer, Serialize,
459        Serializer,
460    };
461    use serde_json::Number;
462
463    pub const ADDR_BYTE_LEN: usize = 8;
464    pub const FIELD_BYTE_LEN: usize = 32;
465    pub const CELL_BYTE_LEN: usize = ADDR_BYTE_LEN + FIELD_BYTE_LEN;
466    pub const ADDR_BASE: u64 = 0x8000000000000000; // 2 ** (8 * ADDR_BYTE_LEN - 1)
467    pub const OFFSET_BASE: u64 = 0x800000000000; // 2 ** OFFSET_BIT_LEN
468    pub const RELOCATE_BASE: &str =
469        "8000000000000000000000000000000000000000000000000000000000000000"; // 2 ** (8 * FIELD_BYTE_LEN - 1)
470
471    pub(crate) struct Felt252Wrapper<'a>(&'a Felt252);
472
473    impl Serialize for Felt252Wrapper<'_> {
474        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
475        where
476            S: Serializer,
477        {
478            // Note: This uses an API intended only for testing.
479            serde_json::Number::from_string_unchecked(self.0.to_string()).serialize(serializer)
480        }
481    }
482
483    pub mod version {
484        use super::*;
485
486        pub fn serialize<S>(_value: &(), serializer: S) -> Result<S::Ok, S::Error>
487        where
488            S: Serializer,
489        {
490            serializer.serialize_str(CAIRO_PIE_VERSION)
491        }
492
493        pub fn deserialize<'de, D>(d: D) -> Result<(), D::Error>
494        where
495            D: Deserializer<'de>,
496        {
497            let version = String::deserialize(d)?;
498
499            if version != CAIRO_PIE_VERSION {
500                Err(D::Error::custom("Invalid cairo_pie version"))
501            } else {
502                Ok(())
503            }
504        }
505    }
506
507    pub mod program_data {
508        use super::*;
509
510        pub fn serialize<S>(values: &[MaybeRelocatable], serializer: S) -> Result<S::Ok, S::Error>
511        where
512            S: Serializer,
513        {
514            use serde::ser::Error;
515            let mut seq_serializer = serializer.serialize_seq(Some(values.len()))?;
516
517            for value in values {
518                match value {
519                    MaybeRelocatable::RelocatableValue(_) => {
520                        return Err(S::Error::custom("Invalid program data"))
521                    }
522                    MaybeRelocatable::Int(x) => {
523                        seq_serializer.serialize_element(&Felt252Wrapper(x))?;
524                    }
525                };
526            }
527
528            seq_serializer.end()
529        }
530
531        pub fn deserialize<'de, D>(d: D) -> Result<Vec<MaybeRelocatable>, D::Error>
532        where
533            D: Deserializer<'de>,
534        {
535            let numbers = Vec::<serde_json::Number>::deserialize(d)?;
536            numbers
537                .into_iter()
538                .map(|n| Felt252::from_dec_str(n.as_str()).map(MaybeRelocatable::from))
539                .collect::<Result<Vec<_>, _>>()
540                .map_err(|_| D::Error::custom("Failed to deserilaize Felt252 value"))
541        }
542    }
543
544    pub mod prime {
545        use super::*;
546
547        use lazy_static::lazy_static;
548        lazy_static! {
549            static ref CAIRO_PRIME_NUMBER: Number =
550                Number::from_string_unchecked(CAIRO_PRIME.to_string());
551        }
552
553        pub fn serialize<S>(_value: &(), serializer: S) -> Result<S::Ok, S::Error>
554        where
555            S: Serializer,
556        {
557            // Note: This uses an API intended only for testing.
558            CAIRO_PRIME_NUMBER.serialize(serializer)
559        }
560
561        pub fn deserialize<'de, D>(d: D) -> Result<(), D::Error>
562        where
563            D: Deserializer<'de>,
564        {
565            let prime = Number::deserialize(d)?;
566
567            if prime != *CAIRO_PRIME_NUMBER {
568                Err(D::Error::custom("Invalid prime"))
569            } else {
570                Ok(())
571            }
572        }
573    }
574
575    pub fn serialize_memory<S>(
576        values: &[((usize, usize), MaybeRelocatable)],
577        serializer: S,
578    ) -> Result<S::Ok, S::Error>
579    where
580        S: Serializer,
581    {
582        // Missing segment and memory holes can be ignored
583        // as they can be inferred by the address on the prover side
584        let mem_cap = values.len() * ADDR_BYTE_LEN + values.len() * FIELD_BYTE_LEN;
585        let mut res = Vec::with_capacity(mem_cap);
586
587        for ((segment, offset), value) in values.iter() {
588            // mem_addr = ADDR_BASE + segment * OFFSET_BASE + offset
589            let mem_addr = (*segment as u64)
590                .checked_mul(OFFSET_BASE)
591                .and_then(|n| n.checked_add(ADDR_BASE))
592                .and_then(|n| n.checked_add(*offset as u64))
593                .ok_or_else(|| {
594                    serde::ser::Error::custom(format!(
595                        "failed to serialize address: {segment}:{offset}"
596                    ))
597                })?;
598
599            res.extend_from_slice(mem_addr.to_le_bytes().as_ref());
600            match value {
601                // Serializes RelocatableValue(little endian):
602                // 1bit |   SEGMENT_BITS |   OFFSET_BITS
603                // 1    |     segment    |   offset
604                MaybeRelocatable::RelocatableValue(rel_val) => {
605                    let reloc_base = BigUint::from_str_radix(RELOCATE_BASE, 16)
606                        .map_err(|_| serde::ser::Error::custom("invalid relocation base str"))?;
607                    let reloc_value = reloc_base
608                        + BigUint::from(rel_val.segment_index as usize)
609                            * BigUint::from(OFFSET_BASE)
610                        + BigUint::from(rel_val.offset);
611                    res.extend_from_slice(reloc_value.to_bytes_le().as_ref());
612                }
613                // Serializes Int(little endian):
614                // 1bit | Num
615                // 0    | num
616                MaybeRelocatable::Int(data_val) => {
617                    res.extend_from_slice(data_val.to_bytes_le().as_ref());
618                }
619            };
620        }
621
622        let string = res
623            .iter()
624            .fold(String::new(), |string, b| string + &format!("{:02x}", b));
625
626        serializer.serialize_str(&string)
627    }
628
629    pub mod pages {
630        use super::*;
631
632        pub fn serialize<S>(pages: &Pages, serializer: S) -> Result<S::Ok, S::Error>
633        where
634            S: Serializer,
635        {
636            let mut map = serializer.serialize_map(Some(pages.len()))?;
637            for (k, v) in pages {
638                map.serialize_entry(&k.to_string(), &vec![v.start, v.size])?;
639            }
640            map.end()
641        }
642
643        pub fn deserialize<'de, D>(deserializer: D) -> Result<Pages, D::Error>
644        where
645            D: Deserializer<'de>,
646        {
647            Ok(HashMap::<String, Vec<usize>>::deserialize(deserializer)?
648                .iter()
649                .map(|(k, v)| {
650                    if v.len() == 2 {
651                        Ok((
652                            k.parse::<usize>().map_err(|_| {
653                                D::Error::custom("Failed to deserialize page index.")
654                            })?,
655                            PublicMemoryPage::from(v),
656                        ))
657                    } else {
658                        Err(D::Error::custom(
659                            "Memory page description must be of length 2.",
660                        ))
661                    }
662                })
663                .collect::<Result<Vec<_>, _>>()
664                .map_err(|_| D::Error::custom("PublicMemoryPage deserialization failed."))?
665                .into_iter()
666                .collect::<Pages>())
667        }
668    }
669
670    impl CairoPieMemory {
671        /// Relocates a `Relocatable` value, which represented by its
672        /// index and offset, according to a given segment offsets
673        fn relocate_value(
674            index: usize,
675            offset: usize,
676            segment_offsets: &Option<HashMap<usize, Relocatable>>,
677        ) -> (usize, usize) {
678            segment_offsets
679                .as_ref()
680                .and_then(|offsets| offsets.get(&index))
681                .map(|relocatable| {
682                    (
683                        relocatable.segment_index as usize,
684                        relocatable.offset + offset,
685                    )
686                })
687                .unwrap_or((index, offset))
688        }
689
690        pub fn to_bytes(&self, seg_offsets: Option<HashMap<usize, Relocatable>>) -> Vec<u8> {
691            // Missing segment and memory holes can be ignored
692            // as they can be inferred by the address on the prover side
693            let values = &self.0;
694            let mem_cap = values.len() * ADDR_BYTE_LEN + values.len() * FIELD_BYTE_LEN;
695            let mut res = Vec::with_capacity(mem_cap);
696
697            for ((segment, offset), value) in values.iter() {
698                let (segment, offset) = Self::relocate_value(*segment, *offset, &seg_offsets);
699                let mem_addr = ADDR_BASE + segment as u64 * OFFSET_BASE + offset as u64;
700                res.extend_from_slice(mem_addr.to_le_bytes().as_ref());
701                match value {
702                    // Serializes RelocatableValue(little endian):
703                    // 1bit |   SEGMENT_BITS |   OFFSET_BITS
704                    // 1    |     segment    |   offset
705                    MaybeRelocatable::RelocatableValue(rel_val) => {
706                        let (segment, offset) = Self::relocate_value(
707                            rel_val.segment_index as usize,
708                            rel_val.offset,
709                            &seg_offsets,
710                        );
711                        let reloc_base = BigUint::from_str_radix(RELOCATE_BASE, 16).unwrap();
712                        let reloc_value = reloc_base
713                            + BigUint::from(segment) * BigUint::from(OFFSET_BASE)
714                            + BigUint::from(offset);
715                        res.extend_from_slice(reloc_value.to_bytes_le().as_ref());
716                    }
717                    // Serializes Int(little endian):
718                    // 1bit | Num
719                    // 0    | num
720                    MaybeRelocatable::Int(data_val) => {
721                        res.extend_from_slice(data_val.to_bytes_le().as_ref());
722                    }
723                };
724            }
725            res
726        }
727
728        pub fn from_bytes(bytes: &[u8]) -> Option<CairoPieMemory> {
729            if !num_integer::Integer::is_multiple_of(&bytes.len(), &CELL_BYTE_LEN) {
730                return None;
731            }
732
733            let relocatable_from_bytes = |bytes: [u8; 8]| -> (usize, usize) {
734                const N_SEGMENT_BITS: usize = 16;
735                const N_OFFSET_BITS: usize = 47;
736                const SEGMENT_MASK: u64 = ((1 << N_SEGMENT_BITS) - 1) << N_OFFSET_BITS;
737                const OFFSET_MASK: u64 = (1 << N_OFFSET_BITS) - 1;
738
739                let addr = u64::from_le_bytes(bytes);
740                let segment = (addr & SEGMENT_MASK) >> N_OFFSET_BITS;
741                let offset = addr & OFFSET_MASK;
742                (segment as usize, offset as usize)
743            };
744
745            let mut res = vec![];
746            for cell_bytes in bytes.chunks(CELL_BYTE_LEN) {
747                let addr = relocatable_from_bytes(cell_bytes[0..ADDR_BYTE_LEN].try_into().ok()?);
748                let field_bytes = &cell_bytes[ADDR_BYTE_LEN..CELL_BYTE_LEN];
749                // Check the last bit to determine if it is a Relocatable or Felt value
750                let value = if (field_bytes[field_bytes.len() - 1] & 0x80) != 0 {
751                    let (segment, offset) =
752                        relocatable_from_bytes(field_bytes[0..ADDR_BYTE_LEN].try_into().ok()?);
753                    MaybeRelocatable::from((segment as isize, offset))
754                } else {
755                    MaybeRelocatable::from(Felt252::from_bytes_le_slice(field_bytes))
756                };
757                res.push((addr, value));
758            }
759
760            Some(CairoPieMemory(res))
761        }
762    }
763
764    pub mod signature_additional_data {
765        use super::*;
766
767        pub fn serialize<S>(
768            values: &BTreeMap<Relocatable, (Felt252, Felt252)>,
769            serializer: S,
770        ) -> Result<S::Ok, S::Error>
771        where
772            S: Serializer,
773        {
774            let mut seq_serializer = serializer.serialize_seq(Some(values.len()))?;
775
776            for (key, (x, y)) in values {
777                seq_serializer.serialize_element(&[
778                    [
779                        Felt252Wrapper(&Felt252::from(key.segment_index)),
780                        Felt252Wrapper(&Felt252::from(key.offset)),
781                    ],
782                    [Felt252Wrapper(x), Felt252Wrapper(y)],
783                ])?;
784            }
785            seq_serializer.end()
786        }
787
788        pub fn deserialize<'de, D>(
789            d: D,
790        ) -> Result<BTreeMap<Relocatable, (Felt252, Felt252)>, D::Error>
791        where
792            D: Deserializer<'de>,
793        {
794            let number_map = Vec::<((Number, Number), (Number, Number))>::deserialize(d)?;
795            number_map
796                .into_iter()
797                .map(|((index, offset), (r, s))| {
798                    let idx = index
799                        .as_u64()
800                        .ok_or_else(|| D::Error::custom("Invalid address"))?
801                        as isize;
802                    let off = offset
803                        .as_u64()
804                        .ok_or_else(|| D::Error::custom("Invalid address"))?
805                        as usize;
806                    let addr = Relocatable::from((idx, off));
807                    let r = Felt252::from_dec_str(r.as_str())
808                        .map_err(|_| D::Error::custom("Invalid Felt252 value"))?;
809                    let s = Felt252::from_dec_str(s.as_str())
810                        .map_err(|_| D::Error::custom("Invalid Felt252 value"))?;
811                    Ok((addr, (r, s)))
812                })
813                .collect::<Result<BTreeMap<_, _>, D::Error>>()
814        }
815    }
816
817    pub mod hash_additional_data {
818        use super::*;
819
820        pub fn serialize<S>(values: &[Relocatable], serializer: S) -> Result<S::Ok, S::Error>
821        where
822            S: Serializer,
823        {
824            let mut seq_serializer: <S as Serializer>::SerializeSeq =
825                serializer.serialize_seq(Some(values.len()))?;
826
827            for value in values {
828                seq_serializer.serialize_element(&[value.segment_index, value.offset as isize])?;
829            }
830
831            seq_serializer.end()
832        }
833
834        pub fn deserialize<'de, D>(d: D) -> Result<Vec<Relocatable>, D::Error>
835        where
836            D: Deserializer<'de>,
837        {
838            let tuples = Vec::<(usize, usize)>::deserialize(d)?;
839            Ok(tuples
840                .into_iter()
841                .map(|(x, y)| Relocatable::from((x as isize, y)))
842                .collect())
843        }
844    }
845
846    pub fn serialize_builtin_segments<S>(
847        values: &BTreeMap<BuiltinName, SegmentInfo>,
848        serializer: S,
849    ) -> Result<S::Ok, S::Error>
850    where
851        S: Serializer,
852    {
853        let mut map_serializer = serializer.serialize_map(Some(values.len()))?;
854        const BUILTIN_ORDERED_LIST: &[BuiltinName] = &[
855            BuiltinName::output,
856            BuiltinName::pedersen,
857            BuiltinName::range_check,
858            BuiltinName::ecdsa,
859            BuiltinName::bitwise,
860            BuiltinName::ec_op,
861            BuiltinName::keccak,
862            BuiltinName::poseidon,
863            BuiltinName::range_check96,
864            BuiltinName::add_mod,
865            BuiltinName::mul_mod,
866        ];
867
868        for name in BUILTIN_ORDERED_LIST {
869            if let Some(info) = values.get(name) {
870                map_serializer.serialize_entry(name, info)?
871            }
872        }
873        map_serializer.end()
874    }
875}
876
877#[cfg(test)]
878mod test {
879    #[cfg(feature = "std")]
880    use {
881        crate::{
882            cairo_run::CairoRunConfig,
883            hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor,
884            types::layout_name::LayoutName,
885        },
886        rstest::rstest,
887    };
888
889    use super::*;
890
891    #[test]
892    fn serialize_cairo_pie_memory() {
893        let addrs = [
894            ((1, 0), "0000000000800080"),
895            ((1, 1), "0100000000800080"),
896            ((1, 4), "0400000000800080"),
897            ((1, 8), "0800000000800080"),
898            ((2, 0), "0000000000000180"),
899            ((5, 8), "0800000000800280"),
900        ];
901
902        let memory = CairoPieMemory(vec![
903            (addrs[0].0, MaybeRelocatable::Int(1234.into())),
904            (addrs[1].0, MaybeRelocatable::Int(11.into())),
905            (addrs[2].0, MaybeRelocatable::Int(12.into())),
906            (
907                addrs[3].0,
908                MaybeRelocatable::RelocatableValue((1, 2).into()),
909            ),
910            (
911                addrs[4].0,
912                MaybeRelocatable::RelocatableValue((3, 4).into()),
913            ),
914            (
915                addrs[5].0,
916                MaybeRelocatable::RelocatableValue((5, 6).into()),
917            ),
918        ]);
919
920        let mem = serde_json::to_value(memory).unwrap();
921        let mem_str = mem.as_str().unwrap();
922        let shift_len = (serde_impl::ADDR_BYTE_LEN + serde_impl::FIELD_BYTE_LEN) * 2;
923        let shift_field = serde_impl::FIELD_BYTE_LEN * 2;
924        let shift_addr = serde_impl::ADDR_BYTE_LEN * 2;
925
926        // Serializes Address 8 Byte(little endian):
927        for (i, expected_addr) in addrs.into_iter().enumerate() {
928            let shift = shift_len * i;
929            assert_eq!(
930                &mem_str[shift..shift + shift_addr],
931                expected_addr.1,
932                "addr mismatch({i}): {mem_str:?}",
933            );
934        }
935
936        // Serializes Int(little endian):
937        // 1bit | Num
938        // 0    | num
939        assert_eq!(
940            &mem_str[shift_addr..shift_addr + shift_field],
941            "d204000000000000000000000000000000000000000000000000000000000000",
942            "value mismatch: {mem_str:?}",
943        );
944        // Serializes RelocatableValue(little endian):
945        // 1bit |   SEGMENT_BITS |   OFFSET_BITS
946        // 1    |     segment    |   offset
947        let shift_first_relocatable = shift_len * 3 + shift_addr;
948        assert_eq!(
949            &mem_str[shift_first_relocatable..shift_first_relocatable + shift_field],
950            "0200000000800000000000000000000000000000000000000000000000000080",
951            "value mismatch: {mem_str:?}",
952        );
953    }
954
955    #[test]
956    fn serialize_cairo_pie_memory_with_overflow() {
957        let memory = CairoPieMemory(vec![
958            ((0, 0), MaybeRelocatable::Int(0.into())),
959            ((0, 1), MaybeRelocatable::Int(1.into())),
960            ((usize::MAX, 0), MaybeRelocatable::Int(2.into())),
961        ]);
962
963        serde_json::to_value(memory).unwrap_err();
964    }
965
966    #[rstest]
967    #[cfg(feature = "std")]
968    #[case(include_bytes!("../../../../cairo_programs/fibonacci.json"), "fibonacci")]
969    #[case(include_bytes!("../../../../cairo_programs/integration.json"), "integration")]
970    #[case(include_bytes!("../../../../cairo_programs/common_signature.json"), "signature")]
971    #[case(include_bytes!("../../../../cairo_programs/relocate_segments.json"), "relocate")]
972    #[case(include_bytes!("../../../../cairo_programs/ec_op.json"), "ec_op")]
973    #[case(include_bytes!("../../../../cairo_programs/bitwise_output.json"), "bitwise")]
974    #[case(include_bytes!("../../../../cairo_programs/value_beyond_segment.json"), "relocate_beyond")]
975    fn read_write_pie_zip(#[case] program_content: &[u8], #[case] identifier: &str) {
976        // Run a program to obtain the CairoPie
977        let cairo_pie = {
978            let cairo_run_config = CairoRunConfig {
979                layout: LayoutName::starknet_with_keccak,
980                ..Default::default()
981            };
982            let runner = crate::cairo_run::cairo_run(
983                program_content,
984                &cairo_run_config,
985                &mut BuiltinHintProcessor::new_empty(),
986            )
987            .unwrap();
988            runner.get_cairo_pie().unwrap()
989        };
990        // Serialize the CairoPie into a zip file
991        let filename = format!("temp_file_{}", identifier); // Identifier used to avoid name clashes
992        let file_path = Path::new(&filename);
993        cairo_pie.write_zip_file(file_path, false).unwrap();
994        // Deserialize the zip file
995        let deserialized_pie = CairoPie::read_zip_file(file_path).unwrap();
996        // Check that both pies are equal
997        assert_eq!(cairo_pie, deserialized_pie);
998        // Remove zip file created by the test
999        std::fs::remove_file(file_path).unwrap();
1000    }
1001
1002    #[test]
1003    #[cfg(feature = "std")]
1004    fn cairo_pie_with_extra_segments() {
1005        let program_content = include_bytes!("../../../../cairo_programs/fibonacci.json");
1006        let mut cairo_pie = {
1007            let cairo_run_config = CairoRunConfig {
1008                layout: LayoutName::starknet_with_keccak,
1009                ..Default::default()
1010            };
1011            let runner = crate::cairo_run::cairo_run(
1012                program_content,
1013                &cairo_run_config,
1014                &mut BuiltinHintProcessor::new_empty(),
1015            )
1016            .unwrap();
1017            runner.get_cairo_pie().unwrap()
1018        };
1019
1020        cairo_pie.metadata.extra_segments = vec![
1021            SegmentInfo { index: 8, size: 10 },
1022            SegmentInfo { index: 9, size: 20 },
1023        ];
1024        let memory = CairoPieMemory(vec![
1025            (
1026                (3, 4),
1027                MaybeRelocatable::RelocatableValue(Relocatable {
1028                    segment_index: 6,
1029                    offset: 7,
1030                }),
1031            ),
1032            (
1033                (8, 0),
1034                MaybeRelocatable::RelocatableValue(Relocatable {
1035                    segment_index: 8,
1036                    offset: 4,
1037                }),
1038            ),
1039            (
1040                (9, 3),
1041                MaybeRelocatable::RelocatableValue(Relocatable {
1042                    segment_index: 9,
1043                    offset: 7,
1044                }),
1045            ),
1046        ]);
1047
1048        cairo_pie.memory = memory;
1049
1050        let file_path = Path::new("merge_extra_segments_test");
1051
1052        cairo_pie.write_zip_file(file_path, true).unwrap();
1053
1054        let result_cairo_pie = CairoPie::read_zip_file(file_path).unwrap();
1055
1056        std::fs::remove_file(file_path).unwrap();
1057
1058        assert_eq!(
1059            result_cairo_pie.metadata.extra_segments,
1060            vec![SegmentInfo { index: 8, size: 30 }]
1061        );
1062        assert_eq!(
1063            result_cairo_pie.memory,
1064            CairoPieMemory(vec![
1065                (
1066                    (3, 4),
1067                    MaybeRelocatable::RelocatableValue(Relocatable {
1068                        segment_index: 6,
1069                        offset: 7
1070                    })
1071                ),
1072                (
1073                    (8, 0),
1074                    MaybeRelocatable::RelocatableValue(Relocatable {
1075                        segment_index: 8,
1076                        offset: 4
1077                    })
1078                ),
1079                (
1080                    (8, 13),
1081                    MaybeRelocatable::RelocatableValue(Relocatable {
1082                        segment_index: 8,
1083                        offset: 17
1084                    })
1085                ),
1086            ])
1087        )
1088    }
1089
1090    #[test]
1091    #[cfg(feature = "std")]
1092    fn cairo_pie_without_extra_segments() {
1093        let program_content = include_bytes!("../../../../cairo_programs/fibonacci.json");
1094        let mut cairo_pie = {
1095            let cairo_run_config = CairoRunConfig {
1096                layout: LayoutName::starknet_with_keccak,
1097                ..Default::default()
1098            };
1099            let runner = crate::cairo_run::cairo_run(
1100                program_content,
1101                &cairo_run_config,
1102                &mut BuiltinHintProcessor::new_empty(),
1103            )
1104            .unwrap();
1105            runner.get_cairo_pie().unwrap()
1106        };
1107
1108        cairo_pie.metadata.extra_segments = vec![];
1109        let memory = CairoPieMemory(vec![
1110            (
1111                (3, 4),
1112                MaybeRelocatable::RelocatableValue(Relocatable {
1113                    segment_index: 6,
1114                    offset: 7,
1115                }),
1116            ),
1117            (
1118                (8, 0),
1119                MaybeRelocatable::RelocatableValue(Relocatable {
1120                    segment_index: 8,
1121                    offset: 4,
1122                }),
1123            ),
1124            (
1125                (9, 3),
1126                MaybeRelocatable::RelocatableValue(Relocatable {
1127                    segment_index: 9,
1128                    offset: 7,
1129                }),
1130            ),
1131        ]);
1132
1133        cairo_pie.memory = memory.clone();
1134
1135        let file_path = Path::new("merge_without_extra_segments_test");
1136
1137        cairo_pie.write_zip_file(file_path, true).unwrap();
1138
1139        let result_cairo_pie = CairoPie::read_zip_file(file_path).unwrap();
1140
1141        std::fs::remove_file(file_path).unwrap();
1142
1143        assert_eq!(result_cairo_pie.metadata.extra_segments, vec![]);
1144        assert_eq!(result_cairo_pie.memory, memory)
1145    }
1146}