openvm_circuit/system/memory/merkle/
public_values.rs1use std::io::{self, Write};
2
3use itertools::Itertools;
4use openvm_stark_backend::{
5 codec::{DecodableConfig, EncodableConfig},
6 p3_util::log2_strict_usize,
7};
8use p3_field::Field;
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11use tracing::instrument;
12
13use crate::{
14 arch::{hasher::Hasher, MemoryCellType, ADDR_SPACE_OFFSET},
15 system::memory::{dimensions::MemoryDimensions, online::LinearMemory, MemoryImage},
16};
17
18pub const PUBLIC_VALUES_AS: u32 = 3;
19pub const PUBLIC_VALUES_ADDRESS_SPACE_OFFSET: u32 = PUBLIC_VALUES_AS - ADDR_SPACE_OFFSET;
20
21#[derive(Clone, Debug, Serialize, Deserialize)]
23#[serde(bound(
24 serialize = "F: Serialize, [F; CHUNK]: Serialize",
25 deserialize = "F: Deserialize<'de>, [F; CHUNK]: Deserialize<'de>"
26))]
27pub struct UserPublicValuesProof<const CHUNK: usize, F> {
28 pub proof: Vec<[F; CHUNK]>,
31 pub public_values: Vec<F>,
33 pub public_values_commit: [F; CHUNK],
37}
38
39#[derive(Error, Debug)]
40pub enum UserPublicValuesProofError {
41 #[error("unexpected length: {0}")]
42 UnexpectedLength(usize),
43 #[error("incorrect proof length: {0} (expected {1})")]
44 IncorrectProofLength(usize, usize),
45 #[error("user public values do not match commitment")]
46 UserPublicValuesCommitMismatch,
47 #[error("final memory root mismatch")]
48 FinalMemoryRootMismatch,
49}
50
51impl<const CHUNK: usize, F: Field> UserPublicValuesProof<CHUNK, F> {
52 #[instrument(name = "compute_user_public_values_proof", skip_all)]
60 pub fn compute(
61 memory_dimensions: MemoryDimensions,
62 num_public_values: usize,
63 hasher: &(impl Hasher<CHUNK, F> + Sync),
64 final_memory: &MemoryImage,
65 top_tree: &[[F; CHUNK]],
66 ) -> Self {
67 let public_values = extract_public_values(num_public_values, final_memory)
68 .iter()
69 .map(|&x| F::from_u8(x))
70 .collect_vec();
71 let public_values_commit = hasher.merkle_root(&public_values);
72 let proof = compute_merkle_proof_to_user_public_values_root(
73 memory_dimensions,
74 num_public_values,
75 hasher,
76 top_tree,
77 );
78 UserPublicValuesProof {
79 proof,
80 public_values,
81 public_values_commit,
82 }
83 }
84
85 pub fn verify(
86 &self,
87 hasher: &impl Hasher<CHUNK, F>,
88 memory_dimensions: MemoryDimensions,
89 final_memory_root: [F; CHUNK],
90 ) -> Result<(), UserPublicValuesProofError> {
91 let pv_commit = self.public_values_commit;
96 let pv_as = PUBLIC_VALUES_AS;
98 let pv_start_idx = memory_dimensions.label_to_index((pv_as, 0));
99 let pvs = &self.public_values;
100 if !pvs.len().is_multiple_of(CHUNK) || !(pvs.len() / CHUNK).is_power_of_two() {
101 return Err(UserPublicValuesProofError::UnexpectedLength(pvs.len()));
102 }
103 let pv_height = log2_strict_usize(pvs.len() / CHUNK);
104 let proof_len = memory_dimensions.overall_height() - pv_height;
105 let idx_prefix = pv_start_idx >> pv_height;
106 if self.proof.len() != proof_len {
108 return Err(UserPublicValuesProofError::IncorrectProofLength(
109 self.proof.len(),
110 proof_len,
111 ));
112 }
113 let mut curr_root = pv_commit;
114 for (i, sibling_hash) in self.proof.iter().enumerate() {
115 curr_root = if idx_prefix & (1 << i) != 0 {
116 hasher.compress(sibling_hash, &curr_root)
117 } else {
118 hasher.compress(&curr_root, sibling_hash)
119 }
120 }
121 if curr_root != final_memory_root {
122 return Err(UserPublicValuesProofError::FinalMemoryRootMismatch);
123 }
124 if hasher.merkle_root(pvs) != pv_commit {
126 return Err(UserPublicValuesProofError::UserPublicValuesCommitMismatch);
127 }
128
129 Ok(())
130 }
131
132 pub fn encode<SC: EncodableConfig<F = F, Digest = [F; CHUNK]>, W: Write>(
133 &self,
134 writer: &mut W,
135 ) -> io::Result<()> {
136 SC::encode_digest_slice(&self.proof, writer)?;
137 SC::encode_base_field_slice(&self.public_values, writer)?;
138 SC::encode_digest(&self.public_values_commit, writer)?;
139 Ok(())
140 }
141
142 pub fn decode<SC: DecodableConfig<F = F, Digest = [F; CHUNK]>, R: io::Read>(
143 reader: &mut R,
144 ) -> io::Result<Self> {
145 let proof = SC::decode_digest_vec(reader)?;
146 let public_values = SC::decode_base_field_vec(reader)?;
147 let public_values_commit = SC::decode_digest(reader)?;
148 Ok(Self {
149 proof,
150 public_values,
151 public_values_commit,
152 })
153 }
154}
155
156fn compute_merkle_proof_to_user_public_values_root<const CHUNK: usize, F: Field>(
157 memory_dimensions: MemoryDimensions,
158 num_public_values: usize,
159 hasher: &(impl Hasher<CHUNK, F> + Sync),
160 top_tree: &[[F; CHUNK]],
161) -> Vec<[F; CHUNK]> {
162 assert_eq!(
163 num_public_values % CHUNK,
164 0,
165 "num_public_values must be a multiple of memory chunk {CHUNK}"
166 );
167 let address_height = memory_dimensions.address_height;
168 let addr_space_height = memory_dimensions.addr_space_height;
169 assert_eq!(top_tree.len(), (2 << addr_space_height) - 1);
170 let num_pv_chunks: usize = num_public_values / CHUNK;
171 assert!(
173 num_pv_chunks.is_power_of_two(),
174 "pv_height must be a power of two"
175 );
176 let pv_height = log2_strict_usize(num_pv_chunks);
177 let address_leading_zeros = address_height - pv_height;
178
179 let mut cur_node_idx = 1; let mut proof = Vec::with_capacity(addr_space_height + address_leading_zeros);
181 let zero_nodes: Vec<_> = (0..address_height)
182 .scan(hasher.hash(&[F::ZERO; CHUNK]), |acc, _| {
183 let result = Some(*acc);
184 *acc = hasher.compress(acc, acc);
185 result
186 })
187 .collect();
188 for i in 0..addr_space_height {
189 let bit = 1 << (memory_dimensions.addr_space_height - i - 1);
190 if (PUBLIC_VALUES_AS - ADDR_SPACE_OFFSET) & bit != 0 {
192 proof.push(top_tree[cur_node_idx * 2 - 1]);
193 cur_node_idx = cur_node_idx * 2 + 1;
194 } else {
195 proof.push(top_tree[cur_node_idx * 2]);
196 cur_node_idx *= 2;
197 }
198 }
199 for i in 0..address_leading_zeros {
200 proof.push(zero_nodes[address_height - 1 - i]);
202 }
203 proof.reverse();
204 proof
205}
206
207pub fn extract_public_values(num_public_values: usize, final_memory: &MemoryImage) -> Vec<u8> {
208 let mut public_values: Vec<u8> = {
209 assert_eq!(
210 final_memory.config[PUBLIC_VALUES_AS as usize].layout,
211 MemoryCellType::U8
212 );
213 final_memory.mem[PUBLIC_VALUES_AS as usize]
214 .as_slice()
215 .to_vec()
216 };
217
218 assert!(
219 public_values.len() >= num_public_values,
220 "Public values address space has {} elements, but configuration has num_public_values={}",
221 public_values.len(),
222 num_public_values
223 );
224 public_values.truncate(num_public_values);
225 public_values
226}
227
228#[cfg(test)]
229mod tests {
230 use openvm_stark_backend::p3_field::PrimeCharacteristicRing;
231 use openvm_stark_sdk::p3_baby_bear::BabyBear;
232
233 use super::UserPublicValuesProof;
234 use crate::{
235 arch::{hasher::poseidon2::vm_poseidon2_hasher, MemoryConfig, SystemConfig},
236 system::memory::{
237 merkle::{public_values::PUBLIC_VALUES_AS, tree::MerkleTree},
238 online::GuestMemory,
239 AddressMap, CHUNK,
240 },
241 };
242
243 type F = BabyBear;
244 #[test]
245 fn test_public_value_happy_path() {
246 let mut vm_config = SystemConfig::default();
247 let addr_space_height = 4;
248 vm_config.memory_config.addr_space_height = addr_space_height;
249 vm_config.memory_config.pointer_max_bits = 5;
250 let memory_dimensions = vm_config.memory_config.memory_dimensions();
251 let num_public_values = 16;
252 let mut addr_spaces_config = MemoryConfig::empty_address_space_configs(4);
253 addr_spaces_config[PUBLIC_VALUES_AS as usize].num_cells = num_public_values;
254 let mut memory = GuestMemory {
255 memory: AddressMap::new(addr_spaces_config),
256 };
257 unsafe {
258 memory.write::<u8, 4>(PUBLIC_VALUES_AS, 12, [0, 0, 0, 1]);
259 }
260 let mut expected_pvs = F::zero_vec(num_public_values);
261 expected_pvs[15] = F::ONE;
262
263 let hasher = vm_poseidon2_hasher();
264 let tree = MerkleTree::from_memory(&memory.memory, &memory_dimensions, &hasher);
265 let top_tree = tree.top_tree(addr_space_height);
266 let pv_proof = UserPublicValuesProof::<{ CHUNK }, F>::compute(
267 memory_dimensions,
268 num_public_values,
269 &hasher,
270 &memory.memory,
271 &top_tree,
272 );
273 assert_eq!(pv_proof.public_values, expected_pvs);
274 let final_memory_root =
275 MerkleTree::from_memory(&memory.memory, &memory_dimensions, &hasher).root();
276 pv_proof
277 .verify(&hasher, memory_dimensions, final_memory_root)
278 .unwrap();
279 }
280
281 #[test]
282 #[should_panic]
283 fn test_public_values_write_beyond_num_public_values_is_rejected() {
284 let num_public_values = 16;
285 let mut addr_spaces_config = MemoryConfig::empty_address_space_configs(4);
286 addr_spaces_config[PUBLIC_VALUES_AS as usize].num_cells = num_public_values;
287 let mut memory = GuestMemory {
288 memory: AddressMap::new(addr_spaces_config),
289 };
290 unsafe {
291 memory.write::<u8, 4>(PUBLIC_VALUES_AS, num_public_values as u32 + 4, [0, 0, 0, 1]);
292 }
293 }
294}