openvm_sdk/
types.rs

1use std::sync::Arc;
2
3use derive_more::derive::From;
4use eyre::Result;
5use openvm::platform::memory::MEM_SIZE;
6use openvm_circuit::{
7    arch::instructions::exe::VmExe,
8    system::memory::{dimensions::MemoryDimensions, merkle::public_values::UserPublicValuesProof},
9};
10use openvm_continuations::CommitBytes;
11use openvm_stark_backend::{
12    codec::{Decode, Encode},
13    proof::Proof,
14};
15use openvm_transpiler::elf::Elf;
16use openvm_verify_stark_host::{
17    deferral::DeferralMerkleProofs, pvs::VkCommit, vk::VerificationBaseline, VmStarkProof,
18};
19use serde::{Deserialize, Serialize};
20use serde_with::serde_as;
21
22use crate::OPENVM_VERSION;
23
24#[derive(From)]
25pub enum ExecutableFormat {
26    Elf(Elf),
27    VmExe(VmExe<crate::F>),
28    SharedVmExe(Arc<VmExe<crate::F>>),
29}
30
31impl<'a> From<&'a [u8]> for ExecutableFormat {
32    fn from(bytes: &'a [u8]) -> Self {
33        let elf = Elf::decode(bytes, MEM_SIZE.try_into().unwrap()).expect("Invalid ELF bytes");
34        ExecutableFormat::Elf(elf)
35    }
36}
37impl From<Vec<u8>> for ExecutableFormat {
38    fn from(bytes: Vec<u8>) -> Self {
39        ExecutableFormat::from(&bytes[..])
40    }
41}
42
43/// Number of bytes in a Bn254.
44#[allow(dead_code)]
45pub(crate) const BN254_BYTES: usize = 32;
46/// Number of Bn254 in `accumulator` field (KZG accumulator).
47pub const NUM_BN254_ACCUMULATOR: usize = 12;
48/// Number of Bn254 in `proof` field for a circuit with only 1 advice column.
49#[cfg(feature = "evm-prove")]
50#[allow(dead_code)]
51pub(crate) const NUM_BN254_PROOF: usize = 43;
52
53#[derive(Clone, Debug, Deserialize, Serialize)]
54pub struct ProofData {
55    #[serde(with = "prefixed_hex")]
56    /// KZG accumulator.
57    pub accumulator: Vec<u8>,
58    #[serde(with = "prefixed_hex")]
59    /// Bn254 proof in little-endian bytes. The circuit only has 1 advice column, so the proof is
60    /// of length `NUM_BN254_PROOF * BN254_BYTES`.
61    pub proof: Vec<u8>,
62}
63
64mod prefixed_hex {
65    use serde::{Deserialize, Deserializer, Serializer};
66
67    pub fn serialize<S: Serializer>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error> {
68        serializer.serialize_str(&format!("0x{}", hex::encode(bytes)))
69    }
70
71    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
72        let hex_str = String::deserialize(deserializer)?;
73        let hex_str = hex_str.strip_prefix("0x").unwrap_or(&hex_str);
74        hex::decode(hex_str).map_err(serde::de::Error::custom)
75    }
76}
77
78// =================== EVM types (evm-prove feature) ===================
79
80#[cfg(feature = "evm-prove")]
81pub use openvm_static_verifier::wrapper::EvmVerifierByteCode;
82
83#[cfg(feature = "evm-prove")]
84#[derive(Clone, Debug, Serialize, Deserialize)]
85pub struct EvmHalo2Verifier {
86    pub halo2_verifier_code: String,
87    pub openvm_verifier_code: String,
88    pub openvm_verifier_interface: String,
89    pub artifact: EvmVerifierByteCode,
90}
91
92/// Application execution commitment pair (big-endian 32-byte values).
93#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
94pub struct AppExecutionCommit {
95    pub app_exe_commit: openvm_continuations::CommitBytes,
96    pub app_vm_commit: openvm_continuations::CommitBytes,
97}
98
99#[cfg(feature = "evm-prove")]
100#[derive(Clone, Debug, Deserialize, Serialize)]
101pub struct EvmProof {
102    /// The openvm major and minor version v{}.{}. The proof format will not change on patch
103    /// versions.
104    pub version: String,
105    #[serde(flatten)]
106    /// Bn254 public value app commits.
107    pub app_commit: AppExecutionCommit,
108    #[serde(with = "prefixed_hex")]
109    /// User public values packed into bytes.
110    pub user_public_values: Vec<u8>,
111    /// Byte encoding of the `proof`.
112    pub proof_data: ProofData,
113}
114
115#[cfg(feature = "evm-prove")]
116#[derive(Debug, thiserror::Error)]
117pub enum EvmProofConversionError {
118    #[error("Invalid length of instances: expected more than {}, got {0}", NUM_BN254_ACCUMULATOR + 2)]
119    InvalidLengthInstances(usize),
120    #[error("Accumulator length {0} is not a multiple of {BN254_BYTES}")]
121    InvalidAccumulatorLength(usize),
122    #[error("Value is not a canonical Bn254 scalar")]
123    NonCanonicalScalar,
124    #[error(transparent)]
125    NonCanonicalCommit(#[from] openvm_continuations::CommitBytesError),
126}
127
128#[cfg(feature = "evm-prove")]
129impl EvmProof {
130    #[cfg(feature = "evm-verify")]
131    /// Return bytes calldata to be passed to the verifier contract.
132    pub fn verifier_calldata(self) -> Vec<u8> {
133        use alloy_sol_types::SolCall;
134
135        use crate::solidity::IOpenVmHalo2Verifier;
136
137        let EvmProof {
138            user_public_values,
139            app_commit,
140            proof_data,
141            version: _,
142        } = self;
143
144        let ProofData { accumulator, proof } = proof_data;
145
146        let mut proof_data_bytes = accumulator;
147        proof_data_bytes.extend(proof);
148
149        IOpenVmHalo2Verifier::verifyCall {
150            publicValues: user_public_values.into(),
151            proofData: proof_data_bytes.into(),
152            appExeCommit: (*app_commit.app_exe_commit.as_slice()).into(),
153            appVmCommit: (*app_commit.app_vm_commit.as_slice()).into(),
154        }
155        .abi_encode()
156    }
157
158    #[cfg(feature = "evm-verify")]
159    pub fn fallback_calldata(&self) -> Result<Vec<u8>, EvmProofConversionError> {
160        let raw: openvm_static_verifier::keygen::RawEvmProof = self.clone().try_into()?;
161        Ok(encode_raw_evm_proof_calldata(&raw))
162    }
163}
164
165/// Encode a [`RawEvmProof`](openvm_static_verifier::keygen::RawEvmProof) as calldata for the
166/// fallback (raw) verifier.
167///
168/// Format: each instance as 32-byte big-endian, followed by raw proof bytes.
169#[cfg(feature = "evm-verify")]
170pub fn encode_raw_evm_proof_calldata(
171    proof: &openvm_static_verifier::keygen::RawEvmProof,
172) -> Vec<u8> {
173    let mut calldata = Vec::new();
174    for instance in &proof.instances {
175        // Fr::to_bytes() is little-endian; EVM expects big-endian
176        let mut bytes = instance.to_bytes();
177        bytes.reverse();
178        calldata.extend_from_slice(&bytes);
179    }
180    calldata.extend_from_slice(&proof.proof);
181    calldata
182}
183
184/// Convert `RawEvmProof` → `EvmProof`.
185///
186/// Instance layout (with KZG accumulator from wrapper circuit):
187/// - `instances[0..12]`: KZG accumulator (12 Fr values)
188/// - `instances[12]`: app_exe_commit (Fr)
189/// - `instances[13]`: app_vm_commit (Fr)
190/// - `instances[14..]`: user public values (each byte as Fr)
191#[cfg(feature = "evm-prove")]
192impl TryFrom<openvm_static_verifier::keygen::RawEvmProof> for EvmProof {
193    type Error = EvmProofConversionError;
194
195    fn try_from(raw: openvm_static_verifier::keygen::RawEvmProof) -> Result<Self, Self::Error> {
196        use openvm_continuations::CommitBytes;
197
198        let openvm_static_verifier::keygen::RawEvmProof { instances, proof } = raw;
199        if instances.len() <= NUM_BN254_ACCUMULATOR + 2 {
200            return Err(EvmProofConversionError::InvalidLengthInstances(
201                instances.len(),
202            ));
203        }
204
205        // instances[0..12] are the KZG accumulator
206        let accumulator = instances[0..NUM_BN254_ACCUMULATOR]
207            .iter()
208            .flat_map(|f| f.to_bytes())
209            .collect::<Vec<_>>();
210
211        // Reverse each 32-byte chunk for big-endian EVM format
212        let mut evm_accumulator = Vec::with_capacity(accumulator.len());
213        accumulator
214            .chunks(BN254_BYTES)
215            .for_each(|chunk| evm_accumulator.extend(chunk.iter().rev().copied()));
216
217        // instances[12] and [13] are Fr values encoding commits.
218        // Fr::to_bytes() returns 32 bytes in little-endian; CommitBytes expects big-endian.
219        let mut app_exe_bytes = instances[NUM_BN254_ACCUMULATOR].to_bytes();
220        app_exe_bytes.reverse();
221        let mut app_vm_bytes = instances[NUM_BN254_ACCUMULATOR + 1].to_bytes();
222        app_vm_bytes.reverse();
223
224        let user_public_values = instances[NUM_BN254_ACCUMULATOR + 2..]
225            .iter()
226            .map(|f| {
227                // Each user public value is a single byte stored in the least significant position
228                f.to_bytes()[0]
229            })
230            .collect::<Vec<u8>>();
231
232        let app_commit = AppExecutionCommit {
233            app_exe_commit: CommitBytes::try_new(app_exe_bytes)?,
234            app_vm_commit: CommitBytes::try_new(app_vm_bytes)?,
235        };
236
237        Ok(Self {
238            version: format!("v{OPENVM_VERSION}"),
239            app_commit,
240            user_public_values,
241            proof_data: ProofData {
242                accumulator: evm_accumulator,
243                proof,
244            },
245        })
246    }
247}
248
249/// Convert `EvmProof` → `RawEvmProof`. Fallible because `evm_proof` may be untrusted.
250#[cfg(feature = "evm-prove")]
251impl TryFrom<EvmProof> for openvm_static_verifier::keygen::RawEvmProof {
252    type Error = EvmProofConversionError;
253
254    fn try_from(evm_proof: EvmProof) -> Result<Self, Self::Error> {
255        use openvm_static_verifier::Fr;
256
257        fn to_fr(le_bytes: &[u8; 32]) -> Result<Fr, EvmProofConversionError> {
258            Option::from(Fr::from_bytes(le_bytes))
259                .ok_or(EvmProofConversionError::NonCanonicalScalar)
260        }
261
262        let EvmProof {
263            app_commit,
264            user_public_values,
265            proof_data,
266            version: _,
267        } = evm_proof;
268
269        let ProofData { accumulator, proof } = proof_data;
270
271        if !accumulator.len().is_multiple_of(BN254_BYTES) {
272            return Err(EvmProofConversionError::InvalidAccumulatorLength(
273                accumulator.len(),
274            ));
275        }
276
277        // Reverse each 32-byte chunk from big-endian (EVM) to little-endian (Fr)
278        let mut reversed_accumulator = Vec::with_capacity(accumulator.len());
279        accumulator
280            .chunks(BN254_BYTES)
281            .for_each(|chunk| reversed_accumulator.extend(chunk.iter().rev().copied()));
282
283        // CommitBytes is big-endian; Fr::from_bytes expects little-endian
284        let mut app_exe_bytes = *app_commit.app_exe_commit.as_slice();
285        app_exe_bytes.reverse();
286        let app_exe_fr = to_fr(&app_exe_bytes)?;
287
288        let mut app_vm_bytes = *app_commit.app_vm_commit.as_slice();
289        app_vm_bytes.reverse();
290        let app_vm_fr = to_fr(&app_vm_bytes)?;
291
292        let user_pvs_frs: Vec<Fr> = user_public_values
293            .into_iter()
294            .map(|byte| {
295                let mut bytes = [0u8; 32];
296                bytes[0] = byte;
297                to_fr(&bytes)
298            })
299            .collect::<Result<_, _>>()?;
300
301        // Reconstruct instances: accumulator + commits + user PVs
302        let mut instances = Vec::new();
303        for chunk in reversed_accumulator.chunks(BN254_BYTES) {
304            // Length checked above, so every chunk is full-width.
305            let c: [u8; 32] = chunk.try_into().unwrap();
306            instances.push(to_fr(&c)?);
307        }
308        instances.push(app_exe_fr);
309        instances.push(app_vm_fr);
310        instances.extend(user_pvs_frs);
311
312        Ok(openvm_static_verifier::keygen::RawEvmProof { instances, proof })
313    }
314}
315
316// =================== Non-EVM types ===================
317
318/// Struct purely for encoding and decoding of [VmStarkProof].
319#[serde_as]
320#[derive(Clone, Debug, Deserialize, Serialize, Encode, Decode)]
321pub struct VersionedVmStarkProof {
322    /// The openvm major and minor version v{}.{}. The proof format will not change on patch
323    /// versions.
324    pub version: String,
325    #[serde_as(as = "serde_with::hex::Hex")]
326    pub proof: Vec<u8>,
327    #[serde_as(as = "serde_with::hex::Hex")]
328    pub user_pvs_proof: Vec<u8>,
329    #[serde(default)]
330    #[serde_as(as = "Option<serde_with::hex::Hex>")]
331    pub deferral_merkle_proofs: Option<Vec<u8>>,
332}
333
334impl VersionedVmStarkProof {
335    pub fn new(proof: VmStarkProof) -> Result<Self> {
336        Ok(Self {
337            version: format!("v{}", OPENVM_VERSION),
338            proof: proof.inner.encode_to_vec()?,
339            user_pvs_proof: {
340                let mut buf = Vec::new();
341                proof.user_pvs_proof.encode::<crate::SC, _>(&mut buf)?;
342                buf
343            },
344            deferral_merkle_proofs: proof
345                .deferral_merkle_proofs
346                .map(|ref dmp| {
347                    let mut buf = Vec::new();
348                    dmp.encode(&mut buf)?;
349                    Ok::<_, std::io::Error>(buf)
350                })
351                .transpose()?,
352        })
353    }
354}
355
356impl TryFrom<VersionedVmStarkProof> for VmStarkProof {
357    type Error = std::io::Error;
358    fn try_from(proof: VersionedVmStarkProof) -> Result<Self, std::io::Error> {
359        let VersionedVmStarkProof {
360            proof,
361            user_pvs_proof,
362            deferral_merkle_proofs,
363            ..
364        } = proof;
365        Ok(Self {
366            inner: Proof::<crate::SC>::decode_from_bytes(&proof)?,
367            user_pvs_proof: UserPublicValuesProof::decode::<crate::SC, _>(
368                &mut std::io::Cursor::new(&user_pvs_proof),
369            )?,
370            deferral_merkle_proofs: deferral_merkle_proofs
371                .map(|bytes| DeferralMerkleProofs::decode(&mut std::io::Cursor::new(&bytes)))
372                .transpose()?,
373        })
374    }
375}
376
377// =================== Verification baseline JSON types ===================
378
379/// Hex-formatted [`VkCommit`] for JSON serialization.
380#[derive(Clone, Debug, Serialize, Deserialize)]
381pub struct VkCommitJson {
382    pub cached_commit: CommitBytes,
383    pub vk_pre_hash: CommitBytes,
384}
385
386/// Hex-formatted [`VerificationBaseline`] for JSON serialization.
387///
388/// Mirrors [`VerificationBaseline`] but serializes all commit fields as `0x`-prefixed hex strings,
389/// consistent with [`AppExecutionCommit`].
390#[derive(Clone, Debug, Serialize, Deserialize)]
391pub struct VerificationBaselineJson {
392    pub app_exe_commit: CommitBytes,
393    pub memory_dimensions: MemoryDimensions,
394    pub num_user_pvs: usize,
395    pub app_vk_commit: VkCommitJson,
396    pub leaf_vk_commit: VkCommitJson,
397    pub internal_for_leaf_vk_commit: VkCommitJson,
398    pub internal_recursive_vk_commit: VkCommitJson,
399    pub expected_def_hook_commit: Option<CommitBytes>,
400}
401
402impl From<VerificationBaseline> for VerificationBaselineJson {
403    fn from(b: VerificationBaseline) -> Self {
404        let vk = |d: VkCommit<crate::F>| VkCommitJson {
405            cached_commit: CommitBytes::from(d.cached_commit),
406            vk_pre_hash: CommitBytes::from(d.vk_pre_hash),
407        };
408        Self {
409            app_exe_commit: CommitBytes::from(b.app_exe_commit),
410            memory_dimensions: b.memory_dimensions,
411            num_user_pvs: b.num_user_pvs,
412            app_vk_commit: vk(b.app_vk_commit),
413            leaf_vk_commit: vk(b.leaf_vk_commit),
414            internal_for_leaf_vk_commit: vk(b.internal_for_leaf_vk_commit),
415            internal_recursive_vk_commit: vk(b.internal_recursive_vk_commit),
416            expected_def_hook_commit: b.expected_def_hook_commit.map(CommitBytes::from),
417        }
418    }
419}
420
421impl From<VerificationBaselineJson> for VerificationBaseline {
422    fn from(b: VerificationBaselineJson) -> Self {
423        use openvm_verify_stark_host::pvs::VkCommit;
424        let vk = |d: VkCommitJson| VkCommit {
425            cached_commit: d.cached_commit.into(),
426            vk_pre_hash: d.vk_pre_hash.into(),
427        };
428        Self {
429            app_exe_commit: b.app_exe_commit.into(),
430            memory_dimensions: b.memory_dimensions,
431            num_user_pvs: b.num_user_pvs,
432            app_vk_commit: vk(b.app_vk_commit),
433            leaf_vk_commit: vk(b.leaf_vk_commit),
434            internal_for_leaf_vk_commit: vk(b.internal_for_leaf_vk_commit),
435            internal_recursive_vk_commit: vk(b.internal_recursive_vk_commit),
436            expected_def_hook_commit: b.expected_def_hook_commit.map(|c| c.into()),
437        }
438    }
439}