plonk_wasm/
pasta_fq_plonk_index.rs

1use ark_poly::EvaluationDomain;
2use kimchi::circuits::lookup::runtime_tables::RuntimeTableCfg;
3
4use crate::{
5    arkworks::WasmPastaFq,
6    gate_vector::fq::WasmGateVector,
7    srs::fq::WasmFqSrs as WasmSrs,
8    wasm_flat_vector::WasmFlatVector,
9    wasm_vector::{fq::*, WasmVector},
10};
11use kimchi::{
12    circuits::{constraints::ConstraintSystem, gate::CircuitGate, lookup::tables::LookupTable},
13    linearization::expr_linearization,
14    poly_commitment::{ipa::OpeningProof, SRS as _},
15    prover_index::ProverIndex,
16};
17use mina_curves::pasta::{Fq, Pallas as GAffine, PallasParameters, Vesta as GAffineOther};
18use mina_poseidon::{constants::PlonkSpongeConstantsKimchi, sponge::DefaultFqSponge};
19use serde::{Deserialize, Serialize};
20use std::{
21    fs::{File, OpenOptions},
22    io::{BufReader, BufWriter, Seek, SeekFrom::Start},
23};
24use wasm_bindgen::prelude::*;
25
26//
27// CamlPastaFqPlonkIndex (custom type)
28//
29
30/// Boxed so that we don't store large proving indexes in the OCaml heap.
31#[wasm_bindgen]
32pub struct WasmPastaFqPlonkIndex(
33    #[wasm_bindgen(skip)] pub Box<ProverIndex<GAffine, OpeningProof<GAffine>>>,
34);
35
36#[wasm_bindgen]
37pub struct WasmPastaFqLookupTable {
38    #[wasm_bindgen(skip)]
39    pub id: i32,
40    #[wasm_bindgen(skip)]
41    pub data: WasmVecVecFq,
42}
43
44impl From<WasmPastaFqLookupTable> for LookupTable<Fq> {
45    fn from(wasm_lt: WasmPastaFqLookupTable) -> LookupTable<Fq> {
46        LookupTable {
47            id: wasm_lt.id,
48            data: wasm_lt.data.0,
49        }
50    }
51}
52
53// JS constructor for js/bindings.js
54#[wasm_bindgen]
55impl WasmPastaFqLookupTable {
56    #[wasm_bindgen(constructor)]
57    pub fn new(id: i32, data: WasmVecVecFq) -> WasmPastaFqLookupTable {
58        WasmPastaFqLookupTable { id, data }
59    }
60}
61
62// Runtime table config
63
64#[wasm_bindgen]
65pub struct WasmPastaFqRuntimeTableCfg {
66    #[wasm_bindgen(skip)]
67    pub id: i32,
68    #[wasm_bindgen(skip)]
69    pub first_column: WasmFlatVector<WasmPastaFq>,
70}
71
72impl From<WasmPastaFqRuntimeTableCfg> for RuntimeTableCfg<Fq> {
73    fn from(wasm_rt_cfg: WasmPastaFqRuntimeTableCfg) -> Self {
74        Self {
75            id: wasm_rt_cfg.id,
76            first_column: wasm_rt_cfg
77                .first_column
78                .into_iter()
79                .map(Into::into)
80                .collect(),
81        }
82    }
83}
84
85// JS constructor for js/bindings.js
86#[wasm_bindgen]
87impl WasmPastaFqRuntimeTableCfg {
88    #[wasm_bindgen(constructor)]
89    pub fn new(id: i32, first_column: WasmFlatVector<WasmPastaFq>) -> Self {
90        Self { id, first_column }
91    }
92}
93
94//
95// CamlPastaFqPlonkIndex methods
96//
97
98// Change js/web/worker-spec.js accordingly
99#[wasm_bindgen]
100pub fn caml_pasta_fq_plonk_index_create(
101    gates: &WasmGateVector,
102    public_: i32,
103    lookup_tables: WasmVector<WasmPastaFqLookupTable>,
104    runtime_table_cfgs: WasmVector<WasmPastaFqRuntimeTableCfg>,
105    prev_challenges: i32,
106    srs: &WasmSrs,
107) -> Result<WasmPastaFqPlonkIndex, JsError> {
108    console_error_panic_hook::set_once();
109    let index = crate::rayon::run_in_pool(|| {
110        // flatten the permutation information (because OCaml has a different way of keeping track of permutations)
111        let gates: Vec<_> = gates
112            .0
113            .iter()
114            .map(|gate| CircuitGate::<Fq> {
115                typ: gate.typ,
116                wires: gate.wires,
117                coeffs: gate.coeffs.clone(),
118            })
119            .collect();
120
121        let rust_runtime_table_cfgs: Vec<RuntimeTableCfg<Fq>> =
122            runtime_table_cfgs.into_iter().map(Into::into).collect();
123
124        let rust_lookup_tables: Vec<LookupTable<Fq>> =
125            lookup_tables.into_iter().map(Into::into).collect();
126
127        // create constraint system
128        let cs = match ConstraintSystem::<Fq>::create(gates)
129            .public(public_ as usize)
130            .prev_challenges(prev_challenges as usize)
131            .lookup(rust_lookup_tables)
132            .runtime(if rust_runtime_table_cfgs.is_empty() {
133                None
134            } else {
135                Some(rust_runtime_table_cfgs)
136            })
137            .build()
138        {
139            Err(_) => {
140                return Err("caml_pasta_fq_plonk_index_create: could not create constraint system");
141            }
142            Ok(cs) => cs,
143        };
144
145        // endo
146        let (endo_q, _endo_r) = poly_commitment::ipa::endos::<GAffineOther>();
147
148        srs.0.get_lagrange_basis(cs.domain.d1);
149
150        let mut index =
151            ProverIndex::<GAffine, OpeningProof<GAffine>>::create(cs, endo_q, srs.0.clone());
152        // Compute and cache the verifier index digest
153        index.compute_verifier_index_digest::<DefaultFqSponge<PallasParameters, PlonkSpongeConstantsKimchi>>();
154
155        Ok(index)
156    });
157
158    // create index
159    match index {
160        Ok(index) => Ok(WasmPastaFqPlonkIndex(Box::new(index))),
161        Err(str) => Err(JsError::new(str)),
162    }
163}
164
165#[wasm_bindgen]
166pub fn caml_pasta_fq_plonk_index_max_degree(index: &WasmPastaFqPlonkIndex) -> i32 {
167    index.0.srs.max_poly_size() as i32
168}
169
170#[wasm_bindgen]
171pub fn caml_pasta_fq_plonk_index_public_inputs(index: &WasmPastaFqPlonkIndex) -> i32 {
172    index.0.cs.public as i32
173}
174
175#[wasm_bindgen]
176pub fn caml_pasta_fq_plonk_index_domain_d1_size(index: &WasmPastaFqPlonkIndex) -> i32 {
177    index.0.cs.domain.d1.size() as i32
178}
179
180#[wasm_bindgen]
181pub fn caml_pasta_fq_plonk_index_domain_d4_size(index: &WasmPastaFqPlonkIndex) -> i32 {
182    index.0.cs.domain.d4.size() as i32
183}
184
185#[wasm_bindgen]
186pub fn caml_pasta_fq_plonk_index_domain_d8_size(index: &WasmPastaFqPlonkIndex) -> i32 {
187    index.0.cs.domain.d8.size() as i32
188}
189
190#[wasm_bindgen]
191pub fn caml_pasta_fq_plonk_index_decode(
192    bytes: &[u8],
193    srs: &WasmSrs,
194) -> Result<WasmPastaFqPlonkIndex, JsError> {
195    let mut deserializer = rmp_serde::Deserializer::new(bytes);
196    let mut index =
197        ProverIndex::<GAffine, OpeningProof<GAffine>>::deserialize(&mut deserializer)
198            .map_err(|e| JsError::new(&format!("caml_pasta_fq_plonk_index_decode: {}", e)))?;
199
200    index.srs = srs.0.clone();
201    let (linearization, powers_of_alpha) = expr_linearization(Some(&index.cs.feature_flags), true);
202    index.linearization = linearization;
203    index.powers_of_alpha = powers_of_alpha;
204
205    Ok(WasmPastaFqPlonkIndex(Box::new(index)))
206}
207
208#[wasm_bindgen]
209pub fn caml_pasta_fq_plonk_index_encode(index: &WasmPastaFqPlonkIndex) -> Result<Vec<u8>, JsError> {
210    let mut buffer = Vec::new();
211    let mut serializer = rmp_serde::Serializer::new(&mut buffer);
212    index
213        .0
214        .serialize(&mut serializer)
215        .map_err(|e| JsError::new(&format!("caml_pasta_fq_plonk_index_encode: {}", e)))?;
216    Ok(buffer)
217}
218
219#[wasm_bindgen]
220pub fn caml_pasta_fq_plonk_index_read(
221    offset: Option<i32>,
222    srs: &WasmSrs,
223    path: String,
224) -> Result<WasmPastaFqPlonkIndex, JsValue> {
225    // read from file
226    let file = match File::open(path) {
227        Err(_) => return Err(JsValue::from_str("caml_pasta_fq_plonk_index_read")),
228        Ok(file) => file,
229    };
230    let mut r = BufReader::new(file);
231
232    // optional offset in file
233    if let Some(offset) = offset {
234        r.seek(Start(offset as u64))
235            .map_err(|err| JsValue::from_str(&format!("caml_pasta_fq_plonk_index_read: {err}")))?;
236    }
237
238    // deserialize the index
239    let mut t = ProverIndex::<GAffine, OpeningProof<GAffine>>::deserialize(
240        &mut rmp_serde::Deserializer::new(r),
241    )
242    .map_err(|err| JsValue::from_str(&format!("caml_pasta_fq_plonk_index_read: {err}")))?;
243    t.srs = srs.0.clone();
244    let (linearization, powers_of_alpha) = expr_linearization(Some(&t.cs.feature_flags), true);
245    t.linearization = linearization;
246    t.powers_of_alpha = powers_of_alpha;
247
248    //
249    Ok(WasmPastaFqPlonkIndex(Box::new(t)))
250}
251
252#[wasm_bindgen]
253pub fn caml_pasta_fq_plonk_index_write(
254    append: Option<bool>,
255    index: &WasmPastaFqPlonkIndex,
256    path: String,
257) -> Result<(), JsValue> {
258    let file = OpenOptions::new()
259        .append(append.unwrap_or(true))
260        .open(path)
261        .map_err(|_| JsValue::from_str("caml_pasta_fq_plonk_index_write"))?;
262    let w = BufWriter::new(file);
263    index
264        .0
265        .serialize(&mut rmp_serde::Serializer::new(w))
266        .map_err(|e| JsValue::from_str(&format!("caml_pasta_fq_plonk_index_read: {e}")))
267}
268
269#[allow(deprecated)]
270#[wasm_bindgen]
271pub fn caml_pasta_fq_plonk_index_serialize(index: &WasmPastaFqPlonkIndex) -> String {
272    let serialized = rmp_serde::to_vec(&index.0).unwrap();
273    // Deprecated used on purpose: updating this leads to a bug in o1js
274    base64::encode(serialized)
275}