export_test_vectors/
main.rs

1use clap::{Parser, ValueEnum};
2use core::str::FromStr;
3use std::{
4    fs::File,
5    io::{self, Write},
6};
7mod vectors;
8
9/// Parse a hex string into a 32-byte seed
10fn parse_seed(seed_str: &str) -> Result<[u8; 32], String> {
11    if seed_str.len() != 64 {
12        return Err(format!(
13            "Seed must be exactly 64 hex characters (32 bytes), got {}",
14            seed_str.len()
15        ));
16    }
17
18    let mut seed = [0u8; 32];
19    for (i, chunk) in seed_str.as_bytes().chunks(2).enumerate() {
20        let hex_str =
21            std::str::from_utf8(chunk).map_err(|_| "Invalid UTF-8 in seed".to_string())?;
22        seed[i] = u8::from_str_radix(hex_str, 16)
23            .map_err(|_| format!("Invalid hex character in seed: {}", hex_str))?;
24    }
25
26    Ok(seed)
27}
28
29#[derive(Debug, Clone, ValueEnum)]
30pub enum Mode {
31    B10,
32    Hex,
33}
34
35impl FromStr for Mode {
36    type Err = ();
37
38    fn from_str(input: &str) -> Result<Self, Self::Err> {
39        match input.to_lowercase().as_str() {
40            "b10" => Ok(Mode::B10),
41            "hex" => Ok(Mode::Hex),
42            _ => Err(()),
43        }
44    }
45}
46
47#[derive(Debug, Clone, ValueEnum)]
48pub enum OutputFormat {
49    Es5,
50    Json,
51}
52
53impl FromStr for OutputFormat {
54    type Err = ();
55
56    fn from_str(input: &str) -> Result<Self, Self::Err> {
57        match input.to_lowercase().as_str() {
58            "es5" => Ok(OutputFormat::Es5),
59            "json" => Ok(OutputFormat::Json),
60            _ => Err(()),
61        }
62    }
63}
64
65#[derive(Debug, Clone, ValueEnum)]
66pub enum ParamType {
67    Legacy,
68    Kimchi,
69}
70
71impl FromStr for ParamType {
72    type Err = ();
73
74    fn from_str(input: &str) -> Result<Self, Self::Err> {
75        match input.to_lowercase().as_str() {
76            "legacy" => Ok(ParamType::Legacy),
77            "kimchi" => Ok(ParamType::Kimchi),
78            _ => Err(()),
79        }
80    }
81}
82
83#[derive(Parser)]
84#[command(name = "export_test_vectors")]
85#[command(about = "Export test vectors for the mina-poseidon crate")]
86struct Args {
87    /// Number encoding format (base-10 or hexadecimal)
88    #[arg(value_enum)]
89    mode: Mode,
90
91    /// Parameter type to use
92    #[arg(value_enum)]
93    param_type: ParamType,
94
95    /// Output file path, use "-" for stdout
96    output_file: String,
97
98    /// Output file format
99    #[arg(value_enum, default_value = "json", short, long)]
100    format: OutputFormat,
101
102    /// Use deterministic output for regression testing (stable version info)
103    /// This only affects the version info in ES5 file headers, not the test
104    /// vectors themselves. Test vectors always use a fixed seed for
105    /// reproducibility.
106    /// - deterministic=true: Use crate version (v0.1.0) in ES5 headers
107    /// - deterministic=false: Use git commit hash in ES5 headers
108    #[arg(long)]
109    deterministic: bool,
110
111    /// Custom seed for test vector generation (32 bytes as hex string)
112    /// If not provided, uses a default fixed seed for reproducibility.
113    /// Example: --seed 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
114    #[arg(long)]
115    seed: Option<String>,
116}
117
118pub fn main() {
119    let args = Args::parse();
120
121    // Parse seed if provided
122    let seed = args.seed.map(|seed_str| {
123        parse_seed(&seed_str).unwrap_or_else(|err| {
124            eprintln!("Error parsing seed: {}", err);
125            std::process::exit(1);
126        })
127    });
128
129    // generate vectors
130    let vectors = vectors::generate(args.mode.clone(), args.param_type.clone(), seed);
131
132    // save to output file
133    let mut writer: Box<dyn Write> = match args.output_file.as_str() {
134        "-" => Box::new(io::stdout()),
135        _ => Box::new(File::create(&args.output_file).expect("could not create file")),
136    };
137
138    match args.format {
139        OutputFormat::Es5 => {
140            vectors::write_es5(
141                &mut writer,
142                &vectors,
143                args.param_type,
144                args.deterministic,
145                seed,
146            )
147            .expect("could not write to file");
148        }
149        OutputFormat::Json => {
150            serde_json::to_writer_pretty(writer, &vectors).expect("could not write to file");
151        }
152    }
153}