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    /// Number of custom public values to publish.
28    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
108/// ATTENTION: If a specific public value is not provided, a default 0 will be used when generating
109/// the proof but in the perspective of constraints, it could be any value.
110pub struct PublicValuesCoreChip<F> {
111    air: PublicValuesCoreAir,
112    // Mutex is to make the struct Sync. But it actually won't be accessed by multiple threads.
113    custom_pvs: Mutex<Vec<Option<F>>>,
114}
115
116impl<F: PrimeField32> PublicValuesCoreChip<F> {
117    /// **Note:** `max_degree` is the maximum degree of the constraint polynomials to represent the flags.
118    /// If you want the overall AIR's constraint degree to be `<= max_constraint_degree`, then typically
119    /// you should set `max_degree` to `max_constraint_degree - 1`.
120    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                // Not a hard constraint violation when publishing the same value twice but the
151                // program should avoid that.
152                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}