1use crate::{gate_vector::NapiFpGateVector, WasmFpSrs};
2use ark_poly::EvaluationDomain;
3use kimchi::{
4 circuits::{
5 constraints::ConstraintSystem,
6 lookup::{runtime_tables::RuntimeTableCfg, tables::LookupTable},
7 },
8 linearization::expr_linearization,
9 prover_index::ProverIndex,
10};
11use mina_curves::pasta::{Fp, Pallas as GAffineOther, Vesta as GAffine, VestaParameters};
12use mina_poseidon::{
13 constants::PlonkSpongeConstantsKimchi, pasta::FULL_ROUNDS, sponge::DefaultFqSponge,
14};
15use napi::bindgen_prelude::{Error, External, Status, Uint8Array};
16use napi_derive::napi;
17use poly_commitment::{
18 ipa::{OpeningProof, SRS as IPA_SRS},
19 OpenProof, SRS,
20};
21use serde::{Deserialize, Serialize};
22use std::{
23 fs::{File, OpenOptions},
24 io::{BufReader, BufWriter, Cursor, Seek, SeekFrom::Start},
25 sync::Arc,
26};
27
28use crate::tables::{
29 lookup_table_fp_from_js, runtime_table_cfg_fp_from_js, JsLookupTableFp, JsRuntimeTableCfgFp,
30};
31
32type ProverIndexType = ProverIndex<
33 FULL_ROUNDS,
34 GAffine,
35 <OpeningProof<GAffine, FULL_ROUNDS> as OpenProof<GAffine, FULL_ROUNDS>>::SRS,
36>;
37
38#[napi(js_name = "WasmPastaFpPlonkIndex")]
39pub struct WasmPastaFpPlonkIndex(#[napi(skip)] pub Box<ProverIndexType>);
40
41#[derive(Serialize, Deserialize)]
42struct SerializedProverIndex {
43 prover_index: Vec<u8>,
44 srs: Vec<u8>,
45}
46
47impl WasmPastaFpPlonkIndex {
48 fn serialize_inner(&self) -> Result<Vec<u8>, String> {
49 let prover_index = rmp_serde::to_vec(self.0.as_ref()).map_err(|e| e.to_string())?;
50
51 let mut srs = Vec::new();
52 self.0
53 .srs
54 .serialize(&mut rmp_serde::Serializer::new(&mut srs))
55 .map_err(|e| e.to_string())?;
56
57 let serialized = SerializedProverIndex { prover_index, srs };
58
59 rmp_serde::to_vec(&serialized).map_err(|e| e.to_string())
60 }
61
62 fn deserialize_inner(bytes: &[u8]) -> Result<Self, String> {
63 let serialized: SerializedProverIndex =
64 rmp_serde::from_slice(bytes).map_err(|e| e.to_string())?;
65
66 let mut index: ProverIndexType = ProverIndex::deserialize(
67 &mut rmp_serde::Deserializer::new(Cursor::new(serialized.prover_index)),
68 )
69 .map_err(|e| e.to_string())?;
70
71 let srs = IPA_SRS::<GAffine>::deserialize(&mut rmp_serde::Deserializer::new(Cursor::new(
72 serialized.srs,
73 )))
74 .map_err(|e| e.to_string())?;
75
76 index.srs = Arc::new(srs);
77
78 let (linearization, powers_of_alpha) =
79 expr_linearization(Some(&index.cs.feature_flags), true);
80 index.linearization = linearization;
81 index.powers_of_alpha = powers_of_alpha;
82
83 index.compute_verifier_index_digest::<
84 DefaultFqSponge<VestaParameters, PlonkSpongeConstantsKimchi, FULL_ROUNDS>,
85 >();
86
87 Ok(WasmPastaFpPlonkIndex(Box::new(index)))
88 }
89}
90
91#[napi(js_name = "prover_index_fp_serialize")]
93pub fn prover_index_fp_serialize(
94 index: &External<WasmPastaFpPlonkIndex>,
95) -> napi::bindgen_prelude::Result<Uint8Array> {
96 let bytes = index
97 .serialize_inner()
98 .map_err(|e| Error::new(Status::GenericFailure, e))?;
99 Ok(Uint8Array::from(bytes))
100}
101
102#[napi(js_name = "prover_index_fp_deserialize")]
104pub fn prover_index_fp_deserialize(
105 bytes: Uint8Array,
106) -> napi::bindgen_prelude::Result<External<WasmPastaFpPlonkIndex>> {
107 let index = WasmPastaFpPlonkIndex::deserialize_inner(bytes.as_ref())
108 .map_err(|e| Error::new(Status::InvalidArg, e))?;
109 Ok(External::new(index))
110}
111
112#[napi(js_name = "caml_pasta_fp_plonk_index_max_degree")]
113pub fn caml_pasta_fp_plonk_index_max_degree(index: &External<WasmPastaFpPlonkIndex>) -> i32 {
114 index.0.srs.max_poly_size() as i32
115}
116
117#[napi(js_name = "caml_pasta_fp_plonk_index_public_inputs")]
118pub fn caml_pasta_fp_plonk_index_public_inputs(index: &External<WasmPastaFpPlonkIndex>) -> i32 {
119 index.0.cs.public as i32
120}
121
122#[napi(js_name = "caml_pasta_fp_plonk_index_domain_d1_size")]
123pub fn caml_pasta_fp_plonk_index_domain_d1_size(index: &External<WasmPastaFpPlonkIndex>) -> i32 {
124 index.0.cs.domain.d1.size() as i32
125}
126
127#[napi(js_name = "caml_pasta_fp_plonk_index_domain_d4_size")]
128pub fn caml_pasta_fp_plonk_index_domain_d4_size(index: &External<WasmPastaFpPlonkIndex>) -> i32 {
129 index.0.cs.domain.d4.size() as i32
130}
131
132#[napi(js_name = "pasta_fp_plonk_index_domain_d8_size")]
133pub fn caml_pasta_fp_plonk_index_domain_d8_size(index: &External<WasmPastaFpPlonkIndex>) -> i32 {
134 index.0.cs.domain.d8.size() as i32
135}
136
137#[napi(js_name = "caml_pasta_fp_plonk_index_create")]
138pub fn caml_pasta_fp_plonk_index_create(
139 gates: &NapiFpGateVector,
140 public_: i32,
141 lookup_tables: Vec<JsLookupTableFp>,
142 runtime_table_cfgs: Vec<JsRuntimeTableCfgFp>,
143 prev_challenges: i32,
144 srs: &WasmFpSrs,
145 lazy_mode: bool,
146) -> Result<External<WasmPastaFpPlonkIndex>, Error> {
147 let gates: Vec<_> = gates.to_vec();
148
149 let runtime_cfgs: Vec<RuntimeTableCfg<Fp>> = runtime_table_cfgs
150 .into_iter()
151 .map(runtime_table_cfg_fp_from_js)
152 .collect::<Result<_, _>>()?;
153
154 let lookup_tables: Vec<LookupTable<Fp>> = lookup_tables
155 .into_iter()
156 .map(lookup_table_fp_from_js)
157 .collect::<Result<_, _>>()?;
158
159 let cs = ConstraintSystem::<Fp>::create(gates)
160 .public(public_ as usize)
161 .prev_challenges(prev_challenges as usize)
162 .lookup(lookup_tables)
163 .max_poly_size(Some(srs.0.max_poly_size()))
164 .runtime(if runtime_cfgs.is_empty() {
165 None
166 } else {
167 Some(runtime_cfgs)
168 })
169 .lazy_mode(lazy_mode)
170 .build()
171 .map_err(|_| {
172 Error::new(
173 Status::InvalidArg,
174 "caml_pasta_fp_plonk_index_create: could not create constraint system",
175 )
176 })?;
177
178 let (endo_q, _endo_r) = poly_commitment::ipa::endos::<GAffineOther>();
179
180 srs.0.get_lagrange_basis(cs.domain.d1);
181
182 let mut index = ProverIndex::<FULL_ROUNDS, GAffine, IPA_SRS<GAffine>>::create(
183 cs,
184 endo_q,
185 srs.0.clone(),
186 lazy_mode,
187 );
188 index.compute_verifier_index_digest::<DefaultFqSponge<VestaParameters, PlonkSpongeConstantsKimchi, FULL_ROUNDS>>();
189
190 Ok(External::new(WasmPastaFpPlonkIndex(Box::new(index))))
191}
192
193#[napi(js_name = "caml_pasta_fp_plonk_index_decode")]
194pub fn caml_pasta_fp_plonk_index_decode(
195 bytes: &[u8],
196 srs: &WasmFpSrs,
197) -> Result<External<WasmPastaFpPlonkIndex>, Error> {
198 let mut deserializer = rmp_serde::Deserializer::new(bytes);
199 let mut index =
200 ProverIndex::<FULL_ROUNDS, GAffine, IPA_SRS<GAffine>>::deserialize(&mut deserializer)
201 .map_err(|e| {
202 Error::new(
203 Status::InvalidArg,
204 format!("caml_pasta_fp_plonk_index_decode: {}", e),
205 )
206 })?;
207
208 index.srs = srs.0.clone();
209 let (linearization, powers_of_alpha) = expr_linearization(Some(&index.cs.feature_flags), true);
210 index.linearization = linearization;
211 index.powers_of_alpha = powers_of_alpha;
212
213 Ok(External::new(WasmPastaFpPlonkIndex(Box::new(index))))
214}
215
216#[napi(js_name = "caml_pasta_fp_plonk_index_encode")]
217pub fn caml_pasta_fp_plonk_index_encode(
218 index: &External<WasmPastaFpPlonkIndex>,
219) -> Result<Uint8Array, Error> {
220 let mut buffer = Vec::new();
221 let mut serializer = rmp_serde::Serializer::new(&mut buffer);
222 index.0.serialize(&mut serializer).map_err(|e| {
223 Error::new(
224 Status::InvalidArg,
225 format!("caml_pasta_fp_plonk_index_encode: {}", e),
226 )
227 })?;
228 Ok(Uint8Array::from(buffer))
229}
230
231#[napi(js_name = "caml_pasta_fp_plonk_index_write")]
232pub fn caml_pasta_fp_plonk_index_write(
233 append: Option<bool>,
234 index: &External<WasmPastaFpPlonkIndex>,
235 path: String,
236) -> Result<(), Error> {
237 let file = OpenOptions::new()
238 .append(append.unwrap_or(true))
239 .open(path)
240 .map_err(|_| Error::new(Status::InvalidArg, "caml_pasta_fp_plonk_index_write"))?;
241 let w = BufWriter::new(file);
242 index
243 .0
244 .serialize(&mut rmp_serde::Serializer::new(w))
245 .map_err(|e| {
246 Error::new(
247 Status::InvalidArg,
248 format!("caml_pasta_fp_plonk_index_write: {e}"),
249 )
250 })
251}
252
253#[napi(js_name = "caml_pasta_fp_plonk_index_read")]
254pub fn caml_pasta_fp_plonk_index_read(
255 offset: Option<i32>,
256 srs: &WasmFpSrs,
257 path: String,
258) -> Result<External<WasmPastaFpPlonkIndex>, Error> {
259 let file = match File::open(path) {
261 Err(_) => {
262 return Err(Error::new(
263 Status::InvalidArg,
264 "caml_pasta_fp_plonk_index_read",
265 ))
266 }
267 Ok(file) => file,
268 };
269 let mut r = BufReader::new(file);
270
271 if let Some(offset) = offset {
273 r.seek(Start(offset as u64)).map_err(|err| {
274 Error::new(
275 Status::InvalidArg,
276 format!("caml_pasta_fp_plonk_index_read: {err}"),
277 )
278 })?;
279 }
280
281 let mut t = ProverIndex::<FULL_ROUNDS, GAffine, IPA_SRS<GAffine>>::deserialize(
283 &mut rmp_serde::Deserializer::new(r),
284 )
285 .map_err(|err| {
286 Error::new(
287 Status::InvalidArg,
288 format!("caml_pasta_fp_plonk_index_read: {err}"),
289 )
290 })?;
291 t.srs = srs.0.clone();
292 let (linearization, powers_of_alpha) = expr_linearization(Some(&t.cs.feature_flags), true);
293 t.linearization = linearization;
294 t.powers_of_alpha = powers_of_alpha;
295
296 Ok(External::new(WasmPastaFpPlonkIndex(Box::new(t))))
298}