openvm_circuit/system/public_values/
core.rs
1use std::sync::Mutex;
2
3use openvm_circuit_primitives::{encoder::Encoder, SubAir};
4use openvm_instructions::{
5 instruction::Instruction, LocalOpcode, PublishOpcode, PublishOpcode::PUBLISH,
6};
7use openvm_stark_backend::{
8 interaction::InteractionBuilder,
9 p3_air::{AirBuilder, AirBuilderWithPublicValues, BaseAir},
10 p3_field::{Field, FieldAlgebra, PrimeField32},
11 rap::BaseAirWithPublicValues,
12};
13use serde::{Deserialize, Serialize};
14
15use crate::{
16 arch::{
17 AdapterAirContext, AdapterRuntimeContext, BasicAdapterInterface, MinimalInstruction,
18 Result, VmAdapterInterface, VmCoreAir, VmCoreChip,
19 },
20 system::public_values::columns::PublicValuesCoreColsView,
21};
22pub(crate) type AdapterInterface<F> = BasicAdapterInterface<F, MinimalInstruction<F>, 2, 0, 1, 1>;
23pub(crate) type AdapterInterfaceReads<F> = <AdapterInterface<F> as VmAdapterInterface<F>>::Reads;
24
25#[derive(Clone, Debug)]
26pub struct PublicValuesCoreAir {
27 pub num_custom_pvs: usize,
29 encoder: Encoder,
30}
31
32impl PublicValuesCoreAir {
33 pub fn new(num_custom_pvs: usize, max_degree: u32) -> Self {
34 Self {
35 num_custom_pvs,
36 encoder: Encoder::new(num_custom_pvs, max_degree, true),
37 }
38 }
39}
40
41impl<F: Field> BaseAir<F> for PublicValuesCoreAir {
42 fn width(&self) -> usize {
43 3 + self.encoder.width()
44 }
45}
46
47impl<F: Field> BaseAirWithPublicValues<F> for PublicValuesCoreAir {
48 fn num_public_values(&self) -> usize {
49 self.num_custom_pvs
50 }
51}
52
53impl<AB: InteractionBuilder + AirBuilderWithPublicValues> VmCoreAir<AB, AdapterInterface<AB::Expr>>
54 for PublicValuesCoreAir
55{
56 fn eval(
57 &self,
58 builder: &mut AB,
59 local_core: &[AB::Var],
60 _from_pc: AB::Var,
61 ) -> AdapterAirContext<AB::Expr, AdapterInterface<AB::Expr>> {
62 let cols = PublicValuesCoreColsView::<_, &AB::Var>::borrow(local_core);
63 debug_assert_eq!(cols.width(), BaseAir::<AB::F>::width(self));
64 let is_valid = *cols.is_valid;
65 let value = *cols.value;
66 let index = *cols.index;
67
68 let vars = cols.custom_pv_vars.iter().map(|&&x| x).collect::<Vec<_>>();
69 self.encoder.eval(builder, &vars);
70
71 let flags = self.encoder.flags::<AB>(&vars);
72
73 let mut match_public_value_index = AB::Expr::ZERO;
74 let mut match_public_value = AB::Expr::ZERO;
75 for (i, flag) in flags.iter().enumerate() {
76 match_public_value_index += flag.clone() * AB::F::from_canonical_usize(i);
77 match_public_value += flag.clone() * builder.public_values()[i].into();
78 }
79 builder.assert_eq(is_valid, self.encoder.is_valid::<AB>(&vars));
80
81 let mut when_publish = builder.when(is_valid);
82 when_publish.assert_eq(index, match_public_value_index);
83 when_publish.assert_eq(value, match_public_value);
84
85 AdapterAirContext {
86 to_pc: None,
87 reads: [[value.into()], [index.into()]],
88 writes: [],
89 instruction: MinimalInstruction {
90 is_valid: is_valid.into(),
91 opcode: AB::Expr::from_canonical_usize(PUBLISH.global_opcode().as_usize()),
92 },
93 }
94 }
95
96 fn start_offset(&self) -> usize {
97 PublishOpcode::CLASS_OFFSET
98 }
99}
100
101#[repr(C)]
102#[derive(Debug, Serialize, Deserialize)]
103pub struct PublicValuesRecord<F> {
104 value: F,
105 index: F,
106}
107
108pub struct PublicValuesCoreChip<F> {
111 air: PublicValuesCoreAir,
112 custom_pvs: Mutex<Vec<Option<F>>>,
114}
115
116impl<F: PrimeField32> PublicValuesCoreChip<F> {
117 pub fn new(num_custom_pvs: usize, max_degree: u32) -> Self {
121 Self {
122 air: PublicValuesCoreAir::new(num_custom_pvs, max_degree),
123 custom_pvs: Mutex::new(vec![None; num_custom_pvs]),
124 }
125 }
126 pub fn get_custom_public_values(&self) -> Vec<Option<F>> {
127 self.custom_pvs.lock().unwrap().clone()
128 }
129}
130
131impl<F: PrimeField32> VmCoreChip<F, AdapterInterface<F>> for PublicValuesCoreChip<F> {
132 type Record = PublicValuesRecord<F>;
133 type Air = PublicValuesCoreAir;
134
135 #[allow(clippy::type_complexity)]
136 fn execute_instruction(
137 &self,
138 _instruction: &Instruction<F>,
139 _from_pc: u32,
140 reads: AdapterInterfaceReads<F>,
141 ) -> Result<(AdapterRuntimeContext<F, AdapterInterface<F>>, Self::Record)> {
142 let [[value], [index]] = reads;
143 {
144 let idx: usize = index.as_canonical_u32() as usize;
145 let mut custom_pvs = self.custom_pvs.lock().unwrap();
146
147 if custom_pvs[idx].is_none() {
148 custom_pvs[idx] = Some(value);
149 } else {
150 panic!("Custom public value {} already set", idx);
153 }
154 }
155 let output = AdapterRuntimeContext {
156 to_pc: None,
157 writes: [],
158 };
159 let record = Self::Record { value, index };
160 Ok((output, record))
161 }
162
163 fn get_opcode_name(&self, opcode: usize) -> String {
164 format!(
165 "{:?}",
166 PublishOpcode::from_usize(opcode - PublishOpcode::CLASS_OFFSET)
167 )
168 }
169
170 fn generate_trace_row(&self, row_slice: &mut [F], record: Self::Record) {
171 let mut cols = PublicValuesCoreColsView::<_, &mut F>::borrow_mut(row_slice);
172 debug_assert_eq!(cols.width(), BaseAir::<F>::width(&self.air));
173 *cols.is_valid = F::ONE;
174 *cols.value = record.value;
175 *cols.index = record.index;
176 let idx: usize = record.index.as_canonical_u32() as usize;
177 let pt = self.air.encoder.get_flag_pt(idx);
178 for (i, var) in cols.custom_pv_vars.iter_mut().enumerate() {
179 **var = F::from_canonical_u32(pt[i]);
180 }
181 }
182
183 fn generate_public_values(&self) -> Vec<F> {
184 self.get_custom_public_values()
185 .into_iter()
186 .map(|x| x.unwrap_or(F::ZERO))
187 .collect()
188 }
189
190 fn air(&self) -> &Self::Air {
191 &self.air
192 }
193}