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 at least 3, got {0}")]
119    InvalidLengthInstances(usize),
120    #[error("Invalid length of user public values")]
121    InvalidUserPublicValuesLength,
122}
123
124#[cfg(feature = "evm-prove")]
125impl EvmProof {
126    #[cfg(feature = "evm-verify")]
127    /// Return bytes calldata to be passed to the verifier contract.
128    pub fn verifier_calldata(self) -> Vec<u8> {
129        use alloy_sol_types::SolCall;
130
131        use crate::solidity::IOpenVmHalo2Verifier;
132
133        let EvmProof {
134            user_public_values,
135            app_commit,
136            proof_data,
137            version: _,
138        } = self;
139
140        let ProofData { accumulator, proof } = proof_data;
141
142        let mut proof_data_bytes = accumulator;
143        proof_data_bytes.extend(proof);
144
145        IOpenVmHalo2Verifier::verifyCall {
146            publicValues: user_public_values.into(),
147            proofData: proof_data_bytes.into(),
148            appExeCommit: (*app_commit.app_exe_commit.as_slice()).into(),
149            appVmCommit: (*app_commit.app_vm_commit.as_slice()).into(),
150        }
151        .abi_encode()
152    }
153
154    #[cfg(feature = "evm-verify")]
155    pub fn fallback_calldata(&self) -> Vec<u8> {
156        let raw: openvm_static_verifier::keygen::RawEvmProof = self.clone().into();
157        encode_raw_evm_proof_calldata(&raw)
158    }
159}
160
161/// Encode a [`RawEvmProof`](openvm_static_verifier::keygen::RawEvmProof) as calldata for the
162/// fallback (raw) verifier.
163///
164/// Format: each instance as 32-byte big-endian, followed by raw proof bytes.
165#[cfg(feature = "evm-verify")]
166pub fn encode_raw_evm_proof_calldata(
167    proof: &openvm_static_verifier::keygen::RawEvmProof,
168) -> Vec<u8> {
169    let mut calldata = Vec::new();
170    for instance in &proof.instances {
171        // Fr::to_bytes() is little-endian; EVM expects big-endian
172        let mut bytes = instance.to_bytes();
173        bytes.reverse();
174        calldata.extend_from_slice(&bytes);
175    }
176    calldata.extend_from_slice(&proof.proof);
177    calldata
178}
179
180/// Convert `RawEvmProof` → `EvmProof`.
181///
182/// Instance layout (with KZG accumulator from wrapper circuit):
183/// - `instances[0..12]`: KZG accumulator (12 Fr values)
184/// - `instances[12]`: app_exe_commit (Fr)
185/// - `instances[13]`: app_vm_commit (Fr)
186/// - `instances[14..]`: user public values (each byte as Fr)
187#[cfg(feature = "evm-prove")]
188impl From<openvm_static_verifier::keygen::RawEvmProof> for EvmProof {
189    fn from(raw: openvm_static_verifier::keygen::RawEvmProof) -> Self {
190        use openvm_continuations::CommitBytes;
191
192        let openvm_static_verifier::keygen::RawEvmProof { instances, proof } = raw;
193        assert!(
194            instances.len() > NUM_BN254_ACCUMULATOR + 2,
195            "RawEvmProof instances must have at least {} elements (accumulator + exe commit + vk commit)",
196            NUM_BN254_ACCUMULATOR + 2
197        );
198
199        // instances[0..12] are the KZG accumulator
200        let accumulator = instances[0..NUM_BN254_ACCUMULATOR]
201            .iter()
202            .flat_map(|f| f.to_bytes())
203            .collect::<Vec<_>>();
204
205        // Reverse each 32-byte chunk for big-endian EVM format
206        let mut evm_accumulator = Vec::with_capacity(accumulator.len());
207        accumulator
208            .chunks(BN254_BYTES)
209            .for_each(|chunk| evm_accumulator.extend(chunk.iter().rev().copied()));
210
211        // instances[12] and [13] are Fr values encoding commits.
212        // Fr::to_bytes() returns 32 bytes in little-endian; CommitBytes expects big-endian.
213        let mut app_exe_bytes = instances[NUM_BN254_ACCUMULATOR].to_bytes();
214        app_exe_bytes.reverse();
215        let mut app_vm_bytes = instances[NUM_BN254_ACCUMULATOR + 1].to_bytes();
216        app_vm_bytes.reverse();
217
218        let user_public_values = instances[NUM_BN254_ACCUMULATOR + 2..]
219            .iter()
220            .map(|f| {
221                // Each user public value is a single byte stored in the least significant position
222                f.to_bytes()[0]
223            })
224            .collect::<Vec<u8>>();
225
226        let app_commit = AppExecutionCommit {
227            app_exe_commit: CommitBytes::new(app_exe_bytes),
228            app_vm_commit: CommitBytes::new(app_vm_bytes),
229        };
230
231        Self {
232            version: format!("v{OPENVM_VERSION}"),
233            app_commit,
234            user_public_values,
235            proof_data: ProofData {
236                accumulator: evm_accumulator,
237                proof,
238            },
239        }
240    }
241}
242
243/// Convert `EvmProof` → `RawEvmProof`.
244#[cfg(feature = "evm-prove")]
245impl From<EvmProof> for openvm_static_verifier::keygen::RawEvmProof {
246    fn from(evm_proof: EvmProof) -> Self {
247        use openvm_static_verifier::Fr;
248
249        let EvmProof {
250            app_commit,
251            user_public_values,
252            proof_data,
253            version: _,
254        } = evm_proof;
255
256        let ProofData { accumulator, proof } = proof_data;
257
258        // Reverse each 32-byte chunk from big-endian (EVM) to little-endian (Fr)
259        let mut reversed_accumulator = Vec::with_capacity(accumulator.len());
260        accumulator
261            .chunks(BN254_BYTES)
262            .for_each(|chunk| reversed_accumulator.extend(chunk.iter().rev().copied()));
263
264        // CommitBytes is big-endian; Fr::from_bytes expects little-endian
265        let mut app_exe_bytes = *app_commit.app_exe_commit.as_slice();
266        app_exe_bytes.reverse();
267        let app_exe_fr = Fr::from_bytes(&app_exe_bytes).unwrap();
268
269        let mut app_vm_bytes = *app_commit.app_vm_commit.as_slice();
270        app_vm_bytes.reverse();
271        let app_vm_fr = Fr::from_bytes(&app_vm_bytes).unwrap();
272
273        let user_pvs_frs: Vec<Fr> = user_public_values
274            .into_iter()
275            .map(|byte| {
276                let mut bytes = [0u8; 32];
277                bytes[0] = byte;
278                Fr::from_bytes(&bytes).unwrap()
279            })
280            .collect();
281
282        // Reconstruct instances: accumulator + commits + user PVs
283        let mut instances = Vec::new();
284        for chunk in reversed_accumulator.chunks(BN254_BYTES) {
285            let c: [u8; 32] = chunk.try_into().unwrap();
286            instances.push(Fr::from_bytes(&c).unwrap());
287        }
288        instances.push(app_exe_fr);
289        instances.push(app_vm_fr);
290        instances.extend(user_pvs_frs);
291
292        openvm_static_verifier::keygen::RawEvmProof { instances, proof }
293    }
294}
295
296// =================== Non-EVM types ===================
297
298/// Struct purely for encoding and decoding of [VmStarkProof].
299#[serde_as]
300#[derive(Clone, Debug, Deserialize, Serialize, Encode, Decode)]
301pub struct VersionedVmStarkProof {
302    /// The openvm major and minor version v{}.{}. The proof format will not change on patch
303    /// versions.
304    pub version: String,
305    #[serde_as(as = "serde_with::hex::Hex")]
306    pub proof: Vec<u8>,
307    #[serde_as(as = "serde_with::hex::Hex")]
308    pub user_pvs_proof: Vec<u8>,
309    #[serde(default)]
310    #[serde_as(as = "Option<serde_with::hex::Hex>")]
311    pub deferral_merkle_proofs: Option<Vec<u8>>,
312}
313
314impl VersionedVmStarkProof {
315    pub fn new(proof: VmStarkProof) -> Result<Self> {
316        Ok(Self {
317            version: format!("v{}", OPENVM_VERSION),
318            proof: proof.inner.encode_to_vec()?,
319            user_pvs_proof: {
320                let mut buf = Vec::new();
321                proof.user_pvs_proof.encode::<crate::SC, _>(&mut buf)?;
322                buf
323            },
324            deferral_merkle_proofs: proof
325                .deferral_merkle_proofs
326                .map(|ref dmp| {
327                    let mut buf = Vec::new();
328                    dmp.encode(&mut buf)?;
329                    Ok::<_, std::io::Error>(buf)
330                })
331                .transpose()?,
332        })
333    }
334}
335
336impl TryFrom<VersionedVmStarkProof> for VmStarkProof {
337    type Error = std::io::Error;
338    fn try_from(proof: VersionedVmStarkProof) -> Result<Self, std::io::Error> {
339        let VersionedVmStarkProof {
340            proof,
341            user_pvs_proof,
342            deferral_merkle_proofs,
343            ..
344        } = proof;
345        Ok(Self {
346            inner: Proof::<crate::SC>::decode_from_bytes(&proof)?,
347            user_pvs_proof: UserPublicValuesProof::decode::<crate::SC, _>(
348                &mut std::io::Cursor::new(&user_pvs_proof),
349            )?,
350            deferral_merkle_proofs: deferral_merkle_proofs
351                .map(|bytes| DeferralMerkleProofs::decode(&mut std::io::Cursor::new(&bytes)))
352                .transpose()?,
353        })
354    }
355}
356
357// =================== Verification baseline JSON types ===================
358
359/// Hex-formatted [`VkCommit`] for JSON serialization.
360#[derive(Clone, Debug, Serialize, Deserialize)]
361pub struct VkCommitJson {
362    pub cached_commit: CommitBytes,
363    pub vk_pre_hash: CommitBytes,
364}
365
366/// Hex-formatted [`VerificationBaseline`] for JSON serialization.
367///
368/// Mirrors [`VerificationBaseline`] but serializes all commit fields as `0x`-prefixed hex strings,
369/// consistent with [`AppExecutionCommit`].
370#[derive(Clone, Debug, Serialize, Deserialize)]
371pub struct VerificationBaselineJson {
372    pub app_exe_commit: CommitBytes,
373    pub memory_dimensions: MemoryDimensions,
374    pub num_user_pvs: usize,
375    pub app_vk_commit: VkCommitJson,
376    pub leaf_vk_commit: VkCommitJson,
377    pub internal_for_leaf_vk_commit: VkCommitJson,
378    pub internal_recursive_vk_commit: VkCommitJson,
379    pub expected_def_hook_commit: Option<CommitBytes>,
380}
381
382impl From<VerificationBaseline> for VerificationBaselineJson {
383    fn from(b: VerificationBaseline) -> Self {
384        let vk = |d: VkCommit<crate::F>| VkCommitJson {
385            cached_commit: CommitBytes::from(d.cached_commit),
386            vk_pre_hash: CommitBytes::from(d.vk_pre_hash),
387        };
388        Self {
389            app_exe_commit: CommitBytes::from(b.app_exe_commit),
390            memory_dimensions: b.memory_dimensions,
391            num_user_pvs: b.num_user_pvs,
392            app_vk_commit: vk(b.app_vk_commit),
393            leaf_vk_commit: vk(b.leaf_vk_commit),
394            internal_for_leaf_vk_commit: vk(b.internal_for_leaf_vk_commit),
395            internal_recursive_vk_commit: vk(b.internal_recursive_vk_commit),
396            expected_def_hook_commit: b.expected_def_hook_commit.map(CommitBytes::from),
397        }
398    }
399}
400
401impl From<VerificationBaselineJson> for VerificationBaseline {
402    fn from(b: VerificationBaselineJson) -> Self {
403        use openvm_verify_stark_host::pvs::VkCommit;
404        let vk = |d: VkCommitJson| VkCommit {
405            cached_commit: d.cached_commit.into(),
406            vk_pre_hash: d.vk_pre_hash.into(),
407        };
408        Self {
409            app_exe_commit: b.app_exe_commit.into(),
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(|c| c.into()),
417        }
418    }
419}