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#[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
77pub 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 Empty([(); 0]),
93 #[serde(with = "serde_impl::hash_additional_data")]
95 Hash(Vec<Relocatable>),
96 Output(OutputBuiltinAdditionalData),
97 #[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 #[serde(with = "serde_impl::prime")]
163 pub prime: (),
164}
165
166#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
167pub struct CairoPieVersion {
168 #[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 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 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 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 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 #[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; pub const OFFSET_BASE: u64 = 0x800000000000; pub const RELOCATE_BASE: &str =
469 "8000000000000000000000000000000000000000000000000000000000000000"; 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 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 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 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 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 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 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 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 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 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 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 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 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 assert_eq!(
940 &mem_str[shift_addr..shift_addr + shift_field],
941 "d204000000000000000000000000000000000000000000000000000000000000",
942 "value mismatch: {mem_str:?}",
943 );
944 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 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 let filename = format!("temp_file_{}", identifier); let file_path = Path::new(&filename);
993 cairo_pie.write_zip_file(file_path, false).unwrap();
994 let deserialized_pie = CairoPie::read_zip_file(file_path).unwrap();
996 assert_eq!(cairo_pie, deserialized_pie);
998 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}