Skip to main content

cairo_vm/vm/runners/
cairo_pie.rs

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