risc0_zkvm/host/api/
convert.rs

1// Copyright 2025 RISC Zero, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{fmt::Debug, path::PathBuf};
16
17use anyhow::{anyhow, bail, Result};
18use prost::{Message, Name};
19use risc0_binfmt::SystemState;
20use risc0_circuit_keccak::KeccakState;
21use risc0_zkp::core::digest::Digest;
22use serde::Serialize;
23
24use super::{malformed_err, path_to_string, pb, Asset, AssetRequest, RedisParams};
25use crate::{
26    host::client::env::{ProveKeccakRequest, ProveZkrRequest},
27    receipt::{
28        merkle::MerkleProof, CompositeReceipt, FakeReceipt, InnerAssumptionReceipt, InnerReceipt,
29        ReceiptMetadata, SegmentReceipt, SuccinctReceipt,
30    },
31    receipt_claim::{UnionClaim, Unknown},
32    Assumption, Assumptions, ExitCode, Groth16Receipt, Input, Journal, MaybePruned, Output,
33    ProveInfo, ProverOpts, Receipt, ReceiptClaim, ReceiptKind, SessionStats, TraceEvent,
34};
35
36mod ver {
37    use super::pb::base::CompatVersion;
38
39    pub const RECEIPT: CompatVersion = CompatVersion { value: 1 };
40    pub const SEGMENT_RECEIPT: CompatVersion = CompatVersion { value: 1 };
41    pub const SUCCINCT_RECEIPT: CompatVersion = CompatVersion { value: 1 };
42    pub const GROTH16_RECEIPT: CompatVersion = CompatVersion { value: 1 };
43}
44
45impl TryFrom<AssetRequest> for pb::api::AssetRequest {
46    type Error = anyhow::Error;
47
48    fn try_from(value: AssetRequest) -> Result<Self> {
49        Ok(Self {
50            kind: Some(match value {
51                AssetRequest::Inline => pb::api::asset_request::Kind::Inline(()),
52                AssetRequest::Path(path) => {
53                    pb::api::asset_request::Kind::Path(path_to_string(path)?)
54                }
55                AssetRequest::Redis(params) => {
56                    pb::api::asset_request::Kind::Redis(pb::api::RedisParams {
57                        url: params.url,
58                        key: params.key,
59                        ttl: params.ttl,
60                    })
61                }
62            }),
63        })
64    }
65}
66
67impl TryFrom<Asset> for pb::api::Asset {
68    type Error = anyhow::Error;
69
70    fn try_from(value: Asset) -> Result<Self> {
71        Ok(Self {
72            kind: match value {
73                Asset::Inline(bytes) => Some(pb::api::asset::Kind::Inline(bytes.into())),
74                Asset::Path(path) => Some(pb::api::asset::Kind::Path(path_to_string(path)?)),
75                Asset::Redis(key) => Some(pb::api::asset::Kind::Redis(key)),
76            },
77        })
78    }
79}
80
81impl<Claim> TryFrom<SuccinctReceipt<Claim>> for Asset
82where
83    Claim: risc0_binfmt::Digestible + Debug + Clone + Serialize,
84{
85    type Error = anyhow::Error;
86
87    fn try_from(succinct_receipt: SuccinctReceipt<Claim>) -> Result<Self> {
88        Ok(Asset::Inline(bincode::serialize(&succinct_receipt)?.into()))
89    }
90}
91
92impl TryFrom<SegmentReceipt> for Asset {
93    type Error = anyhow::Error;
94
95    fn try_from(segment_receipt: SegmentReceipt) -> Result<Self> {
96        Ok(Asset::Inline(bincode::serialize(&segment_receipt)?.into()))
97    }
98}
99
100impl TryFrom<Receipt> for Asset {
101    type Error = anyhow::Error;
102
103    fn try_from(receipt: Receipt) -> Result<Self> {
104        Ok(Asset::Inline(bincode::serialize(&receipt)?.into()))
105    }
106}
107
108impl TryFrom<pb::api::Asset> for Asset {
109    type Error = anyhow::Error;
110
111    fn try_from(value: pb::api::Asset) -> Result<Self> {
112        Ok(
113            match value.kind.ok_or_else(|| malformed_err("Asset.kind"))? {
114                pb::api::asset::Kind::Inline(bytes) => Asset::Inline(bytes.into()),
115                pb::api::asset::Kind::Path(path) => Asset::Path(PathBuf::from(path)),
116                pb::api::asset::Kind::Redis(key) => Asset::Redis(key),
117            },
118        )
119    }
120}
121
122impl TryFrom<pb::api::AssetRequest> for AssetRequest {
123    type Error = anyhow::Error;
124
125    fn try_from(value: pb::api::AssetRequest) -> Result<Self> {
126        Ok(
127            match value
128                .kind
129                .ok_or_else(|| malformed_err("AssetRequest.kind"))?
130            {
131                pb::api::asset_request::Kind::Inline(()) => AssetRequest::Inline,
132                pb::api::asset_request::Kind::Path(path) => {
133                    AssetRequest::Path(std::path::PathBuf::from(path))
134                }
135                pb::api::asset_request::Kind::Redis(params) => AssetRequest::Redis(RedisParams {
136                    url: params.url,
137                    key: params.key,
138                    ttl: params.ttl,
139                }),
140            },
141        )
142    }
143}
144
145impl TryFrom<TraceEvent> for pb::api::TraceEvent {
146    type Error = anyhow::Error;
147
148    fn try_from(event: TraceEvent) -> Result<Self> {
149        match event {
150            TraceEvent::InstructionStart { cycle, pc, insn } => Ok(Self {
151                kind: Some(pb::api::trace_event::Kind::InsnStart(
152                    pb::api::trace_event::InstructionStart { cycle, pc, insn },
153                )),
154            }),
155            TraceEvent::RegisterSet { idx, value } => Ok(Self {
156                kind: Some(pb::api::trace_event::Kind::RegisterSet(
157                    pb::api::trace_event::RegisterSet {
158                        idx: idx as u32,
159                        value,
160                    },
161                )),
162            }),
163            TraceEvent::MemorySet { addr, region } => Ok(Self {
164                kind: Some(pb::api::trace_event::Kind::MemorySet(
165                    pb::api::trace_event::MemorySet {
166                        addr,
167                        value: 0,
168                        region,
169                    },
170                )),
171            }),
172            TraceEvent::PageIn { cycles } => Ok(Self {
173                kind: Some(pb::api::trace_event::Kind::PageIn(
174                    pb::api::trace_event::PageIn { cycles },
175                )),
176            }),
177            TraceEvent::PageOut { cycles } => Ok(Self {
178                kind: Some(pb::api::trace_event::Kind::PageOut(
179                    pb::api::trace_event::PageOut { cycles },
180                )),
181            }),
182            _ => Err(anyhow!("unknown TraceEvent kind")),
183        }
184    }
185}
186
187impl TryFrom<pb::api::TraceEvent> for TraceEvent {
188    type Error = anyhow::Error;
189
190    fn try_from(event: pb::api::TraceEvent) -> Result<Self> {
191        Ok(
192            match event.kind.ok_or_else(|| malformed_err("TraceEvent.kind"))? {
193                pb::api::trace_event::Kind::InsnStart(event) => TraceEvent::InstructionStart {
194                    cycle: event.cycle,
195                    pc: event.pc,
196                    insn: event.insn,
197                },
198                pb::api::trace_event::Kind::RegisterSet(event) => TraceEvent::RegisterSet {
199                    idx: event.idx as usize,
200                    value: event.value,
201                },
202                pb::api::trace_event::Kind::MemorySet(event) => TraceEvent::MemorySet {
203                    addr: event.addr,
204                    region: event.region,
205                },
206                pb::api::trace_event::Kind::PageIn(event) => TraceEvent::PageIn {
207                    cycles: event.cycles,
208                },
209                pb::api::trace_event::Kind::PageOut(event) => TraceEvent::PageOut {
210                    cycles: event.cycles,
211                },
212            },
213        )
214    }
215}
216
217impl TryFrom<ExitCode> for pb::base::ExitCode {
218    type Error = anyhow::Error;
219
220    fn try_from(value: ExitCode) -> Result<Self> {
221        Ok(Self {
222            kind: Some(match value {
223                ExitCode::SystemSplit => pb::base::exit_code::Kind::SystemSplit(()),
224                ExitCode::SessionLimit => pb::base::exit_code::Kind::SessionLimit(()),
225                ExitCode::Paused(code) => pb::base::exit_code::Kind::Paused(code),
226                ExitCode::Halted(code) => pb::base::exit_code::Kind::Halted(code),
227                e => bail!("unsupported exit code: {e:?}"),
228            }),
229        })
230    }
231}
232
233impl TryFrom<pb::base::ExitCode> for ExitCode {
234    type Error = anyhow::Error;
235
236    fn try_from(value: pb::base::ExitCode) -> Result<Self> {
237        Ok(
238            match value.kind.ok_or_else(|| malformed_err("ExitCode.kind"))? {
239                pb::base::exit_code::Kind::Halted(code) => Self::Halted(code),
240                pb::base::exit_code::Kind::Paused(code) => Self::Paused(code),
241                pb::base::exit_code::Kind::SystemSplit(_) => Self::SystemSplit,
242                pb::base::exit_code::Kind::SessionLimit(_) => Self::SessionLimit,
243            },
244        )
245    }
246}
247
248impl From<Result<(), anyhow::Error>> for pb::api::GenericReply {
249    fn from(result: Result<(), anyhow::Error>) -> Self {
250        Self {
251            kind: Some(match result {
252                Ok(_) => pb::api::generic_reply::Kind::Ok(()),
253                Err(err) => pb::api::generic_reply::Kind::Error(err.into()),
254            }),
255        }
256    }
257}
258
259impl From<anyhow::Error> for pb::api::GenericError {
260    fn from(err: anyhow::Error) -> Self {
261        Self {
262            reason: err.to_string(),
263        }
264    }
265}
266
267impl From<pb::api::GenericError> for anyhow::Error {
268    fn from(err: pb::api::GenericError) -> Self {
269        anyhow::Error::msg(err.reason)
270    }
271}
272
273impl TryFrom<pb::api::ProverOpts> for ProverOpts {
274    type Error = anyhow::Error;
275
276    fn try_from(opts: pb::api::ProverOpts) -> Result<Self> {
277        Ok(Self {
278            hashfn: opts.hashfn,
279            prove_guest_errors: opts.prove_guest_errors,
280            receipt_kind: match opts.receipt_kind {
281                0 => ReceiptKind::Composite,
282                1 => ReceiptKind::Succinct,
283                2 => ReceiptKind::Groth16,
284                value => panic!("Unknown receipt kind number: {value}"),
285            },
286            control_ids: opts
287                .control_ids
288                .into_iter()
289                .map(TryInto::try_into)
290                .collect::<Result<_>>()?,
291            max_segment_po2: opts
292                .max_segment_po2
293                .try_into()
294                .map_err(|_| malformed_err("ProverOpts.max_segment_po2"))?,
295        })
296    }
297}
298
299impl From<ProverOpts> for pb::api::ProverOpts {
300    fn from(opts: ProverOpts) -> Self {
301        Self {
302            hashfn: opts.hashfn,
303            prove_guest_errors: opts.prove_guest_errors,
304            receipt_kind: opts.receipt_kind as i32,
305            control_ids: opts.control_ids.into_iter().map(Into::into).collect(),
306            max_segment_po2: opts.max_segment_po2 as u64,
307        }
308    }
309}
310
311impl From<semver::Version> for pb::base::SemanticVersion {
312    fn from(value: semver::Version) -> Self {
313        Self {
314            major: value.major,
315            minor: value.minor,
316            patch: value.patch,
317            pre: value.pre.to_string(),
318            build: value.build.to_string(),
319        }
320    }
321}
322
323impl TryFrom<pb::base::SemanticVersion> for semver::Version {
324    type Error = semver::Error;
325
326    fn try_from(value: pb::base::SemanticVersion) -> Result<Self, Self::Error> {
327        Ok(Self {
328            major: value.major,
329            minor: value.minor,
330            patch: value.patch,
331            pre: semver::Prerelease::new(&value.pre)?,
332            build: semver::BuildMetadata::new(&value.build)?,
333        })
334    }
335}
336
337impl From<SessionStats> for pb::core::SessionStats {
338    fn from(value: SessionStats) -> Self {
339        Self {
340            // we use usize because that's the type returned by len() for vectors
341            segments: value.segments.try_into().unwrap(),
342            total_cycles: value.total_cycles,
343            user_cycles: value.user_cycles,
344            paging_cycles: value.paging_cycles,
345            reserved_cycles: value.reserved_cycles,
346        }
347    }
348}
349
350impl TryFrom<pb::core::SessionStats> for SessionStats {
351    type Error = anyhow::Error;
352
353    fn try_from(value: pb::core::SessionStats) -> Result<Self> {
354        Ok(Self {
355            segments: value.segments.try_into()?,
356            total_cycles: value.total_cycles,
357            user_cycles: value.user_cycles,
358            paging_cycles: value.paging_cycles,
359            reserved_cycles: value.reserved_cycles,
360        })
361    }
362}
363
364impl TryFrom<ProveInfo> for pb::core::ProveInfo {
365    type Error = anyhow::Error;
366
367    fn try_from(value: ProveInfo) -> Result<Self> {
368        Ok(Self {
369            receipt: Some(value.receipt.try_into()?),
370            stats: Some(value.stats.into()),
371        })
372    }
373}
374
375impl TryFrom<pb::core::ProveInfo> for ProveInfo {
376    type Error = anyhow::Error;
377
378    fn try_from(value: pb::core::ProveInfo) -> Result<Self> {
379        Ok(Self {
380            receipt: value
381                .receipt
382                .ok_or_else(|| malformed_err("ProveInfo.receipt"))?
383                .try_into()?,
384            stats: value
385                .stats
386                .ok_or_else(|| malformed_err("ProveInfo.stats"))?
387                .try_into()?,
388        })
389    }
390}
391
392impl TryFrom<Receipt> for pb::core::Receipt {
393    type Error = anyhow::Error;
394
395    fn try_from(value: Receipt) -> Result<Self> {
396        Ok(Self {
397            version: Some(ver::RECEIPT),
398            inner: Some(value.inner.try_into()?),
399            journal: value.journal.bytes,
400            metadata: Some(value.metadata.into()),
401        })
402    }
403}
404
405impl TryFrom<pb::core::Receipt> for Receipt {
406    type Error = anyhow::Error;
407
408    fn try_from(value: pb::core::Receipt) -> Result<Self> {
409        let version = value
410            .version
411            .ok_or_else(|| malformed_err("Receipt.version"))?
412            .value;
413        if version > ver::RECEIPT.value {
414            bail!("Incompatible Receipt version: {version}");
415        }
416        Ok(Self {
417            inner: value
418                .inner
419                .ok_or_else(|| malformed_err("Receipt.inner"))?
420                .try_into()?,
421            journal: Journal::new(value.journal),
422            metadata: value
423                .metadata
424                .ok_or_else(|| malformed_err("Receipt.metadata"))?
425                .try_into()?,
426        })
427    }
428}
429
430impl From<ReceiptMetadata> for pb::core::ReceiptMetadata {
431    fn from(value: ReceiptMetadata) -> Self {
432        Self {
433            verifier_parameters: Some(value.verifier_parameters.into()),
434        }
435    }
436}
437
438impl TryFrom<pb::core::ReceiptMetadata> for ReceiptMetadata {
439    type Error = anyhow::Error;
440
441    fn try_from(value: pb::core::ReceiptMetadata) -> Result<Self> {
442        Ok(Self {
443            verifier_parameters: value
444                .verifier_parameters
445                .ok_or_else(|| malformed_err("ReceiptMetadata.verifier_parameters"))?
446                .try_into()?,
447        })
448    }
449}
450
451impl TryFrom<SegmentReceipt> for pb::core::SegmentReceipt {
452    type Error = anyhow::Error;
453
454    fn try_from(value: SegmentReceipt) -> Result<Self> {
455        Ok(Self {
456            version: Some(ver::SEGMENT_RECEIPT),
457            seal: value.get_seal_bytes(),
458            index: value.index,
459            hashfn: value.hashfn,
460            claim: Some(value.claim.try_into()?),
461            verifier_parameters: Some(value.verifier_parameters.into()),
462        })
463    }
464}
465
466impl TryFrom<pb::core::SegmentReceipt> for SegmentReceipt {
467    type Error = anyhow::Error;
468
469    fn try_from(value: pb::core::SegmentReceipt) -> Result<Self> {
470        const WORD_SIZE: usize = std::mem::size_of::<u32>();
471
472        let mut seal = Vec::with_capacity(value.seal.len() / WORD_SIZE);
473        for chunk in value.seal.chunks_exact(WORD_SIZE) {
474            let word = u32::from_le_bytes(chunk.try_into()?);
475            seal.push(word);
476        }
477
478        let claim = value
479            .claim
480            .ok_or_else(|| malformed_err("SegmentReceipt.claim"))?
481            .try_into()?;
482
483        Ok(Self {
484            claim,
485            seal,
486            index: value.index,
487            hashfn: value.hashfn,
488            verifier_parameters: value
489                .verifier_parameters
490                .ok_or_else(|| malformed_err("SegmentReceipt.verifier_parameters"))?
491                .try_into()?,
492        })
493    }
494}
495
496impl<Claim> TryFrom<SuccinctReceipt<Claim>> for pb::core::SuccinctReceipt
497where
498    Claim: risc0_binfmt::Digestible + Debug + Clone + Serialize,
499    MaybePruned<Claim>: TryInto<pb::core::MaybePruned, Error = anyhow::Error>,
500{
501    type Error = anyhow::Error;
502
503    fn try_from(value: SuccinctReceipt<Claim>) -> Result<Self> {
504        Ok(Self {
505            version: Some(ver::SUCCINCT_RECEIPT),
506            seal: value.get_seal_bytes(),
507            control_id: Some(value.control_id.into()),
508            control_inclusion_proof: Some(value.control_inclusion_proof.into()),
509            claim: Some(value.claim.try_into()?),
510            hashfn: value.hashfn,
511            verifier_parameters: Some(value.verifier_parameters.into()),
512        })
513    }
514}
515
516impl<Claim> TryFrom<pb::core::SuccinctReceipt> for SuccinctReceipt<Claim>
517where
518    Claim: risc0_binfmt::Digestible + Debug + Clone + Serialize,
519    MaybePruned<Claim>: TryFrom<pb::core::MaybePruned, Error = anyhow::Error>,
520{
521    type Error = anyhow::Error;
522
523    fn try_from(value: pb::core::SuccinctReceipt) -> Result<Self> {
524        const WORD_SIZE: usize = std::mem::size_of::<u32>();
525
526        let version = value
527            .version
528            .ok_or_else(|| malformed_err("SuccinctReceipt.version"))?
529            .value;
530        if version > ver::SUCCINCT_RECEIPT.value {
531            bail!("Incompatible SuccinctReceipt version: {version}");
532        }
533
534        let mut seal = Vec::with_capacity(value.seal.len() / WORD_SIZE);
535        for chunk in value.seal.chunks_exact(WORD_SIZE) {
536            let word = u32::from_le_bytes(chunk.try_into()?);
537            seal.push(word);
538        }
539        Ok(Self {
540            seal,
541            control_id: value
542                .control_id
543                .ok_or_else(|| malformed_err("SuccinctReceipt.control_id"))?
544                .try_into()?,
545            control_inclusion_proof: value
546                .control_inclusion_proof
547                .ok_or_else(|| malformed_err("SuccinctReceipt.control_inclusion_proof"))?
548                .try_into()?,
549            claim: value
550                .claim
551                .ok_or_else(|| malformed_err("SuccinctReceipt.claim"))?
552                .try_into()?,
553            hashfn: value.hashfn,
554            verifier_parameters: value
555                .verifier_parameters
556                .ok_or_else(|| malformed_err("SuccinctReceipt.verifier_parameters"))?
557                .try_into()?,
558        })
559    }
560}
561
562impl From<MerkleProof> for pb::core::MerkleProof {
563    fn from(value: MerkleProof) -> Self {
564        Self {
565            index: value.index,
566            digests: value.digests.into_iter().map(Into::into).collect(),
567        }
568    }
569}
570
571impl TryFrom<pb::core::MerkleProof> for MerkleProof {
572    type Error = anyhow::Error;
573
574    fn try_from(value: pb::core::MerkleProof) -> Result<Self> {
575        Ok(Self {
576            index: value.index,
577            digests: value
578                .digests
579                .into_iter()
580                .map(TryInto::try_into)
581                .collect::<Result<_>>()?,
582        })
583    }
584}
585
586impl<Claim> TryFrom<Groth16Receipt<Claim>> for pb::core::Groth16Receipt
587where
588    Claim: risc0_binfmt::Digestible + Debug + Clone + Serialize,
589    MaybePruned<Claim>: TryInto<pb::core::MaybePruned, Error = anyhow::Error>,
590{
591    type Error = anyhow::Error;
592
593    fn try_from(value: Groth16Receipt<Claim>) -> Result<Self> {
594        Ok(Self {
595            version: Some(ver::GROTH16_RECEIPT),
596            seal: value.seal,
597            claim: Some(value.claim.try_into()?),
598            verifier_parameters: Some(value.verifier_parameters.into()),
599        })
600    }
601}
602
603impl<Claim> TryFrom<pb::core::Groth16Receipt> for Groth16Receipt<Claim>
604where
605    Claim: risc0_binfmt::Digestible + Debug + Clone + Serialize,
606    MaybePruned<Claim>: TryFrom<pb::core::MaybePruned, Error = anyhow::Error>,
607{
608    type Error = anyhow::Error;
609
610    fn try_from(value: pb::core::Groth16Receipt) -> Result<Self> {
611        let version = value
612            .version
613            .ok_or_else(|| malformed_err("Groth16Receipt.version"))?
614            .value;
615        if version > ver::GROTH16_RECEIPT.value {
616            bail!("Incompatible Groth16Receipt version: {version}");
617        }
618
619        Ok(Self {
620            seal: value.seal,
621            claim: value
622                .claim
623                .ok_or_else(|| malformed_err("Groth16Receipt.claim"))?
624                .try_into()?,
625            verifier_parameters: value
626                .verifier_parameters
627                .ok_or_else(|| malformed_err("Groth16Receipt.verifier_parameters"))?
628                .try_into()?,
629        })
630    }
631}
632
633impl TryFrom<InnerReceipt> for pb::core::InnerReceipt {
634    type Error = anyhow::Error;
635
636    fn try_from(value: InnerReceipt) -> Result<Self> {
637        Ok(Self {
638            kind: Some(match value {
639                InnerReceipt::Composite(inner) => {
640                    pb::core::inner_receipt::Kind::Composite(inner.try_into()?)
641                }
642                InnerReceipt::Succinct(inner) => {
643                    pb::core::inner_receipt::Kind::Succinct(inner.try_into()?)
644                }
645                InnerReceipt::Fake(inner) => {
646                    pb::core::inner_receipt::Kind::Fake(pb::core::FakeReceipt {
647                        claim: Some(inner.claim.try_into()?),
648                    })
649                }
650                InnerReceipt::Groth16(inner) => {
651                    pb::core::inner_receipt::Kind::Groth16(inner.try_into()?)
652                }
653            }),
654        })
655    }
656}
657
658impl TryFrom<pb::core::InnerReceipt> for InnerReceipt {
659    type Error = anyhow::Error;
660
661    fn try_from(value: pb::core::InnerReceipt) -> Result<Self> {
662        Ok(
663            match value
664                .kind
665                .ok_or_else(|| malformed_err("InnerReceipt.kind"))?
666            {
667                pb::core::inner_receipt::Kind::Composite(inner) => {
668                    Self::Composite(inner.try_into()?)
669                }
670                pb::core::inner_receipt::Kind::Groth16(inner) => Self::Groth16(inner.try_into()?),
671                pb::core::inner_receipt::Kind::Succinct(inner) => Self::Succinct(inner.try_into()?),
672                pb::core::inner_receipt::Kind::Fake(inner) => Self::Fake(inner.try_into()?),
673            },
674        )
675    }
676}
677
678// NOTE: InnerReceipt and InnerAssumptionReceipt are the same type in protobuf.
679// In Rust, they are distinct types because Rust needs to size everything on the
680// stack and e.g. SuccinctReceipt<ReceiptClaim> and SuccinctReceipt<Unknown>
681// have different sizes. Protobuf handles this without issue.
682impl TryFrom<InnerAssumptionReceipt> for pb::core::InnerReceipt {
683    type Error = anyhow::Error;
684
685    fn try_from(value: InnerAssumptionReceipt) -> Result<Self> {
686        Ok(Self {
687            kind: Some(match value {
688                InnerAssumptionReceipt::Composite(inner) => {
689                    pb::core::inner_receipt::Kind::Composite(inner.try_into()?)
690                }
691                InnerAssumptionReceipt::Succinct(inner) => {
692                    pb::core::inner_receipt::Kind::Succinct(inner.try_into()?)
693                }
694                InnerAssumptionReceipt::Fake(inner) => {
695                    pb::core::inner_receipt::Kind::Fake(pb::core::FakeReceipt {
696                        claim: Some(inner.claim.try_into()?),
697                    })
698                }
699                InnerAssumptionReceipt::Groth16(inner) => {
700                    pb::core::inner_receipt::Kind::Groth16(inner.try_into()?)
701                }
702            }),
703        })
704    }
705}
706
707impl TryFrom<pb::core::InnerReceipt> for InnerAssumptionReceipt {
708    type Error = anyhow::Error;
709
710    fn try_from(value: pb::core::InnerReceipt) -> Result<Self> {
711        Ok(
712            match value
713                .kind
714                .ok_or_else(|| malformed_err("InnerReceipt.kind"))?
715            {
716                pb::core::inner_receipt::Kind::Composite(inner) => {
717                    Self::Composite(inner.try_into()?)
718                }
719                pb::core::inner_receipt::Kind::Groth16(inner) => Self::Groth16(inner.try_into()?),
720                pb::core::inner_receipt::Kind::Succinct(inner) => Self::Succinct(inner.try_into()?),
721                pb::core::inner_receipt::Kind::Fake(inner) => Self::Fake(inner.try_into()?),
722            },
723        )
724    }
725}
726
727impl<Claim> TryFrom<FakeReceipt<Claim>> for pb::core::FakeReceipt
728where
729    Claim: risc0_binfmt::Digestible + Debug + Clone + Serialize,
730    MaybePruned<Claim>: TryInto<pb::core::MaybePruned, Error = anyhow::Error>,
731{
732    type Error = anyhow::Error;
733
734    fn try_from(value: FakeReceipt<Claim>) -> Result<Self> {
735        Ok(Self {
736            claim: Some(value.claim.try_into()?),
737        })
738    }
739}
740
741impl<Claim> TryFrom<pb::core::FakeReceipt> for FakeReceipt<Claim>
742where
743    Claim: risc0_binfmt::Digestible + Debug + Clone + Serialize,
744    MaybePruned<Claim>: TryFrom<pb::core::MaybePruned, Error = anyhow::Error>,
745{
746    type Error = anyhow::Error;
747
748    fn try_from(value: pb::core::FakeReceipt) -> Result<Self> {
749        Ok(Self {
750            claim: value
751                .claim
752                .ok_or_else(|| malformed_err("FakeReceipt.claim"))?
753                .try_into()?,
754        })
755    }
756}
757
758impl TryFrom<CompositeReceipt> for pb::core::CompositeReceipt {
759    type Error = anyhow::Error;
760
761    fn try_from(value: CompositeReceipt) -> Result<Self> {
762        Ok(Self {
763            segments: value
764                .segments
765                .into_iter()
766                .map(TryInto::try_into)
767                .collect::<Result<_>>()?,
768            assumption_receipts: value
769                .assumption_receipts
770                .into_iter()
771                .map(TryInto::try_into)
772                .collect::<Result<_>>()?,
773            verifier_parameters: Some(value.verifier_parameters.into()),
774        })
775    }
776}
777
778impl TryFrom<pb::core::CompositeReceipt> for CompositeReceipt {
779    type Error = anyhow::Error;
780
781    fn try_from(value: pb::core::CompositeReceipt) -> Result<Self> {
782        Ok(Self {
783            segments: value
784                .segments
785                .into_iter()
786                .map(|s| s.try_into())
787                .collect::<Result<Vec<_>>>()?,
788            assumption_receipts: value
789                .assumption_receipts
790                .into_iter()
791                .map(|a| a.try_into())
792                .collect::<Result<Vec<_>>>()?,
793            verifier_parameters: value
794                .verifier_parameters
795                .ok_or_else(|| malformed_err("CompositeReceipt.verifier_parameters"))?
796                .try_into()?,
797        })
798    }
799}
800
801impl From<Digest> for pb::base::Digest {
802    fn from(value: Digest) -> Self {
803        Self {
804            words: value.as_words().to_vec(),
805        }
806    }
807}
808
809impl TryFrom<pb::base::Digest> for Digest {
810    type Error = anyhow::Error;
811
812    fn try_from(value: pb::base::Digest) -> Result<Self> {
813        value
814            .words
815            .try_into()
816            .map_err(|_| anyhow!("invalid digest"))
817    }
818}
819
820impl Name for pb::core::ReceiptClaim {
821    const PACKAGE: &'static str = "risc0.protos.core";
822    const NAME: &'static str = "ReceiptClaim";
823}
824
825impl AssociatedMessage for ReceiptClaim {
826    type Message = pb::core::ReceiptClaim;
827}
828
829impl AssociatedMessage for UnionClaim {
830    type Message = pb::core::UnionClaim;
831}
832
833impl TryFrom<UnionClaim> for pb::core::UnionClaim {
834    type Error = anyhow::Error;
835
836    fn try_from(value: UnionClaim) -> Result<Self> {
837        Ok(Self {
838            left: Some(value.left.into()),
839            right: Some(value.right.into()),
840        })
841    }
842}
843
844impl TryFrom<pb::core::UnionClaim> for UnionClaim {
845    type Error = anyhow::Error;
846
847    fn try_from(value: pb::core::UnionClaim) -> Result<Self> {
848        Ok(Self {
849            left: value
850                .left
851                .ok_or_else(|| malformed_err("UnionClaim.left"))?
852                .try_into()?,
853            right: value
854                .right
855                .ok_or_else(|| malformed_err("UnionClaim.right"))?
856                .try_into()?,
857        })
858    }
859}
860
861impl TryFrom<ReceiptClaim> for pb::core::ReceiptClaim {
862    type Error = anyhow::Error;
863
864    fn try_from(value: ReceiptClaim) -> Result<Self> {
865        Ok(Self {
866            pre: Some(value.pre.try_into()?),
867            post: Some(value.post.try_into()?),
868            exit_code: Some(value.exit_code.try_into()?),
869            // Translate MaybePruned<Option<Input>>> to Option<MaybePruned<Input>>.
870            input: match value.input {
871                MaybePruned::Value(optional) => optional
872                    .map(|input| MaybePruned::Value(input).try_into())
873                    .transpose()?,
874                MaybePruned::Pruned(digest) => {
875                    Some(MaybePruned::<Input>::Pruned(digest).try_into()?)
876                }
877            },
878            // Translate MaybePruned<Option<Output>>> to Option<MaybePruned<Output>>.
879            output: match value.output {
880                MaybePruned::Value(optional) => optional
881                    .map(|output| MaybePruned::Value(output).try_into())
882                    .transpose()?,
883                MaybePruned::Pruned(digest) => {
884                    Some(MaybePruned::<Output>::Pruned(digest).try_into()?)
885                }
886            },
887        })
888    }
889}
890
891impl TryFrom<pb::core::ReceiptClaim> for ReceiptClaim {
892    type Error = anyhow::Error;
893
894    fn try_from(value: pb::core::ReceiptClaim) -> Result<Self> {
895        Ok(Self {
896            pre: value
897                .pre
898                .ok_or_else(|| malformed_err("ReceiptClaim.pre"))?
899                .try_into()?,
900            post: value
901                .post
902                .ok_or_else(|| malformed_err("ReceiptClaim.post"))?
903                .try_into()?,
904            exit_code: value
905                .exit_code
906                .ok_or_else(|| malformed_err("ReceiptClaim.exit_code"))?
907                .try_into()?,
908            // Translate Option<MaybePruned<Input>> to MaybePruned<Option<Input>>.
909            input: match value.input {
910                None => MaybePruned::Value(None),
911                Some(x) => match MaybePruned::<Input>::try_from(x)? {
912                    #[allow(unreachable_patterns)]
913                    MaybePruned::Value(input) => MaybePruned::Value(Some(input)),
914                    MaybePruned::Pruned(digest) => MaybePruned::Pruned(digest),
915                },
916            },
917            // Translate Option<MaybePruned<Output>> to MaybePruned<Option<Output>>.
918            output: match value.output {
919                None => MaybePruned::Value(None),
920                Some(x) => match MaybePruned::<Output>::try_from(x)? {
921                    MaybePruned::Value(output) => MaybePruned::Value(Some(output)),
922                    MaybePruned::Pruned(digest) => MaybePruned::Pruned(digest),
923                },
924            },
925        })
926    }
927}
928
929impl Name for pb::core::SystemState {
930    const PACKAGE: &'static str = "risc0.protos.core";
931    const NAME: &'static str = "SystemState";
932}
933
934impl AssociatedMessage for SystemState {
935    type Message = pb::core::SystemState;
936}
937
938impl TryFrom<SystemState> for pb::core::SystemState {
939    type Error = anyhow::Error;
940
941    fn try_from(value: SystemState) -> Result<Self> {
942        Ok(Self {
943            pc: value.pc,
944            merkle_root: Some(value.merkle_root.into()),
945        })
946    }
947}
948
949impl TryFrom<pb::core::SystemState> for SystemState {
950    type Error = anyhow::Error;
951
952    fn try_from(value: pb::core::SystemState) -> Result<Self> {
953        Ok(Self {
954            pc: value.pc,
955            merkle_root: value
956                .merkle_root
957                .ok_or_else(|| malformed_err("SystemState.merkle_root"))?
958                .try_into()?,
959        })
960    }
961}
962
963impl Name for pb::core::Input {
964    const PACKAGE: &'static str = "risc0.protos.core";
965    const NAME: &'static str = "Input";
966}
967
968impl AssociatedMessage for Input {
969    type Message = pb::core::Input;
970}
971
972impl TryFrom<Input> for pb::core::Input {
973    type Error = anyhow::Error;
974
975    fn try_from(value: Input) -> Result<Self> {
976        match value.x { /* unreachable  */ }
977    }
978}
979
980impl TryFrom<pb::core::Input> for Input {
981    type Error = anyhow::Error;
982
983    fn try_from(_value: pb::core::Input) -> Result<Self> {
984        Err(malformed_err("Input"))
985    }
986}
987
988impl Name for pb::core::Output {
989    const PACKAGE: &'static str = "risc0.protos.core";
990    const NAME: &'static str = "Output";
991}
992
993impl AssociatedMessage for Output {
994    type Message = pb::core::Output;
995}
996
997impl TryFrom<Output> for pb::core::Output {
998    type Error = anyhow::Error;
999
1000    fn try_from(value: Output) -> Result<Self> {
1001        Ok(Self {
1002            journal: Some(value.journal.try_into()?),
1003            assumptions: Some(value.assumptions.try_into()?),
1004        })
1005    }
1006}
1007
1008impl TryFrom<pb::core::Output> for Output {
1009    type Error = anyhow::Error;
1010
1011    fn try_from(value: pb::core::Output) -> Result<Self> {
1012        Ok(Self {
1013            journal: value
1014                .journal
1015                .ok_or_else(|| malformed_err("Output.journal"))?
1016                .try_into()?,
1017            assumptions: value
1018                .assumptions
1019                .ok_or_else(|| malformed_err("Output.assumptions"))?
1020                .try_into()?,
1021        })
1022    }
1023}
1024
1025impl Name for pb::core::Assumption {
1026    const PACKAGE: &'static str = "risc0.protos.core";
1027    const NAME: &'static str = "Assumption";
1028}
1029
1030impl AssociatedMessage for Assumption {
1031    type Message = pb::core::Assumption;
1032}
1033
1034impl TryFrom<Assumption> for pb::core::Assumption {
1035    type Error = anyhow::Error;
1036
1037    fn try_from(value: Assumption) -> Result<Self> {
1038        Ok(Self {
1039            claim: Some(value.claim.into()),
1040            control_root: Some(value.control_root.into()),
1041        })
1042    }
1043}
1044
1045impl TryFrom<pb::core::Assumption> for Assumption {
1046    type Error = anyhow::Error;
1047
1048    fn try_from(value: pb::core::Assumption) -> Result<Self> {
1049        Ok(Self {
1050            claim: value
1051                .claim
1052                .ok_or_else(|| malformed_err("Assumption.claim"))?
1053                .try_into()?,
1054            control_root: value
1055                .control_root
1056                .ok_or_else(|| malformed_err("Assumption.control_root"))?
1057                .try_into()?,
1058        })
1059    }
1060}
1061
1062impl Name for pb::core::Assumptions {
1063    const PACKAGE: &'static str = "risc0.protos.core";
1064    const NAME: &'static str = "Assumptions";
1065}
1066
1067impl AssociatedMessage for Assumptions {
1068    type Message = pb::core::Assumptions;
1069}
1070
1071impl TryFrom<Assumptions> for pb::core::Assumptions {
1072    type Error = anyhow::Error;
1073
1074    fn try_from(value: Assumptions) -> Result<Self> {
1075        Ok(Self {
1076            inner: value
1077                .0
1078                .into_iter()
1079                .map(|a| a.try_into())
1080                .collect::<Result<_>>()?,
1081        })
1082    }
1083}
1084
1085impl TryFrom<pb::core::Assumptions> for Assumptions {
1086    type Error = anyhow::Error;
1087
1088    fn try_from(value: pb::core::Assumptions) -> Result<Self> {
1089        Ok(Self(
1090            value
1091                .inner
1092                .into_iter()
1093                .map(|a| a.try_into())
1094                .collect::<Result<Vec<_>>>()?,
1095        ))
1096    }
1097}
1098
1099trait AssociatedMessage {
1100    type Message: Message;
1101}
1102
1103impl<T> TryFrom<MaybePruned<T>> for pb::core::MaybePruned
1104where
1105    T: AssociatedMessage + serde::Serialize + Clone,
1106    T::Message: TryFrom<T, Error = anyhow::Error> + Sized,
1107{
1108    type Error = anyhow::Error;
1109
1110    fn try_from(value: MaybePruned<T>) -> Result<Self> {
1111        Ok(Self {
1112            kind: Some(match value {
1113                MaybePruned::Value(inner) => pb::core::maybe_pruned::Kind::Value(
1114                    T::Message::try_from(inner)?.encode_to_vec(),
1115                ),
1116                MaybePruned::Pruned(digest) => pb::core::maybe_pruned::Kind::Pruned(digest.into()),
1117            }),
1118        })
1119    }
1120}
1121
1122impl<T> TryFrom<pb::core::MaybePruned> for MaybePruned<T>
1123where
1124    T: AssociatedMessage + serde::Serialize + Clone,
1125    T::Message: TryInto<T, Error = anyhow::Error> + Default,
1126{
1127    type Error = anyhow::Error;
1128
1129    fn try_from(value: pb::core::MaybePruned) -> Result<Self> {
1130        Ok(
1131            match value
1132                .kind
1133                .ok_or_else(|| malformed_err("MaybePruned<T>.kind"))?
1134            {
1135                pb::core::maybe_pruned::Kind::Value(inner) => {
1136                    Self::Value(T::Message::decode(inner.as_slice())?.try_into()?)
1137                }
1138                pb::core::maybe_pruned::Kind::Pruned(digest) => Self::Pruned(digest.try_into()?),
1139            },
1140        )
1141    }
1142}
1143
1144// Specialized implementation for Vec<u8> for work around challenges getting the
1145// generic implementation above to work for Vec<u8>.
1146impl TryFrom<MaybePruned<Vec<u8>>> for pb::core::MaybePruned {
1147    type Error = anyhow::Error;
1148
1149    fn try_from(value: MaybePruned<Vec<u8>>) -> Result<Self> {
1150        Ok(Self {
1151            kind: Some(match value {
1152                MaybePruned::Value(inner) => {
1153                    pb::core::maybe_pruned::Kind::Value(inner.encode_to_vec())
1154                }
1155                MaybePruned::Pruned(digest) => pb::core::maybe_pruned::Kind::Pruned(digest.into()),
1156            }),
1157        })
1158    }
1159}
1160
1161impl TryFrom<pb::core::MaybePruned> for MaybePruned<Vec<u8>> {
1162    type Error = anyhow::Error;
1163
1164    fn try_from(value: pb::core::MaybePruned) -> Result<Self> {
1165        Ok(
1166            match value
1167                .kind
1168                .ok_or_else(|| malformed_err("MaybePruned<Vec<u8>>.kind"))?
1169            {
1170                pb::core::maybe_pruned::Kind::Value(inner) => {
1171                    Self::Value(<Vec<u8> as Message>::decode(inner.as_slice())?)
1172                }
1173                pb::core::maybe_pruned::Kind::Pruned(digest) => Self::Pruned(digest.try_into()?),
1174            },
1175        )
1176    }
1177}
1178
1179impl TryFrom<MaybePruned<Unknown>> for pb::core::MaybePruned {
1180    type Error = anyhow::Error;
1181
1182    fn try_from(value: MaybePruned<Unknown>) -> Result<Self> {
1183        Ok(Self {
1184            kind: Some(match value {
1185                #[allow(unreachable_patterns)]
1186                MaybePruned::Value(inner) => {
1187                    match inner { /* unreachable */ }
1188                }
1189                MaybePruned::Pruned(digest) => pb::core::maybe_pruned::Kind::Pruned(digest.into()),
1190            }),
1191        })
1192    }
1193}
1194
1195impl TryFrom<pb::core::MaybePruned> for MaybePruned<Unknown> {
1196    type Error = anyhow::Error;
1197
1198    fn try_from(value: pb::core::MaybePruned) -> Result<Self> {
1199        Ok(
1200            match value
1201                .kind
1202                .ok_or_else(|| malformed_err("MaybePruned<Unknown>.kind"))?
1203            {
1204                pb::core::maybe_pruned::Kind::Value(_) => {
1205                    Err(malformed_err("MaybePruned<Unknown>.value"))?
1206                }
1207                pb::core::maybe_pruned::Kind::Pruned(digest) => Self::Pruned(digest.try_into()?),
1208            },
1209        )
1210    }
1211}
1212
1213impl TryFrom<pb::api::ProveZkrRequest> for ProveZkrRequest {
1214    type Error = anyhow::Error;
1215
1216    fn try_from(value: pb::api::ProveZkrRequest) -> Result<Self> {
1217        Ok(Self {
1218            claim_digest: value
1219                .claim_digest
1220                .ok_or_else(|| malformed_err("ProveZkrRequest.claim_digest"))?
1221                .try_into()?,
1222            control_id: value
1223                .control_id
1224                .ok_or_else(|| malformed_err("ProveZkrRequest.control_id"))?
1225                .try_into()?,
1226            input: value.input,
1227        })
1228    }
1229}
1230
1231impl TryFrom<pb::api::ProveKeccakRequest> for ProveKeccakRequest {
1232    type Error = anyhow::Error;
1233
1234    fn try_from(value: pb::api::ProveKeccakRequest) -> Result<Self> {
1235        let input = try_keccak_bytes_to_input(&value.input)?;
1236
1237        Ok(Self {
1238            claim_digest: value
1239                .claim_digest
1240                .ok_or_else(|| malformed_err("ProveKeccakRequest.claim_digest"))?
1241                .try_into()?,
1242            po2: value.po2 as usize,
1243            control_root: value
1244                .control_root
1245                .ok_or_else(|| malformed_err("ProveKeccakRequest.control_root"))?
1246                .try_into()?,
1247            input,
1248        })
1249    }
1250}
1251
1252pub(crate) fn keccak_input_to_bytes(input: &[KeccakState]) -> Vec<u8> {
1253    bytemuck::cast_slice(input).to_vec()
1254}
1255
1256pub(crate) fn try_keccak_bytes_to_input(input: &[u8]) -> Result<Vec<KeccakState>> {
1257    let chunks = input.chunks_exact(std::mem::size_of::<KeccakState>());
1258    if !chunks.remainder().is_empty() {
1259        bail!("Input length must be a multiple of KeccakState size");
1260    }
1261    chunks
1262        .map(bytemuck::try_pod_read_unaligned)
1263        .collect::<Result<_, _>>()
1264        .map_err(|e| anyhow!("Failed to convert input bytes to KeccakState: {}", e))
1265}
1266
1267#[test]
1268fn test_keccak_bytes_to_input_alignment() {
1269    // Create a buffer with extra padding at the start to test different alignments
1270    let padding = 16; // We should hit all alignments by cycling through offsets 0-15
1271    let keccak_states = 3; // Test multiple KeccakStates
1272    let state_size = std::mem::size_of::<KeccakState>();
1273    let mut test_buffer = vec![0u8; padding + (keccak_states * state_size)];
1274
1275    // Fill with recognizable pattern
1276    for (i, byte) in test_buffer.iter_mut().enumerate() {
1277        *byte = (i % 256) as u8;
1278    }
1279
1280    // Test each offset
1281    for offset in 0..padding {
1282        let aligned_slice = &test_buffer[offset..][..(keccak_states * state_size)];
1283        let result = try_keccak_bytes_to_input(aligned_slice);
1284
1285        assert!(result.is_ok(), "Failed to parse at offset {offset}");
1286        let states = result.unwrap();
1287        assert_eq!(
1288            states.len(),
1289            keccak_states,
1290            "Wrong number of states at offset {offset}",
1291        );
1292
1293        // Verify roundtrip
1294        let bytes = keccak_input_to_bytes(&states);
1295        assert_eq!(bytes, aligned_slice, "Roundtrip failed at offset {offset}",);
1296    }
1297}
1298
1299#[test]
1300fn test_keccak_bytes_to_input_invalid_size() {
1301    // Test with a buffer that's not a multiple of KeccakState size
1302    let invalid_size = std::mem::size_of::<KeccakState>() + 1;
1303    let buffer = vec![0u8; invalid_size];
1304    let result = try_keccak_bytes_to_input(&buffer);
1305    assert!(result.is_err(), "Should fail with invalid size");
1306}