Skip to main content

boundless_market/
input.rs

1// Copyright 2026 Boundless Foundation, 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 bytemuck::Pod;
16use risc0_zkvm::serde::to_vec;
17use risc0_zkvm::ExecutorEnv;
18use rmp_serde;
19use serde::{Deserialize, Serialize};
20
21use crate::contracts::RequestInput;
22
23// Input version.
24#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
25#[repr(u8)]
26#[non_exhaustive]
27enum Version {
28    // Raw version with no encoding.
29    V0 = 0,
30    // MessagePack encoded version based on [InputV1].
31    #[default]
32    V1 = 1,
33}
34
35impl From<Version> for u8 {
36    fn from(v: Version) -> Self {
37        v as u8
38    }
39}
40
41impl TryFrom<u8> for Version {
42    type Error = Error;
43
44    fn try_from(v: u8) -> Result<Version, Self::Error> {
45        match v {
46            v if v == Version::V0 as u8 => Ok(Version::V0),
47            v if v == Version::V1 as u8 => Ok(Version::V1),
48            _ => Err(Error::UnsupportedVersion(v as u64)),
49        }
50    }
51}
52
53#[derive(Debug, thiserror::Error)]
54#[non_exhaustive]
55/// Input error.
56pub enum Error {
57    /// MessagePack serde encoding error
58    #[error("MessagePack serde encoding error: {0}")]
59    MessagePackSerdeEncode(#[from] rmp_serde::encode::Error),
60    /// MessagePack serde decoding error
61    #[error("MessagePack serde decoding error: {0}")]
62    MessagePackSerdeDecode(#[from] rmp_serde::decode::Error),
63    /// risc0-zkvm Serde error
64    #[error("risc0-zkvm Serde error: {0}")]
65    ZkvmSerde(#[from] risc0_zkvm::serde::Error),
66    /// Unsupported version
67    #[error("Unsupported version: {0}")]
68    UnsupportedVersion(u64),
69    /// Encoded input buffer is empty, which is an invalid encoding.
70    #[error("Cannot decode empty buffer as input")]
71    EmptyEncodedInput,
72}
73
74/// Structured input used by the Boundless prover to execute the guest for the proof request.
75///
76/// This struct is related to the [ExecutorEnv] in that both represent the environments provided to
77/// the guest by the host that is executing and proving the execution. In contrast to the
78/// [ExecutorEnv] provided by [risc0_zkvm], this struct contains only the options that are
79/// supported by Boundless.
80#[derive(Clone, Default, Debug, Serialize, Deserialize)]
81#[cfg_attr(test, derive(PartialEq, Eq))]
82#[non_exhaustive]
83pub struct GuestEnv {
84    /// Input data to be provided to the guest as stdin.
85    ///
86    /// The data here will be provided to the guest without further encoding (e.g. the bytes will
87    /// be provided directly). When the guest calls `env::read_slice` these are the bytes that will
88    /// be read. If the guest uses `env::read`, this should be encoded using the default RISC Zero
89    /// codec. [GuestEnvBuilder::write] will encode the data given using the default codec.
90    pub stdin: Vec<u8>,
91}
92
93impl GuestEnv {
94    /// Create a new [GuestEnvBuilder]
95    pub fn builder() -> GuestEnvBuilder {
96        Default::default()
97    }
98
99    /// Parse an encoded [GuestEnv] with version support.
100    pub fn decode(bytes: &[u8]) -> Result<Self, Error> {
101        if bytes.is_empty() {
102            return Err(Error::EmptyEncodedInput);
103        }
104        match Version::try_from(bytes[0])? {
105            Version::V0 => Ok(Self { stdin: bytes[1..].to_vec() }),
106            Version::V1 => Ok(rmp_serde::from_read(&bytes[1..])?),
107        }
108    }
109
110    /// Encode the [GuestEnv] for inclusion in a proof request.
111    pub fn encode(&self) -> Result<Vec<u8>, Error> {
112        let mut encoded = Vec::<u8>::new();
113        // Push the version as the first byte to indicate the message version.
114        encoded.push(Version::V1.into());
115        encoded.extend_from_slice(&rmp_serde::to_vec_named(&self)?);
116        Ok(encoded)
117    }
118
119    /// Create a [GuestEnv] with `stdin` set to the contents of the given `bytes`.
120    pub fn from_stdin(bytes: impl Into<Vec<u8>>) -> Self {
121        GuestEnv { stdin: bytes.into() }
122    }
123}
124
125impl TryFrom<GuestEnv> for ExecutorEnv<'_> {
126    type Error = anyhow::Error;
127
128    /// Create an [ExecutorEnv], which can be used for execution and proving through the
129    /// [risc0_zkvm] [Prover][risc0_zkvm::Prover] and [Executor][risc0_zkvm::Executor] traits, from
130    /// the given [GuestEnv].
131    fn try_from(env: GuestEnv) -> Result<Self, Self::Error> {
132        ExecutorEnv::builder().write_slice(&env.stdin).build()
133    }
134}
135
136impl From<GuestEnvBuilder> for GuestEnv {
137    fn from(builder: GuestEnvBuilder) -> Self {
138        builder.build_env()
139    }
140}
141
142/// Input builder, used to build the structured input (i.e. env) for execution and proving.
143///
144/// Boundless provers decode the input provided in a proving request as a [GuestEnv]. This
145/// [GuestEnvBuilder] provides methods for constructing and encoding the guest environment.
146#[derive(Clone, Default, Debug)]
147#[non_exhaustive]
148pub struct GuestEnvBuilder {
149    /// Input data to be provided to the guest as stdin.
150    ///
151    /// See [GuestEnv::stdin]
152    pub stdin: Vec<u8>,
153}
154
155impl GuestEnvBuilder {
156    /// Create a new input builder.
157    pub fn new() -> Self {
158        Self { stdin: Vec::new() }
159    }
160
161    /// Build the [GuestEnv] for inclusion in a proof request.
162    pub fn build_env(self) -> GuestEnv {
163        GuestEnv { stdin: self.stdin }
164    }
165
166    /// Build the and encode [GuestEnv] for inclusion in a proof request.
167    pub fn build_vec(self) -> Result<Vec<u8>, Error> {
168        self.build_env().encode()
169    }
170
171    /// Build and encode the [GuestEnv] into an inline [RequestInput] for inclusion in a proof request.
172    pub fn build_inline(self) -> Result<RequestInput, Error> {
173        Ok(RequestInput::inline(self.build_env().encode()?))
174    }
175
176    /// Write input data.
177    ///
178    /// This function will serialize `data` using the RISC Zero default codec that
179    /// can be deserialized in the guest with a corresponding `risc0_zkvm::env::read` with
180    /// the same data type.
181    ///
182    /// # Example
183    ///
184    /// ```
185    /// use boundless_market::GuestEnv;
186    /// use serde::Serialize;
187    ///
188    /// #[derive(Serialize)]
189    /// struct Input {
190    ///     a: u32,
191    ///     b: u32,
192    /// }
193    ///
194    /// let input1 = Input{ a: 1, b: 2 };
195    /// let input2 = Input{ a: 3, b: 4 };
196    /// let input = GuestEnv::builder()
197    ///     .write(&input1).unwrap()
198    ///     .write(&input2).unwrap();
199    /// ```
200    pub fn write<T: Serialize>(self, data: &T) -> Result<Self, Error> {
201        Ok(self.write_slice(&to_vec(data)?))
202    }
203
204    /// Write input data.
205    ///
206    /// This function writes a slice directly to the underlying buffer. A
207    /// corresponding `risc0_zkvm::env::read_slice` can be used within
208    /// the guest to read the data.
209    ///
210    /// # Example
211    ///
212    /// ```
213    /// use boundless_market::GuestEnv;
214    ///
215    /// let slice1 = [0, 1, 2, 3];
216    /// let slice2 = [3, 2, 1, 0];
217    /// let input = GuestEnv::builder()
218    ///     .write_slice(&slice1)
219    ///     .write_slice(&slice2);
220    /// ```
221    pub fn write_slice<T: Pod>(self, slice: &[T]) -> Self {
222        let mut input = self.stdin;
223        input.extend_from_slice(bytemuck::cast_slice(slice));
224        Self { stdin: input, ..self }
225    }
226
227    /// Write a frame.
228    ///
229    /// A frame contains a length header along with the payload. Reading a frame can be more
230    /// efficient than streaming deserialization of a message. Streaming deserialization
231    /// deserialization can cause many syscalls, whereas a frame will only have two.
232    pub fn write_frame(self, payload: &[u8]) -> Self {
233        let len = payload.len() as u32;
234        let mut input = self.stdin;
235        input.extend_from_slice(&len.to_le_bytes());
236        input.extend_from_slice(payload);
237        Self { stdin: input, ..self }
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    #[test]
246    fn test_version_parsing() -> Result<(), Error> {
247        // Test V1
248        let v1 = GuestEnv::builder().write_slice(&[1u8, 2, 3]);
249        let bytes = v1.build_vec()?;
250        let parsed = GuestEnv::decode(&bytes)?;
251        assert_eq!(parsed.stdin, vec![1, 2, 3]);
252
253        // Test V0
254        let bytes = vec![0u8, 1, 2, 3];
255        let parsed = GuestEnv::decode(&bytes)?;
256        assert_eq!(parsed.stdin, vec![1, 2, 3]);
257
258        // Test unsupported version
259        let bytes = vec![2u8, 1, 2, 3];
260        let parsed = GuestEnv::decode(&bytes);
261        assert!(parsed.is_err());
262
263        Ok(())
264    }
265
266    #[test]
267    fn test_encode_decode_env() -> Result<(), Error> {
268        let timestamp = format! {"{:?}", std::time::SystemTime::now()};
269        let env = GuestEnv::builder().write_slice(timestamp.as_bytes()).build_env();
270
271        let decoded_env = GuestEnv::decode(&env.encode()?)?;
272        assert_eq!(env, decoded_env);
273        Ok(())
274    }
275}