diff --git a/Cargo.lock b/Cargo.lock index 62d96b5876..d7aaf3c947 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2386,6 +2386,63 @@ dependencies = [ "rand", ] +[[package]] +name = "napi" +version = "2.16.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3" +dependencies = [ + "bitflags 2.4.2", + "ctor", + "napi-derive", + "napi-sys", + "once_cell", +] + +[[package]] +name = "napi-build" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcae8ad5609d14afb3a3b91dee88c757016261b151e9dcecabf1b2a31a6cab14" + +[[package]] +name = "napi-derive" +version = "2.16.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" +dependencies = [ + "cfg-if 1.0.0", + "convert_case", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "napi-derive-backend" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" +dependencies = [ + "convert_case", + "once_cell", + "proc-macro2", + "quote", + "regex", + "semver", + "syn 2.0.100", +] + +[[package]] +name = "napi-sys" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" +dependencies = [ + "libloading", +] + [[package]] name = "neon" version = "1.0.0" @@ -2867,6 +2924,37 @@ dependencies = [ "time", ] +[[package]] +name = "plonk-napi" +version = "0.1.0" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "arkworks", + "getrandom 0.2.15", + "kimchi", + "libc", + "mina-curves", + "mina-poseidon", + "napi", + "napi-build", + "napi-derive", + "num-bigint", + "o1-utils", + "once_cell", + "paste", + "poly-commitment", + "rand", + "rayon", + "rmp-serde", + "serde", + "serde_json", + "serde_with", + "wasm-types", +] + [[package]] name = "plonk_neon" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index a4303d9237..cbb7773e07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "mvpoly", "o1vm", "plonk-neon", + "plonk-napi", "plonk-wasm", "poly-commitment", "poseidon", @@ -61,6 +62,9 @@ libflate = "2" log = "0.4.20" num-bigint = { version = "0.4.4", features = ["rand", "serde"] } num-integer = "0.1.45" +napi = { version = "2.16.8", default-features = false, features = ["napi7"] } +napi-derive = "2.16.8" +napi-build = "2.1.0" ocaml = { version = "0.22.2" } ocaml-gen = { version = "1.0.0" } once_cell = "=1.21.3" diff --git a/plonk-napi/Cargo.toml b/plonk-napi/Cargo.toml new file mode 100644 index 0000000000..6cfa08e0e6 --- /dev/null +++ b/plonk-napi/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "plonk-napi" +version = "0.1.0" +authors = ["opensource@o1labs.org"] +description = "Node-API bindings for plonk proof systems" +repository = "https://github.com/MinaProtocol/mina" +license = "MIT/Apache-2.0" +edition = "2021" + +[lib] +name = "plonk_napi" +crate-type = ["cdylib"] # to generate a dynamic library that is loadable by Node + +[dependencies] +napi = { workspace = true, features = ["napi7"] } +napi-derive.workspace = true + +# arkworks +ark-ec.workspace = true +ark-ff.workspace = true +ark-poly.workspace = true +ark-serialize.workspace = true +arkworks.workspace = true + +# proof-systems +kimchi.workspace = true +mina-curves = { path = "../curves" } +mina-poseidon = { path = "../poseidon" } +o1-utils = { path = "../utils" } +poly-commitment = { path = "../poly-commitment" } + +getrandom.workspace = true +libc.workspace = true +num-bigint.workspace = true +once_cell.workspace = true +paste.workspace = true +rand.workspace = true +rayon.workspace = true +rmp-serde.workspace = true +serde.workspace = true +serde_json.workspace = true +serde_with.workspace = true +wasm-types.workspace = true + +[build-dependencies] +napi-build.workspace = true diff --git a/plonk-napi/build.rs b/plonk-napi/build.rs new file mode 100644 index 0000000000..0f1b01002b --- /dev/null +++ b/plonk-napi/build.rs @@ -0,0 +1,3 @@ +fn main() { + napi_build::setup(); +} diff --git a/plonk-napi/src/circuit.rs b/plonk-napi/src/circuit.rs new file mode 100644 index 0000000000..0c2a850228 --- /dev/null +++ b/plonk-napi/src/circuit.rs @@ -0,0 +1,36 @@ +use ark_ff::PrimeField; +use kimchi::circuits::{constraints::ConstraintSystem, gate::CircuitGate}; +use mina_curves::pasta::Fp; +use napi::bindgen_prelude::*; +use napi_derive::napi; +use serde::Serialize; + +use crate::types::WasmPastaFpPlonkIndex; + +#[derive(Serialize)] +struct Circuit +where + F: PrimeField, +{ + public_input_size: usize, + #[serde(bound = "CircuitGate: Serialize")] + gates: Vec>, +} + +impl From<&ConstraintSystem> for Circuit +where + F: PrimeField, +{ + fn from(cs: &ConstraintSystem) -> Self { + Self { + public_input_size: cs.public, + gates: cs.gates.to_vec(), + } + } +} + +#[napi] +pub fn prover_to_json(prover_index: External) -> String { + let circuit: Circuit = prover_index.0.cs.as_ref().into(); + serde_json::to_string(&circuit).expect("couldn't serialize constraints") +} diff --git a/plonk-napi/src/lib.rs b/plonk-napi/src/lib.rs new file mode 100644 index 0000000000..f0c304a7b9 --- /dev/null +++ b/plonk-napi/src/lib.rs @@ -0,0 +1,8 @@ +mod circuit; +mod poseidon; +mod types; + +pub use poseidon::{caml_pasta_fp_poseidon_block_cipher, caml_pasta_fq_poseidon_block_cipher}; + +pub use circuit::prover_to_json; +pub use types::{prover_index_from_bytes, prover_index_to_bytes, WasmPastaFpPlonkIndex}; diff --git a/plonk-napi/src/poseidon.rs b/plonk-napi/src/poseidon.rs new file mode 100644 index 0000000000..dc7c9f108d --- /dev/null +++ b/plonk-napi/src/poseidon.rs @@ -0,0 +1,56 @@ +use arkworks::{WasmPastaFp, WasmPastaFq}; +use mina_curves::pasta::{Fp, Fq}; +use mina_poseidon::{constants::PlonkSpongeConstantsKimchi, permutation::poseidon_block_cipher}; +use napi::bindgen_prelude::*; +use napi_derive::napi; +use wasm_types::{FlatVector, FlatVectorElem}; + +// fp + +#[napi] +pub fn caml_pasta_fp_poseidon_block_cipher(state: Uint8Array) -> Result { + println!("from native rust"); + + let mut state_vec: Vec = FlatVector::::from_bytes(state.to_vec()) + .into_iter() + .map(Into::into) + .collect(); + + poseidon_block_cipher::( + mina_poseidon::pasta::fp_kimchi::static_params(), + &mut state_vec, + ); + + let res: Vec = state_vec + .into_iter() + .map(WasmPastaFp) + .flat_map(FlatVectorElem::flatten) + .collect(); + + Ok(Uint8Array::from(res)) +} + +// fq + +#[napi] +pub fn caml_pasta_fq_poseidon_block_cipher(state: Uint8Array) -> Result { + println!("from native rust"); + + let mut state_vec: Vec = FlatVector::::from_bytes(state.to_vec()) + .into_iter() + .map(Into::into) + .collect(); + + poseidon_block_cipher::( + mina_poseidon::pasta::fq_kimchi::static_params(), + &mut state_vec, + ); + + let res: Vec = state_vec + .into_iter() + .map(WasmPastaFq) + .flat_map(FlatVectorElem::flatten) + .collect(); + + Ok(Uint8Array::from(res)) +} diff --git a/plonk-napi/src/types.rs b/plonk-napi/src/types.rs new file mode 100644 index 0000000000..985bf50d35 --- /dev/null +++ b/plonk-napi/src/types.rs @@ -0,0 +1,79 @@ +use kimchi::{linearization::expr_linearization, prover_index::ProverIndex}; +use mina_curves::pasta::{Vesta as GAffine, VestaParameters}; +use mina_poseidon::{constants::PlonkSpongeConstantsKimchi, sponge::DefaultFqSponge}; +use napi::bindgen_prelude::{Error, External, Result as NapiResult, Status, Uint8Array}; +use napi_derive::napi; +use poly_commitment::ipa::{OpeningProof, SRS}; +use serde::{Deserialize, Serialize}; +use std::{io::Cursor, sync::Arc}; + +pub struct WasmPastaFpPlonkIndex(pub Box>>); + +// TOOD: remove incl all dependencies when no longer needed and we only pass napi objects around +#[derive(Serialize, Deserialize)] +struct SerializedProverIndex { + prover_index: Vec, + srs: Vec, +} + +// TOOD: remove incl all dependencies when no longer needed and we only pass napi objects around +impl WasmPastaFpPlonkIndex { + fn serialize_inner(&self) -> Result, String> { + let prover_index = rmp_serde::to_vec(self.0.as_ref()).map_err(|e| e.to_string())?; + + let mut srs = Vec::new(); + self.0 + .srs + .serialize(&mut rmp_serde::Serializer::new(&mut srs)) + .map_err(|e| e.to_string())?; + + let serialized = SerializedProverIndex { prover_index, srs }; + + rmp_serde::to_vec(&serialized).map_err(|e| e.to_string()) + } + + fn deserialize_inner(bytes: &[u8]) -> Result { + let serialized: SerializedProverIndex = + rmp_serde::from_slice(bytes).map_err(|e| e.to_string())?; + + let mut index: ProverIndex> = ProverIndex::deserialize( + &mut rmp_serde::Deserializer::new(Cursor::new(serialized.prover_index)), + ) + .map_err(|e| e.to_string())?; + + let srs = SRS::::deserialize(&mut rmp_serde::Deserializer::new(Cursor::new( + serialized.srs, + ))) + .map_err(|e| e.to_string())?; + + index.srs = Arc::new(srs); + + let (linearization, powers_of_alpha) = + expr_linearization(Some(&index.cs.feature_flags), true); + index.linearization = linearization; + index.powers_of_alpha = powers_of_alpha; + + index.compute_verifier_index_digest::< + DefaultFqSponge, + >(); + + Ok(WasmPastaFpPlonkIndex(Box::new(index))) + } +} + +// TOOD: remove incl all dependencies when no longer needed and we only pass napi objects around +#[napi] +pub fn prover_index_from_bytes(bytes: Uint8Array) -> NapiResult> { + let index = WasmPastaFpPlonkIndex::deserialize_inner(bytes.as_ref()) + .map_err(|e| Error::new(Status::InvalidArg, e))?; + Ok(External::new(index)) +} + +// TOOD: remove incl all dependencies when no longer needed and we only pass napi objects around +#[napi] +pub fn prover_index_to_bytes(index: External) -> NapiResult { + let bytes = index + .serialize_inner() + .map_err(|e| Error::new(Status::GenericFailure, e))?; + Ok(Uint8Array::from(bytes)) +} diff --git a/plonk-wasm/src/pasta_fp_plonk_index.rs b/plonk-wasm/src/pasta_fp_plonk_index.rs index b56347c23f..5af8cdf550 100644 --- a/plonk-wasm/src/pasta_fp_plonk_index.rs +++ b/plonk-wasm/src/pasta_fp_plonk_index.rs @@ -20,7 +20,8 @@ use mina_poseidon::{constants::PlonkSpongeConstantsKimchi, sponge::DefaultFqSpon use serde::{Deserialize, Serialize}; use std::{ fs::{File, OpenOptions}, - io::{BufReader, BufWriter, Seek, SeekFrom::Start}, + io::{BufReader, BufWriter, Cursor, Seek, SeekFrom::Start}, + sync::Arc, }; use wasm_bindgen::prelude::*; use wasm_types::FlatVector as WasmFlatVector; @@ -35,6 +36,71 @@ pub struct WasmPastaFpPlonkIndex( #[wasm_bindgen(skip)] pub Box>>, ); +// TOOD: remove incl all dependencies when no longer needed and we only pass napi objects around +#[derive(Serialize, Deserialize)] +struct SerializedProverIndex { + prover_index: Vec, + srs: Vec, +} + +// TOOD: remove incl all dependencies when no longer needed and we only pass napi objects around +#[wasm_bindgen] +impl WasmPastaFpPlonkIndex { + #[wasm_bindgen(js_name = "serialize")] + pub fn serialize(&self) -> Result, JsError> { + serialize_prover_index(self.0.as_ref()) + .map_err(|e| JsError::new(&format!("WasmPastaFpPlonkIndex::serialize: {e}"))) + } + + #[wasm_bindgen(js_name = "deserialize")] + pub fn deserialize(bytes: &[u8]) -> Result { + deserialize_prover_index(bytes) + .map(WasmPastaFpPlonkIndex) + .map_err(|e| JsError::new(&format!("WasmPastaFpPlonkIndex::deserialize: {e}"))) + } +} + +fn serialize_prover_index( + index: &ProverIndex>, +) -> Result, String> { + let prover_index = rmp_serde::to_vec(index).map_err(|e| e.to_string())?; + + let mut srs = Vec::new(); + index + .srs + .serialize(&mut rmp_serde::Serializer::new(&mut srs)) + .map_err(|e| e.to_string())?; + + let serialized = SerializedProverIndex { prover_index, srs }; + + rmp_serde::to_vec(&serialized).map_err(|e| e.to_string()) +} + +fn deserialize_prover_index( + bytes: &[u8], +) -> Result>>, String> { + let serialized: SerializedProverIndex = + rmp_serde::from_slice(bytes).map_err(|e| e.to_string())?; + + let mut index: ProverIndex> = ProverIndex::deserialize( + &mut rmp_serde::Deserializer::new(Cursor::new(serialized.prover_index)), + ) + .map_err(|e| e.to_string())?; + + let srs = poly_commitment::ipa::SRS::::deserialize(&mut rmp_serde::Deserializer::new( + Cursor::new(serialized.srs), + )) + .map_err(|e| e.to_string())?; + + index.srs = Arc::new(srs); + + let (linearization, powers_of_alpha) = expr_linearization(Some(&index.cs.feature_flags), true); + index.linearization = linearization; + index.powers_of_alpha = powers_of_alpha; + + Ok(Box::new(index)) +} + // This should mimic LookupTable structure #[wasm_bindgen] pub struct WasmPastaFpLookupTable {