kimchi_visu/
lib.rs

1//! Implements a tool to visualize a circuit as an HTML page.
2
3use ark_ff::PrimeField;
4use kimchi::{
5    circuits::{
6        argument::Argument,
7        expr,
8        polynomials::{
9            complete_add::CompleteAdd, endomul_scalar::EndomulScalar, endosclmul::EndosclMul,
10            poseidon::Poseidon, varbasemul::VarbaseMul,
11        },
12    },
13    curve::KimchiCurve,
14    prover_index::ProverIndex,
15};
16use poly_commitment::{commitment::CommitmentCurve, ipa::OpeningProof};
17use serde::Serialize;
18use std::{
19    collections::HashMap,
20    fmt::Display,
21    fs::{self, File},
22    io::Write,
23    path::Path,
24};
25use tinytemplate::TinyTemplate;
26
27pub mod witness;
28
29pub use witness::Witness;
30
31/// Contains variable used in the template
32#[derive(Serialize)]
33struct Context {
34    js: String,
35    data: String,
36}
37
38/// Allows us to quickly implement a LaTeX encoder for each gate
39trait LaTeX<F>: Argument<F>
40where
41    F: PrimeField,
42{
43    fn latex() -> Vec<Vec<String>> {
44        Self::constraints(&mut expr::Cache::default())
45            .iter()
46            .map(|c| c.latex_str())
47            .collect()
48    }
49}
50
51/// Implement [LaTeX] for all gates
52impl<T, F> LaTeX<F> for T
53where
54    T: Argument<F>,
55    F: PrimeField + Display,
56{
57}
58
59pub fn latex_constraints<G>() -> HashMap<&'static str, Vec<Vec<String>>>
60where
61    G: CommitmentCurve,
62{
63    let mut map = HashMap::new();
64    map.insert("Poseidon", Poseidon::<G::ScalarField>::latex());
65    map.insert("CompleteAdd", CompleteAdd::<G::ScalarField>::latex());
66    map.insert("VarBaseMul", VarbaseMul::<G::ScalarField>::latex());
67    map.insert("EndoMul", EndosclMul::<G::ScalarField>::latex());
68    map.insert("EndoMulScalar", EndomulScalar::<G::ScalarField>::latex());
69    map
70}
71
72/// Produces a `circuit.html` in the current folder.
73///
74/// # Panics
75///
76/// Will panic if `TinyTemplate::render()` returns `Error` or `std::fs::File::create()` returns `Error`.
77pub fn visu<G: KimchiCurve>(
78    index: &ProverIndex<G, OpeningProof<G>>,
79    witness: Option<Witness<G::ScalarField>>,
80) where
81    G::BaseField: PrimeField,
82{
83    // serialize index
84    let index = serde_json::to_string(index).expect("couldn't serialize index");
85    let mut data = format!("const index = {index};");
86
87    // serialize witness
88    if let Some(witness) = witness {
89        let witness = serde_json::to_string(&witness).expect("couldn't serialize witness");
90        data = format!("{data}const witness = {witness};");
91    } else {
92        data.push_str("const witness = null;");
93    }
94
95    // serialize constraints
96    let constraints = latex_constraints::<G>();
97    let constraints = serde_json::to_string(&constraints).expect("couldn't serialize constraints");
98    data = format!("{data}const constraints = {constraints};");
99
100    // create template
101    let template_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/assets/template.html");
102    let template = fs::read_to_string(&template_path).unwrap_or_else(|e| {
103        format!(
104            "could not read template file {}: {e}",
105            template_path.display()
106        )
107    });
108
109    let mut tt = TinyTemplate::new();
110    tt.set_default_formatter(&tinytemplate::format_unescaped);
111    tt.add_template("circuit", &template)
112        .expect("could not create template");
113
114    // render
115    let html_output = std::env::current_dir()
116        .expect("no current directory?")
117        .join("circuit.html");
118
119    let js_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/assets/script.js");
120    let js = fs::read_to_string(&js_path)
121        .unwrap_or_else(|e| format!("could not read js file {}: {e}", js_path.display()));
122
123    let context = Context { js, data };
124
125    let rendered = tt
126        .render("circuit", &context)
127        .unwrap_or_else(|e| panic!("template file can't be rendered: {e}"));
128
129    let mut file = File::create(html_output).unwrap_or_else(|e| panic!("{e}"));
130    write!(&mut file, "{rendered}").expect("couldn't write the file on disk");
131}