cargo_openvm/
input.rs

1use std::{fs::read, path::PathBuf, str::FromStr};
2
3use eyre::Result;
4use openvm_sdk::{StdIn, F};
5use openvm_stark_backend::p3_field::FieldAlgebra;
6
7/// Input can be either:
8/// (1) one single hex string
9/// (2) A JSON file containing an array of hex strings.
10/// Each hex string (either in the file or the direct input) is either:
11/// - Hex strings of bytes, which is prefixed with 0x01
12/// - Hex strings of native field elements (represented as u32, little endian), prefixed with 0x02
13#[derive(Debug, Clone)]
14pub enum Input {
15    FilePath(PathBuf),
16    HexBytes(String),
17}
18
19impl FromStr for Input {
20    type Err = String;
21
22    fn from_str(s: &str) -> Result<Self, Self::Err> {
23        if is_valid_hex_string(s) {
24            Ok(Input::HexBytes(s.to_string()))
25        } else if PathBuf::from(s).exists() {
26            Ok(Input::FilePath(PathBuf::from(s)))
27        } else {
28            Err("Input must be a valid file path or hex string.".to_string())
29        }
30    }
31}
32
33pub fn is_valid_hex_string(s: &str) -> bool {
34    if s.len() % 2 != 0 {
35        return false;
36    }
37    // All hex digits with optional 0x prefix
38    s.starts_with("0x") && s[2..].chars().all(|c| c.is_ascii_hexdigit())
39        || s.chars().all(|c| c.is_ascii_hexdigit())
40}
41
42pub fn decode_hex_string(s: &str) -> Result<Vec<u8>> {
43    // Remove 0x prefix if present
44    let s = s.trim_start_matches("0x");
45    if s.is_empty() {
46        return Ok(Vec::new());
47    }
48    hex::decode(s).map_err(|e| eyre::eyre!("Invalid hex: {}", e))
49}
50
51pub fn read_bytes_into_stdin(stdin: &mut StdIn, bytes: &[u8]) -> Result<()> {
52    // should either write_bytes or write_field
53    match bytes[0] {
54        0x01 => {
55            stdin.write_bytes(&bytes[1..]);
56            Ok(())
57        }
58        0x02 => {
59            let data = &bytes[1..];
60            if data.len() % 4 != 0 {
61                return Err(eyre::eyre!(
62                    "Invalid input format: incorrect number of bytes"
63                ));
64            }
65            let mut fields = Vec::with_capacity(data.len() / 4);
66            for chunk in data.chunks_exact(4) {
67                let value = u32::from_le_bytes(chunk.try_into().unwrap());
68                fields.push(F::from_canonical_u32(value));
69            }
70            stdin.write_field(&fields);
71            Ok(())
72        }
73        _ => Err(eyre::eyre!(
74            "Invalid input format: the first byte must be 0x01 or 0x02"
75        )),
76    }
77}
78
79pub fn read_to_stdin(input: &Option<Input>) -> Result<StdIn> {
80    match input {
81        Some(Input::FilePath(path)) => {
82            let mut stdin = StdIn::default();
83            // read the json
84            let bytes = read(path)?;
85            let json: serde_json::Value = serde_json::from_slice(&bytes)?;
86            json["input"]
87                .as_array()
88                .ok_or_else(|| eyre::eyre!("Input must be an array under 'input' key"))?
89                .iter()
90                .try_for_each(|inner| {
91                    inner
92                        .as_str()
93                        .ok_or_else(|| eyre::eyre!("Each value must be a hex string"))
94                        .and_then(|s| {
95                            if !is_valid_hex_string(s) {
96                                return Err(eyre::eyre!("Invalid hex string"));
97                            }
98                            let bytes = decode_hex_string(s)?;
99                            read_bytes_into_stdin(&mut stdin, &bytes)
100                        })
101                })?;
102
103            Ok(stdin)
104        }
105        Some(Input::HexBytes(hex_str)) => {
106            let mut stdin = StdIn::default();
107            let bytes = decode_hex_string(hex_str)?;
108            read_bytes_into_stdin(&mut stdin, &bytes)?;
109            Ok(stdin)
110        }
111        None => Ok(StdIn::default()),
112    }
113}