1use ark_ff::PrimeField;
18use std::fmt;
19
20use crate::circuits::wires::PERMUTS;
21
22pub const FILE_MAGIC: [u8; 8] = *b"MINAPK01";
25
26pub const FORMAT_VERSION: u32 = 3;
39
40pub const IDENTIFIER_MAX_LEN: usize = 512;
45
46pub const ARK_FF_VERSION_MAX_LEN: usize = 32;
48
49pub const SECTION_ALIGNMENT: usize = 32;
54
55pub const FIELD_ELEMENT_BYTES: usize = 32;
58
59#[cfg(target_endian = "big")]
63compile_error!(
64 "cached_prover_index requires a little-endian target; the on-disk \
65 layout stores field-element limbs as little-endian u64."
66);
67
68#[repr(u32)]
78#[derive(Copy, Clone, Debug, PartialEq, Eq)]
79pub enum SectionTag {
80 Sid = 0x01,
82 Gates = 0x02,
84 GateCoeffs = 0x03,
88 Coefficients8Base = 0x10,
91 GenericSelector4 = 0x20,
93 PoseidonSelector8 = 0x21,
95 CompleteAddSelector4 = 0x22,
97 MulSelector8 = 0x23,
99 EmulSelector8 = 0x24,
101 EndomulScalarSelector8 = 0x25,
103 PermutationCoefficients8Base = 0x30,
106 RangeCheck0Selector8 = 0x40,
108 RangeCheck1Selector8 = 0x41,
110 ForeignFieldAddSelector8 = 0x42,
112 ForeignFieldMulSelector8 = 0x43,
114 XorSelector8 = 0x44,
116 RotSelector8 = 0x45,
118 LookupTable8 = 0x50,
123 TableIds8 = 0x51,
125 LookupSelectorXor = 0x52,
127 LookupSelectorLookup = 0x53,
129 LookupSelectorRangeCheck = 0x54,
131 LookupSelectorFfmul = 0x55,
133 RuntimeSelector8 = 0x56,
135 RuntimeTablesSpec = 0x57,
137 RuntimeTableOffset = 0x58,
139}
140
141impl SectionTag {
142 pub fn to_u32(self) -> u32 {
143 self as u32
144 }
145}
146
147const _: () = assert!(
153 COLUMNS <= 0x20 - 0x10,
154 "coefficient section tags would collide with GenericSelector4 (0x20)"
155);
156const _: () = assert!(
157 PERMUTS <= 0x40 - 0x30,
158 "permutation-coefficient section tags would collide with RangeCheck0Selector8 (0x40)"
159);
160
161pub fn coefficient_tag(i: usize) -> u32 {
163 assert!(i < COLUMNS, "coefficient index out of range");
164 SectionTag::Coefficients8Base as u32 + i as u32
165}
166
167pub fn permutation_coefficient_tag(i: usize) -> u32 {
170 assert!(i < PERMUTS, "permutation coefficient index out of range");
171 SectionTag::PermutationCoefficients8Base as u32 + i as u32
172}
173
174#[derive(Clone, Debug)]
181pub struct ScalarHeader {
182 pub public: u32,
183 pub prev_challenges: u32,
184 pub zk_rows: u64,
185 pub max_poly_size: u64,
186 pub disable_gates_checks: bool,
187 pub domain_d1_size: u64,
190 pub feature_flags: u32,
192 pub optional_selectors_present: u32,
195 pub lookup_selectors_present: u32,
197 pub has_verifier_index_digest: bool,
199 pub endo_limbs: [u64; 4],
201 pub shift_limbs: [[u64; 4]; PERMUTS],
203 pub verifier_index_digest_limbs: [u64; 4],
206}
207
208impl ScalarHeader {
209 pub const SERIALIZED_SIZE: usize = 4 + 4 + 8 + 8 + 1 + 7 + 8 + 4 + 4 + 4 + 1 + 3 + 32 + 32 * PERMUTS + 32; }
227
228pub struct FeatureFlagBits;
230impl FeatureFlagBits {
231 pub const RANGE_CHECK_0: u32 = 1 << 0;
232 pub const RANGE_CHECK_1: u32 = 1 << 1;
233 pub const FOREIGN_FIELD_ADD: u32 = 1 << 2;
234 pub const FOREIGN_FIELD_MUL: u32 = 1 << 3;
235 pub const XOR: u32 = 1 << 4;
236 pub const ROT: u32 = 1 << 5;
237 pub const LOOKUP_PATTERN_XOR: u32 = 1 << 8;
238 pub const LOOKUP_PATTERN_LOOKUP: u32 = 1 << 9;
239 pub const LOOKUP_PATTERN_RANGE_CHECK: u32 = 1 << 10;
240 pub const LOOKUP_PATTERN_FOREIGN_FIELD_MUL: u32 = 1 << 11;
241 pub const LOOKUP_JOINT_USED: u32 = 1 << 12;
242 pub const LOOKUP_USES_RUNTIME_TABLES: u32 = 1 << 13;
243}
244
245pub struct OptionalSelectorBits;
247impl OptionalSelectorBits {
248 pub const RANGE_CHECK_0: u32 = 1 << 0;
249 pub const RANGE_CHECK_1: u32 = 1 << 1;
250 pub const FOREIGN_FIELD_ADD: u32 = 1 << 2;
251 pub const FOREIGN_FIELD_MUL: u32 = 1 << 3;
252 pub const XOR: u32 = 1 << 4;
253 pub const ROT: u32 = 1 << 5;
254}
255
256pub struct LookupSelectorBits;
259impl LookupSelectorBits {
260 pub const TABLE_IDS8: u32 = 1 << 0;
261 pub const SELECTOR_XOR: u32 = 1 << 1;
262 pub const SELECTOR_LOOKUP: u32 = 1 << 2;
263 pub const SELECTOR_RANGE_CHECK: u32 = 1 << 3;
264 pub const SELECTOR_FFMUL: u32 = 1 << 4;
265 pub const RUNTIME_SELECTOR: u32 = 1 << 5;
266 pub const RUNTIME_TABLES: u32 = 1 << 6;
267 pub const RUNTIME_TABLE_OFFSET: u32 = 1 << 7;
268}
269
270#[repr(C)]
278#[derive(Clone, Copy, Debug, PartialEq, Eq)]
279pub struct PrunedGate {
280 pub typ_tag: u16,
283 _pad: [u8; 2],
285 pub wires: [PrunedWire; PERMUTS],
287}
288
289impl PrunedGate {
290 pub const fn new(typ_tag: u16, wires: [PrunedWire; PERMUTS]) -> Self {
291 Self {
292 typ_tag,
293 _pad: [0; 2],
294 wires,
295 }
296 }
297}
298
299#[repr(C)]
301#[derive(Clone, Copy, Debug, PartialEq, Eq)]
302pub struct PrunedWire {
303 pub row: u32,
304 pub col: u32,
305}
306
307impl PrunedWire {
308 pub const fn new(row: u32, col: u32) -> Self {
309 Self { row, col }
310 }
311}
312
313#[derive(Clone, Copy, Debug)]
315pub struct SectionEntry {
316 pub tag: u32,
317 pub offset: u64,
319 pub length: u64,
321 pub elem_domain_size: u32,
324 pub _reserved: u32,
326}
327
328impl SectionEntry {
329 pub const SERIALIZED_SIZE: usize = 4 + 8 + 8 + 4 + 4;
330}
331
332#[derive(Debug)]
334pub enum CacheError {
335 Io(std::io::Error),
336 BadMagic {
337 found: [u8; 8],
338 },
339 UnsupportedFormatVersion {
340 found: u32,
341 supported: u32,
342 },
343 ArkFfVersionMismatch {
344 found: String,
345 expected: String,
346 },
347 IdentifierTooLong {
348 len: usize,
349 max: usize,
350 },
351 IdentifierMismatch {
352 found: String,
353 expected: String,
354 },
355 IdentifierInvalidUtf8,
356 DuplicateSectionTag {
357 tag: u32,
358 },
359 MissingSection {
360 tag: u32,
361 },
362 SectionLengthMismatch {
363 tag: u32,
364 expected: u64,
365 found: u64,
366 },
367 MisalignedSection {
368 tag: u32,
369 offset: u64,
370 },
371 TruncatedFile,
372 PayloadNotFieldAligned {
375 tag: u32,
376 length: u64,
377 },
378 UnknownFeatureFlagBits {
380 bits: u32,
381 },
382 LookupConstraintSystem(String),
386 UnknownGateType {
388 tag: u16,
389 },
390 InvalidDomainSize {
393 size: u64,
394 },
395}
396
397impl fmt::Display for CacheError {
398 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399 match self {
400 CacheError::Io(e) => write!(f, "i/o error: {e}"),
401 CacheError::BadMagic { found } => {
402 write!(f, "bad file magic: {found:?} (expected {FILE_MAGIC:?})")
403 }
404 CacheError::UnsupportedFormatVersion { found, supported } => write!(
405 f,
406 "unsupported cache format version {found} (this build supports {supported})"
407 ),
408 CacheError::ArkFfVersionMismatch { found, expected } => write!(
409 f,
410 "ark-ff version mismatch: file declares {found}, binary was built with {expected}"
411 ),
412 CacheError::IdentifierTooLong { len, max } => {
413 write!(f, "identifier length {len} exceeds maximum {max}")
414 }
415 CacheError::IdentifierMismatch { found, expected } => write!(
416 f,
417 "cache file identifier mismatch: file contains {found:?}, caller provided {expected:?}"
418 ),
419 CacheError::IdentifierInvalidUtf8 => write!(f, "cache file identifier is not valid UTF-8"),
420 CacheError::DuplicateSectionTag { tag } => {
421 write!(f, "duplicate section tag {tag:#x} in section table")
422 }
423 CacheError::MissingSection { tag } => {
424 write!(f, "required section tag {tag:#x} missing from section table")
425 }
426 CacheError::SectionLengthMismatch { tag, expected, found } => write!(
427 f,
428 "section {tag:#x} length mismatch: expected {expected}, found {found}"
429 ),
430 CacheError::MisalignedSection { tag, offset } => write!(
431 f,
432 "section {tag:#x} offset {offset} is not {SECTION_ALIGNMENT}-byte aligned"
433 ),
434 CacheError::TruncatedFile => write!(f, "cache file truncated before end of declared payload"),
435 CacheError::PayloadNotFieldAligned { tag, length } => write!(
436 f,
437 "section {tag:#x} payload length {length} is not a multiple of {FIELD_ELEMENT_BYTES}"
438 ),
439 CacheError::UnknownFeatureFlagBits { bits } => {
440 write!(f, "unknown bits {bits:#x} in feature-flags bitmap")
441 }
442 CacheError::LookupConstraintSystem(msg) => {
443 write!(f, "failed to build lookup constraint system for cache: {msg}")
444 }
445 CacheError::UnknownGateType { tag } => {
446 write!(f, "unknown gate type tag {tag} in pruned gate")
447 }
448 CacheError::InvalidDomainSize { size } => {
449 write!(f, "stored d1 domain size {size} is not a valid evaluation domain")
450 }
451 }
452 }
453}
454
455impl std::error::Error for CacheError {
456 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
457 match self {
458 CacheError::Io(e) => Some(e),
459 _ => None,
460 }
461 }
462}
463
464impl From<std::io::Error> for CacheError {
465 fn from(e: std::io::Error) -> Self {
466 CacheError::Io(e)
467 }
468}
469
470pub const ARK_FF_VERSION: &str = "ark-ff-0.5";
474
475pub fn align_up(n: usize) -> usize {
477 (n + SECTION_ALIGNMENT - 1) & !(SECTION_ALIGNMENT - 1)
478}
479
480pub fn alignment_padding(n: usize) -> usize {
483 align_up(n) - n
484}
485
486pub fn field_to_limbs<F: PrimeField>(f: &F) -> [u64; 4] {
503 debug_assert_eq!(
504 core::mem::size_of::<F>(),
505 FIELD_ELEMENT_BYTES,
506 "field_to_limbs assumes F has the memory layout of [u64; 4]"
507 );
508 debug_assert_eq!(
509 core::mem::align_of::<F>(),
510 core::mem::align_of::<[u64; 4]>(),
511 "field_to_limbs assumes F shares alignment with [u64; 4]"
512 );
513 unsafe { core::ptr::read(f as *const F as *const [u64; 4]) }
518}
519
520pub fn limbs_to_field<F: PrimeField>(limbs: &[u64; 4]) -> F {
524 debug_assert_eq!(
525 core::mem::size_of::<F>(),
526 FIELD_ELEMENT_BYTES,
527 "limbs_to_field assumes F has the memory layout of [u64; 4]"
528 );
529 debug_assert_eq!(
530 core::mem::align_of::<F>(),
531 core::mem::align_of::<[u64; 4]>(),
532 "limbs_to_field assumes F shares alignment with [u64; 4]"
533 );
534 unsafe { core::ptr::read(limbs as *const [u64; 4] as *const F) }
539}
540
541pub const PREAMBLE_SIZE: usize = 8 + 4 + 4 + ARK_FF_VERSION_MAX_LEN
552 + 4 + IDENTIFIER_MAX_LEN
554 + 4; pub const SECTION_TABLE_OFFSET: usize = PREAMBLE_SIZE + ScalarHeader::SERIALIZED_SIZE;
559
560fn write_u32_le(out: &mut Vec<u8>, value: u32) {
561 out.extend_from_slice(&value.to_le_bytes());
562}
563
564fn write_u64_le(out: &mut Vec<u8>, value: u64) {
565 out.extend_from_slice(&value.to_le_bytes());
566}
567
568fn read_u32_le(bytes: &[u8]) -> Result<(u32, &[u8]), CacheError> {
569 if bytes.len() < 4 {
570 return Err(CacheError::TruncatedFile);
571 }
572 let (head, tail) = bytes.split_at(4);
573 let mut buf = [0u8; 4];
574 buf.copy_from_slice(head);
575 Ok((u32::from_le_bytes(buf), tail))
576}
577
578fn read_u64_le(bytes: &[u8]) -> Result<(u64, &[u8]), CacheError> {
579 if bytes.len() < 8 {
580 return Err(CacheError::TruncatedFile);
581 }
582 let (head, tail) = bytes.split_at(8);
583 let mut buf = [0u8; 8];
584 buf.copy_from_slice(head);
585 Ok((u64::from_le_bytes(buf), tail))
586}
587
588fn read_exact(bytes: &[u8], n: usize) -> Result<(&[u8], &[u8]), CacheError> {
589 if bytes.len() < n {
590 return Err(CacheError::TruncatedFile);
591 }
592 Ok(bytes.split_at(n))
593}
594
595fn pad_string(out: &mut Vec<u8>, s: &str, max: usize) -> Result<(), CacheError> {
598 if s.len() > max {
599 return Err(CacheError::IdentifierTooLong { len: s.len(), max });
600 }
601 out.extend_from_slice(s.as_bytes());
602 out.resize(out.len() + (max - s.len()), 0);
603 Ok(())
604}
605
606fn unpad_string(bytes: &[u8]) -> Result<&str, CacheError> {
608 let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
609 core::str::from_utf8(&bytes[..end]).map_err(|_| CacheError::IdentifierInvalidUtf8)
610}
611
612impl ScalarHeader {
613 pub fn write(&self, out: &mut Vec<u8>) {
614 write_u32_le(out, self.public);
615 write_u32_le(out, self.prev_challenges);
616 write_u64_le(out, self.zk_rows);
617 write_u64_le(out, self.max_poly_size);
618 out.push(self.disable_gates_checks as u8);
619 out.extend_from_slice(&[0u8; 7]);
621 write_u64_le(out, self.domain_d1_size);
622 write_u32_le(out, self.feature_flags);
623 write_u32_le(out, self.optional_selectors_present);
624 write_u32_le(out, self.lookup_selectors_present);
625 out.push(self.has_verifier_index_digest as u8);
626 out.extend_from_slice(&[0u8; 3]);
627 for limb in &self.endo_limbs {
628 write_u64_le(out, *limb);
629 }
630 for row in &self.shift_limbs {
631 for limb in row {
632 write_u64_le(out, *limb);
633 }
634 }
635 for limb in &self.verifier_index_digest_limbs {
636 write_u64_le(out, *limb);
637 }
638 }
639
640 pub fn read(bytes: &[u8]) -> Result<(Self, &[u8]), CacheError> {
641 let (public, bytes) = read_u32_le(bytes)?;
642 let (prev_challenges, bytes) = read_u32_le(bytes)?;
643 let (zk_rows, bytes) = read_u64_le(bytes)?;
644 let (max_poly_size, bytes) = read_u64_le(bytes)?;
645 let (disable_b, bytes) = read_exact(bytes, 1)?;
646 let disable_gates_checks = disable_b[0] != 0;
647 let (_pad, bytes) = read_exact(bytes, 7)?;
648 let (domain_d1_size, bytes) = read_u64_le(bytes)?;
649 let (feature_flags, bytes) = read_u32_le(bytes)?;
650 let (optional_selectors_present, bytes) = read_u32_le(bytes)?;
651 let (lookup_selectors_present, bytes) = read_u32_le(bytes)?;
652 let (has_vkd_b, bytes) = read_exact(bytes, 1)?;
653 let has_verifier_index_digest = has_vkd_b[0] != 0;
654 let (_pad, bytes) = read_exact(bytes, 3)?;
655 let mut endo_limbs = [0u64; 4];
656 let mut bytes = bytes;
657 for limb in endo_limbs.iter_mut() {
658 let (v, rest) = read_u64_le(bytes)?;
659 *limb = v;
660 bytes = rest;
661 }
662 let mut shift_limbs = [[0u64; 4]; PERMUTS];
663 for row in shift_limbs.iter_mut() {
664 for limb in row.iter_mut() {
665 let (v, rest) = read_u64_le(bytes)?;
666 *limb = v;
667 bytes = rest;
668 }
669 }
670 let mut verifier_index_digest_limbs = [0u64; 4];
671 for limb in verifier_index_digest_limbs.iter_mut() {
672 let (v, rest) = read_u64_le(bytes)?;
673 *limb = v;
674 bytes = rest;
675 }
676 Ok((
677 Self {
678 public,
679 prev_challenges,
680 zk_rows,
681 max_poly_size,
682 disable_gates_checks,
683 domain_d1_size,
684 feature_flags,
685 optional_selectors_present,
686 lookup_selectors_present,
687 has_verifier_index_digest,
688 endo_limbs,
689 shift_limbs,
690 verifier_index_digest_limbs,
691 },
692 bytes,
693 ))
694 }
695}
696
697impl SectionEntry {
698 pub fn write(&self, out: &mut Vec<u8>) {
699 write_u32_le(out, self.tag);
700 write_u64_le(out, self.offset);
701 write_u64_le(out, self.length);
702 write_u32_le(out, self.elem_domain_size);
703 write_u32_le(out, self._reserved);
704 }
705
706 pub fn read(bytes: &[u8]) -> Result<(Self, &[u8]), CacheError> {
707 let (tag, bytes) = read_u32_le(bytes)?;
708 let (offset, bytes) = read_u64_le(bytes)?;
709 let (length, bytes) = read_u64_le(bytes)?;
710 let (elem_domain_size, bytes) = read_u32_le(bytes)?;
711 let (_reserved, bytes) = read_u32_le(bytes)?;
712 Ok((
713 Self {
714 tag,
715 offset,
716 length,
717 elem_domain_size,
718 _reserved,
719 },
720 bytes,
721 ))
722 }
723}
724
725fn write_preamble(
730 out: &mut Vec<u8>,
731 identifier: &str,
732 num_sections: u32,
733) -> Result<(), CacheError> {
734 out.extend_from_slice(&FILE_MAGIC);
735 write_u32_le(out, FORMAT_VERSION);
736 write_u32_le(out, 0); pad_string(out, ARK_FF_VERSION, ARK_FF_VERSION_MAX_LEN)?;
738 write_u32_le(out, identifier.len() as u32);
739 pad_string(out, identifier, IDENTIFIER_MAX_LEN)?;
740 write_u32_le(out, num_sections);
741 Ok(())
742}
743
744pub struct Preamble {
746 pub format_version: u32,
747 pub ark_ff_version: String,
748 pub identifier: String,
749 pub num_sections: u32,
750}
751
752fn read_preamble(bytes: &[u8]) -> Result<(Preamble, &[u8]), CacheError> {
753 let (magic, bytes) = read_exact(bytes, 8)?;
754 let mut magic_arr = [0u8; 8];
755 magic_arr.copy_from_slice(magic);
756 if magic_arr != FILE_MAGIC {
757 return Err(CacheError::BadMagic { found: magic_arr });
758 }
759 let (format_version, bytes) = read_u32_le(bytes)?;
760 if format_version != FORMAT_VERSION {
761 return Err(CacheError::UnsupportedFormatVersion {
762 found: format_version,
763 supported: FORMAT_VERSION,
764 });
765 }
766 let (_reserved_flags, bytes) = read_u32_le(bytes)?;
767 let (ark_ff, bytes) = read_exact(bytes, ARK_FF_VERSION_MAX_LEN)?;
768 let ark_ff_version = unpad_string(ark_ff)?.to_owned();
769 if ark_ff_version != ARK_FF_VERSION {
770 return Err(CacheError::ArkFfVersionMismatch {
771 found: ark_ff_version,
772 expected: ARK_FF_VERSION.to_owned(),
773 });
774 }
775 let (id_len, bytes) = read_u32_le(bytes)?;
776 let (id_field, bytes) = read_exact(bytes, IDENTIFIER_MAX_LEN)?;
777 let id_len_usize = id_len as usize;
778 if id_len_usize > IDENTIFIER_MAX_LEN {
779 return Err(CacheError::IdentifierTooLong {
780 len: id_len_usize,
781 max: IDENTIFIER_MAX_LEN,
782 });
783 }
784 let identifier = core::str::from_utf8(&id_field[..id_len_usize])
785 .map_err(|_| CacheError::IdentifierInvalidUtf8)?
786 .to_owned();
787 let (num_sections, bytes) = read_u32_le(bytes)?;
788 Ok((
789 Preamble {
790 format_version,
791 ark_ff_version,
792 identifier,
793 num_sections,
794 },
795 bytes,
796 ))
797}
798
799use crate::circuits::{
804 constraints::FeatureFlags,
805 lookup::lookups::{LookupFeatures, LookupPatterns},
806};
807
808fn pack_feature_flags(flags: &FeatureFlags) -> u32 {
809 let mut bits = 0u32;
810 if flags.range_check0 {
811 bits |= FeatureFlagBits::RANGE_CHECK_0;
812 }
813 if flags.range_check1 {
814 bits |= FeatureFlagBits::RANGE_CHECK_1;
815 }
816 if flags.foreign_field_add {
817 bits |= FeatureFlagBits::FOREIGN_FIELD_ADD;
818 }
819 if flags.foreign_field_mul {
820 bits |= FeatureFlagBits::FOREIGN_FIELD_MUL;
821 }
822 if flags.xor {
823 bits |= FeatureFlagBits::XOR;
824 }
825 if flags.rot {
826 bits |= FeatureFlagBits::ROT;
827 }
828 if flags.lookup_features.patterns.xor {
829 bits |= FeatureFlagBits::LOOKUP_PATTERN_XOR;
830 }
831 if flags.lookup_features.patterns.lookup {
832 bits |= FeatureFlagBits::LOOKUP_PATTERN_LOOKUP;
833 }
834 if flags.lookup_features.patterns.range_check {
835 bits |= FeatureFlagBits::LOOKUP_PATTERN_RANGE_CHECK;
836 }
837 if flags.lookup_features.patterns.foreign_field_mul {
838 bits |= FeatureFlagBits::LOOKUP_PATTERN_FOREIGN_FIELD_MUL;
839 }
840 if flags.lookup_features.joint_lookup_used {
841 bits |= FeatureFlagBits::LOOKUP_JOINT_USED;
842 }
843 if flags.lookup_features.uses_runtime_tables {
844 bits |= FeatureFlagBits::LOOKUP_USES_RUNTIME_TABLES;
845 }
846 bits
847}
848
849fn unpack_feature_flags(bits: u32) -> Result<FeatureFlags, CacheError> {
850 let known = FeatureFlagBits::RANGE_CHECK_0
851 | FeatureFlagBits::RANGE_CHECK_1
852 | FeatureFlagBits::FOREIGN_FIELD_ADD
853 | FeatureFlagBits::FOREIGN_FIELD_MUL
854 | FeatureFlagBits::XOR
855 | FeatureFlagBits::ROT
856 | FeatureFlagBits::LOOKUP_PATTERN_XOR
857 | FeatureFlagBits::LOOKUP_PATTERN_LOOKUP
858 | FeatureFlagBits::LOOKUP_PATTERN_RANGE_CHECK
859 | FeatureFlagBits::LOOKUP_PATTERN_FOREIGN_FIELD_MUL
860 | FeatureFlagBits::LOOKUP_JOINT_USED
861 | FeatureFlagBits::LOOKUP_USES_RUNTIME_TABLES;
862 let extra = bits & !known;
863 if extra != 0 {
864 return Err(CacheError::UnknownFeatureFlagBits { bits: extra });
865 }
866 Ok(FeatureFlags {
867 range_check0: bits & FeatureFlagBits::RANGE_CHECK_0 != 0,
868 range_check1: bits & FeatureFlagBits::RANGE_CHECK_1 != 0,
869 foreign_field_add: bits & FeatureFlagBits::FOREIGN_FIELD_ADD != 0,
870 foreign_field_mul: bits & FeatureFlagBits::FOREIGN_FIELD_MUL != 0,
871 xor: bits & FeatureFlagBits::XOR != 0,
872 rot: bits & FeatureFlagBits::ROT != 0,
873 lookup_features: LookupFeatures {
874 patterns: LookupPatterns {
875 xor: bits & FeatureFlagBits::LOOKUP_PATTERN_XOR != 0,
876 lookup: bits & FeatureFlagBits::LOOKUP_PATTERN_LOOKUP != 0,
877 range_check: bits & FeatureFlagBits::LOOKUP_PATTERN_RANGE_CHECK != 0,
878 foreign_field_mul: bits & FeatureFlagBits::LOOKUP_PATTERN_FOREIGN_FIELD_MUL != 0,
879 },
880 joint_lookup_used: bits & FeatureFlagBits::LOOKUP_JOINT_USED != 0,
881 uses_runtime_tables: bits & FeatureFlagBits::LOOKUP_USES_RUNTIME_TABLES != 0,
882 },
883 })
884}
885
886fn write_field_slice<F: PrimeField>(out: &mut Vec<u8>, elements: &[F]) {
893 for elt in elements {
894 let limbs = field_to_limbs(elt);
895 for limb in &limbs {
896 out.extend_from_slice(&limb.to_le_bytes());
897 }
898 }
899}
900
901fn validate_field_section(tag: u32, bytes: &[u8], count: usize) -> Result<(), CacheError> {
906 let expected = count
907 .checked_mul(FIELD_ELEMENT_BYTES)
908 .ok_or(CacheError::TruncatedFile)?;
909 if bytes.len() != expected {
910 return Err(CacheError::PayloadNotFieldAligned {
911 tag,
912 length: bytes.len() as u64,
913 });
914 }
915 Ok(())
916}
917
918unsafe fn mmap_field_vec_unchecked<F: PrimeField>(bytes: &[u8], count: usize) -> Vec<F> {
942 debug_assert_eq!(bytes.len(), count * FIELD_ELEMENT_BYTES);
943 debug_assert!(
947 (bytes.as_ptr() as usize).is_multiple_of(core::mem::align_of::<F>()),
948 "mmap section pointer not aligned for F"
949 );
950 let ptr = bytes.as_ptr() as *mut F;
951 Vec::from_raw_parts(ptr, count, count)
954}
955
956use crate::circuits::{
961 gate::{CircuitGate, GateType},
962 wires::{GateWires, Wire},
963};
964
965fn gate_type_to_tag(t: GateType) -> u16 {
966 match t {
970 GateType::Zero => 0,
971 GateType::Generic => 1,
972 GateType::Poseidon => 2,
973 GateType::CompleteAdd => 3,
974 GateType::VarBaseMul => 4,
975 GateType::EndoMul => 5,
976 GateType::EndoMulScalar => 6,
977 GateType::Lookup => 7,
978 GateType::RangeCheck0 => 8,
979 GateType::RangeCheck1 => 9,
980 GateType::ForeignFieldAdd => 10,
981 GateType::ForeignFieldMul => 11,
982 GateType::Xor16 => 12,
983 GateType::Rot64 => 13,
984 }
985}
986
987fn gate_type_from_tag(t: u16) -> Option<GateType> {
988 Some(match t {
989 0 => GateType::Zero,
990 1 => GateType::Generic,
991 2 => GateType::Poseidon,
992 3 => GateType::CompleteAdd,
993 4 => GateType::VarBaseMul,
994 5 => GateType::EndoMul,
995 6 => GateType::EndoMulScalar,
996 7 => GateType::Lookup,
997 8 => GateType::RangeCheck0,
998 9 => GateType::RangeCheck1,
999 10 => GateType::ForeignFieldAdd,
1000 11 => GateType::ForeignFieldMul,
1001 12 => GateType::Xor16,
1002 13 => GateType::Rot64,
1003 _ => return None,
1004 })
1005}
1006
1007fn write_pruned_gate(out: &mut Vec<u8>, gate: &CircuitGate<impl PrimeField>) {
1008 let typ_tag = gate_type_to_tag(gate.typ);
1009 out.extend_from_slice(&typ_tag.to_le_bytes());
1010 out.extend_from_slice(&[0u8; 2]);
1011 for wire in &gate.wires {
1012 out.extend_from_slice(&(wire.row as u32).to_le_bytes());
1013 out.extend_from_slice(&(wire.col as u32).to_le_bytes());
1014 }
1015}
1016
1017pub const PRUNED_GATE_SIZE: usize = 2 + 2 + 2 * 4 * PERMUTS;
1020
1021fn read_pruned_gate<F: PrimeField>(bytes: &[u8]) -> Result<CircuitGate<F>, CacheError> {
1022 if bytes.len() != PRUNED_GATE_SIZE {
1023 return Err(CacheError::TruncatedFile);
1024 }
1025 let mut typ_bytes = [0u8; 2];
1026 typ_bytes.copy_from_slice(&bytes[0..2]);
1027 let typ_tag = u16::from_le_bytes(typ_bytes);
1028 let typ = gate_type_from_tag(typ_tag).ok_or(CacheError::UnknownGateType { tag: typ_tag })?;
1029 let mut wires: GateWires = [Wire::default(); PERMUTS];
1030 for (i, wire) in wires.iter_mut().enumerate() {
1031 let base = 4 + i * 8;
1032 let mut row_bytes = [0u8; 4];
1033 let mut col_bytes = [0u8; 4];
1034 row_bytes.copy_from_slice(&bytes[base..base + 4]);
1035 col_bytes.copy_from_slice(&bytes[base + 4..base + 8]);
1036 *wire = Wire {
1037 row: u32::from_le_bytes(row_bytes) as usize,
1038 col: u32::from_le_bytes(col_bytes) as usize,
1039 };
1040 }
1041 Ok(CircuitGate::new(typ, wires, Vec::new()))
1042}
1043
1044use crate::{
1049 circuits::{
1050 constraints::{ColumnEvaluations, ConstraintSystem},
1051 domains::EvaluationDomains,
1052 lookup::{
1053 constraints::LookupConfiguration,
1054 index::{LookupConstraintSystem, LookupSelectors},
1055 lookups::LookupInfo,
1056 runtime_tables::RuntimeTableSpec,
1057 },
1058 wires::COLUMNS,
1059 },
1060 curve::KimchiCurve,
1061 linearization::expr_linearization,
1062 o1_utils::lazy_cache::LazyCache,
1063 prover_index::ProverIndex,
1064};
1065use ark_poly::{EvaluationDomain, Evaluations, Radix2EvaluationDomain};
1066use poly_commitment::SRS;
1067use std::{fs::OpenOptions, io::Write as _, os::unix::io::AsRawFd, path::Path, sync::Arc};
1068
1069struct ReadOnlyMmap {
1074 ptr: *const u8,
1075 len: usize,
1076}
1077
1078unsafe impl Send for ReadOnlyMmap {}
1083unsafe impl Sync for ReadOnlyMmap {}
1084
1085impl ReadOnlyMmap {
1086 fn map_file(file: &std::fs::File) -> std::io::Result<Self> {
1087 let len = file.metadata()?.len() as usize;
1088 if len == 0 {
1089 return Ok(Self {
1090 ptr: core::ptr::NonNull::<u8>::dangling().as_ptr(),
1091 len: 0,
1092 });
1093 }
1094 let ptr = unsafe {
1097 libc::mmap(
1098 core::ptr::null_mut(),
1099 len,
1100 libc::PROT_READ,
1101 libc::MAP_SHARED,
1102 file.as_raw_fd(),
1103 0,
1104 )
1105 };
1106 if ptr == libc::MAP_FAILED {
1107 return Err(std::io::Error::last_os_error());
1108 }
1109 Ok(Self {
1110 ptr: ptr as *const u8,
1111 len,
1112 })
1113 }
1114
1115 fn as_slice(&self) -> &[u8] {
1116 if self.len == 0 {
1117 &[]
1118 } else {
1119 unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
1122 }
1123 }
1124}
1125
1126impl Drop for ReadOnlyMmap {
1127 fn drop(&mut self) {
1128 if self.len != 0 {
1129 unsafe {
1132 libc::munmap(self.ptr as *mut libc::c_void, self.len);
1133 }
1134 }
1135 }
1136}
1137
1138impl<const FULL_ROUNDS: usize, G, Srs> MmapProverIndex<FULL_ROUNDS, G, Srs>
1139where
1140 G: KimchiCurve<FULL_ROUNDS>,
1141{
1142 pub fn madvise_dontneed(&self) {
1155 if self._mmap.len == 0 {
1156 return;
1157 }
1158 unsafe {
1164 libc::madvise(
1165 self._mmap.ptr as *mut libc::c_void,
1166 self._mmap.len,
1167 libc::MADV_DONTNEED,
1168 );
1169 }
1170 }
1171}
1172
1173pub struct MmapProverIndex<const FULL_ROUNDS: usize, G: KimchiCurve<FULL_ROUNDS>, Srs> {
1210 index: core::mem::ManuallyDrop<ProverIndex<FULL_ROUNDS, G, Srs>>,
1211 _mmap: Arc<ReadOnlyMmap>,
1212}
1213
1214impl<const FULL_ROUNDS: usize, G, Srs> core::ops::Deref for MmapProverIndex<FULL_ROUNDS, G, Srs>
1215where
1216 G: KimchiCurve<FULL_ROUNDS>,
1217{
1218 type Target = ProverIndex<FULL_ROUNDS, G, Srs>;
1219 fn deref(&self) -> &Self::Target {
1220 &self.index
1221 }
1222}
1223
1224impl<const FULL_ROUNDS: usize, G, Srs> core::fmt::Debug for MmapProverIndex<FULL_ROUNDS, G, Srs>
1225where
1226 G: KimchiCurve<FULL_ROUNDS>,
1227{
1228 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1229 f.debug_struct("MmapProverIndex")
1230 .field("mmap_len", &self._mmap.len)
1231 .finish_non_exhaustive()
1232 }
1233}
1234
1235unsafe impl<const FULL_ROUNDS: usize, G, Srs> Send for MmapProverIndex<FULL_ROUNDS, G, Srs>
1238where
1239 G: KimchiCurve<FULL_ROUNDS>,
1240 ProverIndex<FULL_ROUNDS, G, Srs>: Send,
1241{
1242}
1243unsafe impl<const FULL_ROUNDS: usize, G, Srs> Sync for MmapProverIndex<FULL_ROUNDS, G, Srs>
1244where
1245 G: KimchiCurve<FULL_ROUNDS>,
1246 ProverIndex<FULL_ROUNDS, G, Srs>: Sync,
1247{
1248}
1249
1250struct WriteContext {
1253 payload: Vec<u8>,
1254 sections: Vec<SectionEntry>,
1255 payload_base: u64,
1258}
1259
1260impl WriteContext {
1261 fn new() -> Self {
1262 Self {
1263 payload: Vec::new(),
1264 sections: Vec::new(),
1265 payload_base: 0,
1266 }
1267 }
1268
1269 fn push_field_section<F: PrimeField>(&mut self, tag: u32, domain_size: u32, data: &[F]) {
1273 let section_offset = self.payload_base + self.payload.len() as u64;
1274 let start = self.payload.len();
1275 write_field_slice(&mut self.payload, data);
1276 let length = (self.payload.len() - start) as u64;
1277 self.sections.push(SectionEntry {
1278 tag,
1279 offset: section_offset,
1280 length,
1281 elem_domain_size: domain_size,
1282 _reserved: 0,
1283 });
1284 let pad = alignment_padding(self.payload.len());
1285 self.payload.extend(std::iter::repeat_n(0u8, pad));
1286 }
1287
1288 fn push_raw_section(&mut self, tag: u32, payload: &[u8]) {
1289 self.push_raw_section_with_elem_count(tag, 0, payload);
1290 }
1291
1292 fn push_raw_section_with_elem_count(
1298 &mut self,
1299 tag: u32,
1300 elem_domain_size: u32,
1301 payload: &[u8],
1302 ) {
1303 let section_offset = self.payload_base + self.payload.len() as u64;
1304 self.payload.extend_from_slice(payload);
1305 let length = payload.len() as u64;
1306 self.sections.push(SectionEntry {
1307 tag,
1308 offset: section_offset,
1309 length,
1310 elem_domain_size,
1311 _reserved: 0,
1312 });
1313 let pad = alignment_padding(self.payload.len());
1314 self.payload.extend(std::iter::repeat_n(0u8, pad));
1315 }
1316}
1317
1318fn fixed_region_size(num_sections: usize) -> usize {
1321 let raw = PREAMBLE_SIZE
1322 + ScalarHeader::SERIALIZED_SIZE
1323 + num_sections * SectionEntry::SERIALIZED_SIZE;
1324 align_up(raw)
1325}
1326
1327pub fn write_cache<const FULL_ROUNDS: usize, G, Srs>(
1338 identifier: &str,
1339 index: &ProverIndex<FULL_ROUNDS, G, Srs>,
1340 path: &Path,
1341) -> Result<(), CacheError>
1342where
1343 G: KimchiCurve<FULL_ROUNDS>,
1344 Srs: SRS<G>,
1345 G::BaseField: PrimeField,
1346{
1347 if identifier.len() > IDENTIFIER_MAX_LEN {
1348 return Err(CacheError::IdentifierTooLong {
1349 len: identifier.len(),
1350 max: IDENTIFIER_MAX_LEN,
1351 });
1352 }
1353
1354 let cs = &index.cs;
1355 let lcs_result = cs.lookup_constraint_system.get();
1360 let lcs: Option<&LookupConstraintSystem<G::ScalarField>> = match lcs_result {
1361 Ok(opt) => opt.as_ref(),
1362 Err(e) => return Err(CacheError::LookupConstraintSystem(e.to_string())),
1367 };
1368
1369 let (vkd_limbs, has_vkd) = match index.verifier_index_digest.as_ref() {
1371 Some(digest) => (field_to_limbs(digest), true),
1372 None => ([0u64; 4], false),
1373 };
1374
1375 let shift_limbs: [[u64; 4]; PERMUTS] = {
1376 let mut arr = [[0u64; 4]; PERMUTS];
1377 for (i, s) in cs.shift.iter().enumerate() {
1378 arr[i] = field_to_limbs(s);
1379 }
1380 arr
1381 };
1382
1383 let column_evaluations = index.column_evaluations.get();
1384 let optional_selectors_present = {
1385 let mut bits = 0u32;
1386 if column_evaluations.range_check0_selector8.is_some() {
1387 bits |= OptionalSelectorBits::RANGE_CHECK_0;
1388 }
1389 if column_evaluations.range_check1_selector8.is_some() {
1390 bits |= OptionalSelectorBits::RANGE_CHECK_1;
1391 }
1392 if column_evaluations.foreign_field_add_selector8.is_some() {
1393 bits |= OptionalSelectorBits::FOREIGN_FIELD_ADD;
1394 }
1395 if column_evaluations.foreign_field_mul_selector8.is_some() {
1396 bits |= OptionalSelectorBits::FOREIGN_FIELD_MUL;
1397 }
1398 if column_evaluations.xor_selector8.is_some() {
1399 bits |= OptionalSelectorBits::XOR;
1400 }
1401 if column_evaluations.rot_selector8.is_some() {
1402 bits |= OptionalSelectorBits::ROT;
1403 }
1404 bits
1405 };
1406
1407 let lookup_selectors_present = match lcs {
1410 None => 0,
1411 Some(lcs) => {
1412 let mut bits = 0u32;
1413 if lcs.table_ids8.is_some() {
1414 bits |= LookupSelectorBits::TABLE_IDS8;
1415 }
1416 if lcs.lookup_selectors.xor.is_some() {
1417 bits |= LookupSelectorBits::SELECTOR_XOR;
1418 }
1419 if lcs.lookup_selectors.lookup.is_some() {
1420 bits |= LookupSelectorBits::SELECTOR_LOOKUP;
1421 }
1422 if lcs.lookup_selectors.range_check.is_some() {
1423 bits |= LookupSelectorBits::SELECTOR_RANGE_CHECK;
1424 }
1425 if lcs.lookup_selectors.ffmul.is_some() {
1426 bits |= LookupSelectorBits::SELECTOR_FFMUL;
1427 }
1428 if lcs.runtime_selector.is_some() {
1429 bits |= LookupSelectorBits::RUNTIME_SELECTOR;
1430 }
1431 if lcs.runtime_tables.is_some() {
1432 bits |= LookupSelectorBits::RUNTIME_TABLES;
1433 }
1434 if lcs.runtime_table_offset.is_some() {
1435 bits |= LookupSelectorBits::RUNTIME_TABLE_OFFSET;
1436 }
1437 bits
1438 }
1439 };
1440
1441 let header = ScalarHeader {
1442 public: cs.public as u32,
1443 prev_challenges: cs.prev_challenges as u32,
1444 zk_rows: cs.zk_rows,
1445 max_poly_size: index.max_poly_size as u64,
1446 disable_gates_checks: cs.disable_gates_checks,
1447 domain_d1_size: cs.domain.d1.size() as u64,
1448 feature_flags: pack_feature_flags(&cs.feature_flags),
1449 optional_selectors_present,
1450 lookup_selectors_present,
1451 has_verifier_index_digest: has_vkd,
1452 endo_limbs: field_to_limbs(&cs.endo),
1453 shift_limbs,
1454 verifier_index_digest_limbs: vkd_limbs,
1455 };
1456
1457 let mut ctx = WriteContext::new();
1461
1462 ctx.push_field_section::<G::ScalarField>(SectionTag::Sid as u32, cs.sid.len() as u32, &cs.sid);
1464
1465 let mut gates_bytes = Vec::with_capacity(cs.gates.len() * PRUNED_GATE_SIZE);
1467 for gate in cs.gates.iter() {
1468 write_pruned_gate(&mut gates_bytes, gate);
1469 }
1470 ctx.push_raw_section(SectionTag::Gates as u32, &gates_bytes);
1471
1472 let mut coeffs_bytes = Vec::new();
1475 for gate in cs.gates.iter() {
1476 write_u32_le(&mut coeffs_bytes, gate.coeffs.len() as u32);
1477 write_field_slice(&mut coeffs_bytes, &gate.coeffs);
1478 }
1479 ctx.push_raw_section(SectionTag::GateCoeffs as u32, &coeffs_bytes);
1480
1481 let d4_size = cs.domain.d4.size() as u32;
1483 let d8_size = cs.domain.d8.size() as u32;
1484 for (i, e) in column_evaluations.coefficients8.iter().enumerate() {
1485 ctx.push_field_section::<G::ScalarField>(coefficient_tag(i), d8_size, &e.evals);
1486 }
1487 for (i, e) in column_evaluations
1488 .permutation_coefficients8
1489 .iter()
1490 .enumerate()
1491 {
1492 ctx.push_field_section::<G::ScalarField>(permutation_coefficient_tag(i), d8_size, &e.evals);
1493 }
1494 ctx.push_field_section::<G::ScalarField>(
1495 SectionTag::GenericSelector4 as u32,
1496 d4_size,
1497 &column_evaluations.generic_selector4.evals,
1498 );
1499 ctx.push_field_section::<G::ScalarField>(
1500 SectionTag::PoseidonSelector8 as u32,
1501 d8_size,
1502 &column_evaluations.poseidon_selector8.evals,
1503 );
1504 ctx.push_field_section::<G::ScalarField>(
1505 SectionTag::CompleteAddSelector4 as u32,
1506 d4_size,
1507 &column_evaluations.complete_add_selector4.evals,
1508 );
1509 ctx.push_field_section::<G::ScalarField>(
1510 SectionTag::MulSelector8 as u32,
1511 d8_size,
1512 &column_evaluations.mul_selector8.evals,
1513 );
1514 ctx.push_field_section::<G::ScalarField>(
1515 SectionTag::EmulSelector8 as u32,
1516 d8_size,
1517 &column_evaluations.emul_selector8.evals,
1518 );
1519 ctx.push_field_section::<G::ScalarField>(
1520 SectionTag::EndomulScalarSelector8 as u32,
1521 d8_size,
1522 &column_evaluations.endomul_scalar_selector8.evals,
1523 );
1524 let push_optional =
1526 |ctx: &mut WriteContext, tag, opt: &Option<Evaluations<G::ScalarField, _>>| {
1527 if let Some(e) = opt {
1528 ctx.push_field_section::<G::ScalarField>(tag, d8_size, &e.evals);
1529 }
1530 };
1531 push_optional(
1532 &mut ctx,
1533 SectionTag::RangeCheck0Selector8 as u32,
1534 &column_evaluations.range_check0_selector8,
1535 );
1536 push_optional(
1537 &mut ctx,
1538 SectionTag::RangeCheck1Selector8 as u32,
1539 &column_evaluations.range_check1_selector8,
1540 );
1541 push_optional(
1542 &mut ctx,
1543 SectionTag::ForeignFieldAddSelector8 as u32,
1544 &column_evaluations.foreign_field_add_selector8,
1545 );
1546 push_optional(
1547 &mut ctx,
1548 SectionTag::ForeignFieldMulSelector8 as u32,
1549 &column_evaluations.foreign_field_mul_selector8,
1550 );
1551 push_optional(
1552 &mut ctx,
1553 SectionTag::XorSelector8 as u32,
1554 &column_evaluations.xor_selector8,
1555 );
1556 push_optional(
1557 &mut ctx,
1558 SectionTag::RotSelector8 as u32,
1559 &column_evaluations.rot_selector8,
1560 );
1561
1562 if let Some(lcs) = lcs {
1568 {
1574 let n = lcs.lookup_table8.len() as u32;
1575 let inner_len = d8_size as usize * FIELD_ELEMENT_BYTES;
1576 let mut bytes = Vec::with_capacity((n as usize) * inner_len);
1577 for e in lcs.lookup_table8.iter() {
1578 write_field_slice(&mut bytes, &e.evals);
1579 }
1580 ctx.push_raw_section_with_elem_count(SectionTag::LookupTable8 as u32, n, &bytes);
1581 }
1582 if let Some(e) = &lcs.table_ids8 {
1583 ctx.push_field_section::<G::ScalarField>(
1584 SectionTag::TableIds8 as u32,
1585 d8_size,
1586 &e.evals,
1587 );
1588 }
1589 push_optional(
1590 &mut ctx,
1591 SectionTag::LookupSelectorXor as u32,
1592 &lcs.lookup_selectors.xor,
1593 );
1594 push_optional(
1595 &mut ctx,
1596 SectionTag::LookupSelectorLookup as u32,
1597 &lcs.lookup_selectors.lookup,
1598 );
1599 push_optional(
1600 &mut ctx,
1601 SectionTag::LookupSelectorRangeCheck as u32,
1602 &lcs.lookup_selectors.range_check,
1603 );
1604 push_optional(
1605 &mut ctx,
1606 SectionTag::LookupSelectorFfmul as u32,
1607 &lcs.lookup_selectors.ffmul,
1608 );
1609 push_optional(
1610 &mut ctx,
1611 SectionTag::RuntimeSelector8 as u32,
1612 &lcs.runtime_selector,
1613 );
1614 if let Some(rts) = &lcs.runtime_tables {
1615 let mut bytes = Vec::with_capacity(4 + rts.len() * 8);
1620 bytes.extend_from_slice(&(rts.len() as u32).to_le_bytes());
1621 for spec in rts {
1622 bytes.extend_from_slice(&spec.id.to_le_bytes());
1623 bytes.extend_from_slice(&(spec.len as u32).to_le_bytes());
1624 }
1625 ctx.push_raw_section(SectionTag::RuntimeTablesSpec as u32, &bytes);
1626 }
1627 if let Some(off) = lcs.runtime_table_offset {
1628 let bytes = (off as u64).to_le_bytes();
1629 ctx.push_raw_section(SectionTag::RuntimeTableOffset as u32, &bytes);
1630 }
1631 }
1632
1633 let num_sections = ctx.sections.len();
1636 let payload_base = fixed_region_size(num_sections) as u64;
1637 for s in ctx.sections.iter_mut() {
1638 s.offset += payload_base;
1639 }
1640 ctx.payload_base = payload_base;
1641
1642 let mut file = Vec::with_capacity(payload_base as usize + ctx.payload.len());
1645 write_preamble(&mut file, identifier, num_sections as u32)?;
1646 header.write(&mut file);
1647 for s in &ctx.sections {
1648 s.write(&mut file);
1649 }
1650 let fixed_region_end = PREAMBLE_SIZE
1651 + ScalarHeader::SERIALIZED_SIZE
1652 + num_sections * SectionEntry::SERIALIZED_SIZE;
1653 let pad = align_up(fixed_region_end) - fixed_region_end;
1654 file.extend(std::iter::repeat_n(0u8, pad));
1655 debug_assert_eq!(file.len() as u64, payload_base);
1656 file.extend_from_slice(&ctx.payload);
1657
1658 let tmp_path = {
1663 static TMP_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1664 let uniq = TMP_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1665 let mut p = path.to_path_buf();
1666 let mut tmp_name = p.file_name().map(|f| f.to_owned()).unwrap_or_default();
1667 tmp_name.push(format!(".tmp.{}.{uniq}", std::process::id()));
1668 p.set_file_name(tmp_name);
1669 p
1670 };
1671 let staged = (|| -> Result<(), CacheError> {
1677 let mut f = OpenOptions::new()
1678 .create(true)
1679 .write(true)
1680 .truncate(true)
1681 .open(&tmp_path)?;
1682 f.write_all(&file)?;
1683 f.sync_all()?;
1684 std::fs::rename(&tmp_path, path)?;
1685 Ok(())
1686 })();
1687 if let Err(e) = staged {
1688 let _ = std::fs::remove_file(&tmp_path);
1689 return Err(e);
1690 }
1691 let dir = match path.parent() {
1695 Some(d) if !d.as_os_str().is_empty() => d,
1696 _ => Path::new("."),
1697 };
1698 if let Ok(d) = std::fs::File::open(dir) {
1699 let _ = d.sync_all();
1700 }
1701 Ok(())
1702}
1703
1704struct LookupParts<'a> {
1711 lt_bytes: &'a [u8],
1712 n: usize,
1714 d8_size: usize,
1715 inner_bytes: usize,
1716 table_ids8_desc: Option<(&'a [u8], usize)>,
1717 sel_xor_desc: Option<(&'a [u8], usize)>,
1718 sel_lookup_desc: Option<(&'a [u8], usize)>,
1719 sel_range_check_desc: Option<(&'a [u8], usize)>,
1720 sel_ffmul_desc: Option<(&'a [u8], usize)>,
1721 runtime_selector_desc: Option<(&'a [u8], usize)>,
1722 runtime_tables: Option<Vec<RuntimeTableSpec>>,
1723 runtime_table_offset: Option<usize>,
1724}
1725
1726pub fn read_cache<const FULL_ROUNDS: usize, G, Srs>(
1752 identifier: &str,
1753 path: &Path,
1754 srs: Arc<Srs>,
1755) -> Result<MmapProverIndex<FULL_ROUNDS, G, Srs>, CacheError>
1756where
1757 G: KimchiCurve<FULL_ROUNDS>,
1758 Srs: SRS<G>,
1759 G::BaseField: ark_ff::PrimeField,
1760{
1761 let file = std::fs::File::open(path)?;
1762 let mmap = Arc::new(ReadOnlyMmap::map_file(&file)?);
1767 let bytes: &[u8] = mmap.as_slice();
1773
1774 let (preamble, rest) = read_preamble(bytes)?;
1776 if preamble.identifier != identifier {
1777 return Err(CacheError::IdentifierMismatch {
1778 found: preamble.identifier,
1779 expected: identifier.to_owned(),
1780 });
1781 }
1782
1783 let (header, mut rest) = ScalarHeader::read(rest)?;
1785
1786 let mut sections: std::collections::BTreeMap<u32, SectionEntry> = Default::default();
1788 for _ in 0..preamble.num_sections {
1789 let (entry, tail) = SectionEntry::read(rest)?;
1790 rest = tail;
1791 if entry.offset % SECTION_ALIGNMENT as u64 != 0 {
1792 return Err(CacheError::MisalignedSection {
1793 tag: entry.tag,
1794 offset: entry.offset,
1795 });
1796 }
1797 if sections.insert(entry.tag, entry).is_some() {
1798 return Err(CacheError::DuplicateSectionTag { tag: entry.tag });
1799 }
1800 }
1801
1802 let section_bytes = |tag: u32| -> Result<&[u8], CacheError> {
1804 let entry = sections
1805 .get(&tag)
1806 .ok_or(CacheError::MissingSection { tag })?;
1807 let start = entry.offset as usize;
1808 let end = start
1809 .checked_add(entry.length as usize)
1810 .ok_or(CacheError::TruncatedFile)?;
1811 if end > bytes.len() {
1812 return Err(CacheError::TruncatedFile);
1813 }
1814 Ok(&bytes[start..end])
1815 };
1816
1817 let d1_size = header.domain_d1_size as usize;
1819 let domain = EvaluationDomains::<G::ScalarField>::create(d1_size).map_err(|_| {
1820 CacheError::InvalidDomainSize {
1821 size: header.domain_d1_size,
1822 }
1823 })?;
1824 let d4 = domain.d4;
1825 let d8 = domain.d8;
1826
1827 let field_section = |tag: u32| -> Result<(&[u8], usize), CacheError> {
1839 let entry = sections
1840 .get(&tag)
1841 .ok_or(CacheError::MissingSection { tag })?;
1842 let count = entry.elem_domain_size as usize;
1843 let b = section_bytes(tag)?;
1844 validate_field_section(tag, b, count)?;
1845 Ok((b, count))
1846 };
1847 let optional_field_section =
1848 |tag: u32, mask: u32, present: u32| -> Result<Option<(&[u8], usize)>, CacheError> {
1849 if present & mask != 0 {
1850 Ok(Some(field_section(tag)?))
1851 } else {
1852 Ok(None)
1853 }
1854 };
1855
1856 let sid_desc = field_section(SectionTag::Sid as u32)?;
1858
1859 let gates_bytes = section_bytes(SectionTag::Gates as u32)?;
1861 if gates_bytes.len() % PRUNED_GATE_SIZE != 0 {
1862 return Err(CacheError::PayloadNotFieldAligned {
1863 tag: SectionTag::Gates as u32,
1864 length: gates_bytes.len() as u64,
1865 });
1866 }
1867 let mut gates = Vec::with_capacity(gates_bytes.len() / PRUNED_GATE_SIZE);
1868 for chunk in gates_bytes.as_chunks::<PRUNED_GATE_SIZE>().0 {
1869 gates.push(read_pruned_gate::<G::ScalarField>(chunk)?);
1870 }
1871 {
1882 let mut cursor = section_bytes(SectionTag::GateCoeffs as u32)?;
1883 for gate in gates.iter_mut() {
1884 let (count, rest) = read_u32_le(cursor)?;
1885 let (fields, rest) = read_exact(rest, count as usize * FIELD_ELEMENT_BYTES)?;
1886 if cfg!(debug_assertions) {
1887 let mut coeffs = Vec::with_capacity(count as usize);
1888 for chunk in fields.as_chunks::<FIELD_ELEMENT_BYTES>().0 {
1889 let mut limbs = [0u64; 4];
1890 for (i, limb) in limbs.iter_mut().enumerate() {
1891 let mut b = [0u8; 8];
1892 b.copy_from_slice(&chunk[i * 8..i * 8 + 8]);
1893 *limb = u64::from_le_bytes(b);
1894 }
1895 coeffs.push(limbs_to_field::<G::ScalarField>(&limbs));
1896 }
1897 gate.coeffs = coeffs;
1898 }
1899 cursor = rest;
1900 }
1901 }
1902
1903 let mut coeff_descs: Vec<(&[u8], usize)> = Vec::with_capacity(COLUMNS);
1905 for i in 0..COLUMNS {
1906 coeff_descs.push(field_section(coefficient_tag(i))?);
1907 }
1908 let coeff_descs: [(&[u8], usize); COLUMNS] = coeff_descs
1909 .try_into()
1910 .map_err(|_| CacheError::TruncatedFile)?;
1911
1912 let mut perm_descs: Vec<(&[u8], usize)> = Vec::with_capacity(PERMUTS);
1913 for i in 0..PERMUTS {
1914 perm_descs.push(field_section(permutation_coefficient_tag(i))?);
1915 }
1916 let perm_descs: [(&[u8], usize); PERMUTS] = perm_descs
1917 .try_into()
1918 .map_err(|_| CacheError::TruncatedFile)?;
1919
1920 let generic_selector4_desc = field_section(SectionTag::GenericSelector4 as u32)?;
1921 let poseidon_selector8_desc = field_section(SectionTag::PoseidonSelector8 as u32)?;
1922 let complete_add_selector4_desc = field_section(SectionTag::CompleteAddSelector4 as u32)?;
1923 let mul_selector8_desc = field_section(SectionTag::MulSelector8 as u32)?;
1924 let emul_selector8_desc = field_section(SectionTag::EmulSelector8 as u32)?;
1925 let endomul_scalar_selector8_desc = field_section(SectionTag::EndomulScalarSelector8 as u32)?;
1926
1927 let opt = header.optional_selectors_present;
1928 let range_check0_desc = optional_field_section(
1929 SectionTag::RangeCheck0Selector8 as u32,
1930 OptionalSelectorBits::RANGE_CHECK_0,
1931 opt,
1932 )?;
1933 let range_check1_desc = optional_field_section(
1934 SectionTag::RangeCheck1Selector8 as u32,
1935 OptionalSelectorBits::RANGE_CHECK_1,
1936 opt,
1937 )?;
1938 let ffadd_desc = optional_field_section(
1939 SectionTag::ForeignFieldAddSelector8 as u32,
1940 OptionalSelectorBits::FOREIGN_FIELD_ADD,
1941 opt,
1942 )?;
1943 let ffmul_desc = optional_field_section(
1944 SectionTag::ForeignFieldMulSelector8 as u32,
1945 OptionalSelectorBits::FOREIGN_FIELD_MUL,
1946 opt,
1947 )?;
1948 let xor_desc = optional_field_section(
1949 SectionTag::XorSelector8 as u32,
1950 OptionalSelectorBits::XOR,
1951 opt,
1952 )?;
1953 let rot_desc = optional_field_section(
1954 SectionTag::RotSelector8 as u32,
1955 OptionalSelectorBits::ROT,
1956 opt,
1957 )?;
1958
1959 let feature_flags = unpack_feature_flags(header.feature_flags)?;
1960 let shift: [G::ScalarField; PERMUTS] = {
1961 let mut arr = [G::ScalarField::from(0u64); PERMUTS];
1962 for (i, limbs) in header.shift_limbs.iter().enumerate() {
1963 arr[i] = limbs_to_field::<G::ScalarField>(limbs);
1964 }
1965 arr
1966 };
1967 let endo = limbs_to_field::<G::ScalarField>(&header.endo_limbs);
1968
1969 let lp = header.lookup_selectors_present;
1973 let has_lookup = lp != 0 || sections.contains_key(&(SectionTag::LookupTable8 as u32));
1974 let lookup: Option<LookupParts> = if has_lookup {
1975 let lt_entry =
1978 sections
1979 .get(&(SectionTag::LookupTable8 as u32))
1980 .ok_or(CacheError::MissingSection {
1981 tag: SectionTag::LookupTable8 as u32,
1982 })?;
1983 let n = lt_entry.elem_domain_size as usize;
1984 let d8_size = d8.size();
1985 let inner_bytes = d8_size * FIELD_ELEMENT_BYTES;
1986 let lt_bytes = section_bytes(SectionTag::LookupTable8 as u32)?;
1987 let expected_total = n * inner_bytes;
1988 if lt_bytes.len() != expected_total {
1989 return Err(CacheError::SectionLengthMismatch {
1990 tag: SectionTag::LookupTable8 as u32,
1991 expected: expected_total as u64,
1992 found: lt_bytes.len() as u64,
1993 });
1994 }
1995
1996 let table_ids8_desc = optional_field_section(
1997 SectionTag::TableIds8 as u32,
1998 LookupSelectorBits::TABLE_IDS8,
1999 lp,
2000 )?;
2001 let sel_xor_desc = optional_field_section(
2002 SectionTag::LookupSelectorXor as u32,
2003 LookupSelectorBits::SELECTOR_XOR,
2004 lp,
2005 )?;
2006 let sel_lookup_desc = optional_field_section(
2007 SectionTag::LookupSelectorLookup as u32,
2008 LookupSelectorBits::SELECTOR_LOOKUP,
2009 lp,
2010 )?;
2011 let sel_range_check_desc = optional_field_section(
2012 SectionTag::LookupSelectorRangeCheck as u32,
2013 LookupSelectorBits::SELECTOR_RANGE_CHECK,
2014 lp,
2015 )?;
2016 let sel_ffmul_desc = optional_field_section(
2017 SectionTag::LookupSelectorFfmul as u32,
2018 LookupSelectorBits::SELECTOR_FFMUL,
2019 lp,
2020 )?;
2021 let runtime_selector_desc = optional_field_section(
2022 SectionTag::RuntimeSelector8 as u32,
2023 LookupSelectorBits::RUNTIME_SELECTOR,
2024 lp,
2025 )?;
2026
2027 let runtime_tables: Option<Vec<RuntimeTableSpec>> =
2028 if lp & LookupSelectorBits::RUNTIME_TABLES != 0 {
2029 let rt_bytes = section_bytes(SectionTag::RuntimeTablesSpec as u32)?;
2030 if rt_bytes.len() < 4 {
2031 return Err(CacheError::TruncatedFile);
2032 }
2033 let mut count_buf = [0u8; 4];
2034 count_buf.copy_from_slice(&rt_bytes[..4]);
2035 let rn = u32::from_le_bytes(count_buf) as usize;
2036 let expected = 4 + rn * 8;
2037 if rt_bytes.len() != expected {
2038 return Err(CacheError::SectionLengthMismatch {
2039 tag: SectionTag::RuntimeTablesSpec as u32,
2040 expected: expected as u64,
2041 found: rt_bytes.len() as u64,
2042 });
2043 }
2044 let mut out = Vec::with_capacity(rn);
2045 for i in 0..rn {
2046 let base = 4 + i * 8;
2047 let mut id_buf = [0u8; 4];
2048 let mut len_buf = [0u8; 4];
2049 id_buf.copy_from_slice(&rt_bytes[base..base + 4]);
2050 len_buf.copy_from_slice(&rt_bytes[base + 4..base + 8]);
2051 out.push(RuntimeTableSpec {
2052 id: i32::from_le_bytes(id_buf),
2053 len: u32::from_le_bytes(len_buf) as usize,
2054 });
2055 }
2056 Some(out)
2057 } else {
2058 None
2059 };
2060
2061 let runtime_table_offset: Option<usize> =
2062 if lp & LookupSelectorBits::RUNTIME_TABLE_OFFSET != 0 {
2063 let off_bytes = section_bytes(SectionTag::RuntimeTableOffset as u32)?;
2064 if off_bytes.len() != 8 {
2065 return Err(CacheError::SectionLengthMismatch {
2066 tag: SectionTag::RuntimeTableOffset as u32,
2067 expected: 8,
2068 found: off_bytes.len() as u64,
2069 });
2070 }
2071 let mut buf = [0u8; 8];
2072 buf.copy_from_slice(off_bytes);
2073 Some(u64::from_le_bytes(buf) as usize)
2074 } else {
2075 None
2076 };
2077
2078 Some(LookupParts {
2079 lt_bytes,
2080 n,
2081 d8_size,
2082 inner_bytes,
2083 table_ids8_desc,
2084 sel_xor_desc,
2085 sel_lookup_desc,
2086 sel_range_check_desc,
2087 sel_ffmul_desc,
2088 runtime_selector_desc,
2089 runtime_tables,
2090 runtime_table_offset,
2091 })
2092 } else {
2093 None
2094 };
2095
2096 let make_eval = |(b, c): (&[u8], usize),
2106 d: Radix2EvaluationDomain<G::ScalarField>|
2107 -> Evaluations<G::ScalarField, Radix2EvaluationDomain<G::ScalarField>> {
2108 Evaluations::<G::ScalarField, _>::from_vec_and_domain(
2109 unsafe { mmap_field_vec_unchecked::<G::ScalarField>(b, c) },
2110 d,
2111 )
2112 };
2113 let make_opt_eval = |desc: Option<(&[u8], usize)>,
2114 d: Radix2EvaluationDomain<G::ScalarField>| {
2115 desc.map(|x| make_eval(x, d))
2116 };
2117
2118 let sid = unsafe { mmap_field_vec_unchecked::<G::ScalarField>(sid_desc.0, sid_desc.1) };
2119
2120 let coefficients8: [Evaluations<G::ScalarField, _>; COLUMNS] =
2121 coeff_descs.map(|desc| make_eval(desc, d8));
2122 let permutation_coefficients8: [Evaluations<G::ScalarField, _>; PERMUTS] =
2123 perm_descs.map(|desc| make_eval(desc, d8));
2124
2125 let column_evaluations = ColumnEvaluations::<G::ScalarField> {
2126 permutation_coefficients8,
2127 coefficients8,
2128 generic_selector4: make_eval(generic_selector4_desc, d4),
2129 poseidon_selector8: make_eval(poseidon_selector8_desc, d8),
2130 complete_add_selector4: make_eval(complete_add_selector4_desc, d4),
2131 mul_selector8: make_eval(mul_selector8_desc, d8),
2132 emul_selector8: make_eval(emul_selector8_desc, d8),
2133 endomul_scalar_selector8: make_eval(endomul_scalar_selector8_desc, d8),
2134 range_check0_selector8: make_opt_eval(range_check0_desc, d8),
2135 range_check1_selector8: make_opt_eval(range_check1_desc, d8),
2136 foreign_field_add_selector8: make_opt_eval(ffadd_desc, d8),
2137 foreign_field_mul_selector8: make_opt_eval(ffmul_desc, d8),
2138 xor_selector8: make_opt_eval(xor_desc, d8),
2139 rot_selector8: make_opt_eval(rot_desc, d8),
2140 };
2141
2142 let precomputations = Arc::new(LazyCache::new({
2143 let precomputations_domain = domain;
2144 let zk_rows = header.zk_rows;
2145 move || {
2146 Arc::new(
2147 crate::circuits::domain_constant_evaluation::DomainConstantEvaluations::create(
2148 precomputations_domain,
2149 zk_rows,
2150 )
2151 .expect("domain constant evaluations"),
2152 )
2153 }
2154 }));
2155
2156 let lookup_constraint_system = match lookup {
2157 None => Arc::new(LazyCache::new(|| {
2158 Ok::<
2159 Option<LookupConstraintSystem<G::ScalarField>>,
2160 crate::circuits::lookup::index::LookupError,
2161 >(None)
2162 })),
2163 Some(parts) => {
2164 let mut lookup_table8: Vec<Evaluations<G::ScalarField, _>> =
2165 Vec::with_capacity(parts.n);
2166 for i in 0..parts.n {
2167 let start = i * parts.inner_bytes;
2168 let end = start + parts.inner_bytes;
2169 let evals = unsafe {
2170 mmap_field_vec_unchecked::<G::ScalarField>(
2171 &parts.lt_bytes[start..end],
2172 parts.d8_size,
2173 )
2174 };
2175 lookup_table8.push(Evaluations::from_vec_and_domain(evals, d8));
2176 }
2177 let lookup_info = LookupInfo::create(feature_flags.lookup_features);
2179 let lcs = LookupConstraintSystem {
2180 lookup_table: Vec::new(),
2181 lookup_table8,
2182 table_ids: None,
2183 table_ids8: make_opt_eval(parts.table_ids8_desc, d8),
2184 lookup_selectors: LookupSelectors {
2185 xor: make_opt_eval(parts.sel_xor_desc, d8),
2186 lookup: make_opt_eval(parts.sel_lookup_desc, d8),
2187 range_check: make_opt_eval(parts.sel_range_check_desc, d8),
2188 ffmul: make_opt_eval(parts.sel_ffmul_desc, d8),
2189 },
2190 runtime_selector: make_opt_eval(parts.runtime_selector_desc, d8),
2191 runtime_tables: parts.runtime_tables,
2192 runtime_table_offset: parts.runtime_table_offset,
2193 configuration: LookupConfiguration::new(lookup_info),
2194 };
2195 Arc::new(LazyCache::new(move || Ok(Some(lcs))))
2196 }
2197 };
2198
2199 let cs = ConstraintSystem {
2200 public: header.public as usize,
2201 prev_challenges: header.prev_challenges as usize,
2202 domain,
2203 gates: Arc::new(gates),
2204 zk_rows: header.zk_rows,
2205 feature_flags,
2206 sid,
2207 shift,
2208 endo,
2209 lookup_constraint_system,
2210 precomputations,
2211 disable_gates_checks: header.disable_gates_checks,
2212 };
2213
2214 let (linearization, powers_of_alpha) = expr_linearization(Some(&cs.feature_flags), true);
2216
2217 let verifier_index_digest = if header.has_verifier_index_digest {
2218 Some(limbs_to_field::<G::BaseField>(
2219 &header.verifier_index_digest_limbs,
2220 ))
2221 } else {
2222 None
2223 };
2224
2225 let cs = Arc::new(cs);
2226 let column_evaluations_cache = Arc::new(LazyCache::new({
2227 let ce = column_evaluations;
2228 move || ce
2229 }));
2230 column_evaluations_cache.get();
2233
2234 let index = ProverIndex {
2235 cs,
2236 linearization,
2237 powers_of_alpha,
2238 srs,
2239 max_poly_size: header.max_poly_size as usize,
2240 column_evaluations: column_evaluations_cache,
2241 verifier_index: None,
2242 verifier_index_digest,
2243 };
2244 Ok(MmapProverIndex {
2245 index: core::mem::ManuallyDrop::new(index),
2246 _mmap: mmap,
2247 })
2248}