diff --git a/Cargo.lock b/Cargo.lock index cccd069127..d7aaf3c947 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2934,6 +2934,7 @@ dependencies = [ "ark-serialize", "arkworks", "getrandom 0.2.15", + "kimchi", "libc", "mina-curves", "mina-poseidon", @@ -2944,10 +2945,12 @@ dependencies = [ "o1-utils", "once_cell", "paste", + "poly-commitment", "rand", "rayon", "rmp-serde", "serde", + "serde_json", "serde_with", "wasm-types", ] diff --git a/plonk-napi/Cargo.toml b/plonk-napi/Cargo.toml index 87dfbbd0da..21d4471017 100644 --- a/plonk-napi/Cargo.toml +++ b/plonk-napi/Cargo.toml @@ -26,6 +26,8 @@ arkworks.workspace = true mina-curves = { path = "../curves" } mina-poseidon = { path = "../poseidon" } o1-utils = { path = "../utils" } +kimchi = { path = "../kimchi" } +poly-commitment = { path = "../poly-commitment" } getrandom.workspace = true libc.workspace = true @@ -36,6 +38,7 @@ 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 diff --git a/plonk-napi/src/gate_vector.rs b/plonk-napi/src/gate_vector.rs new file mode 100644 index 0000000000..f614c3d3a0 --- /dev/null +++ b/plonk-napi/src/gate_vector.rs @@ -0,0 +1,254 @@ +use kimchi::circuits::{ + gate::{Circuit, CircuitGate, GateType}, + wires::{GateWires, Wire as KimchiWire}, +}; +use mina_curves::pasta::{Fp, Fq}; +use napi::bindgen_prelude::*; +use napi_derive::napi; +use o1_utils::hasher::CryptoDigest; +use paste::paste; +use wasm_types::{FlatVector as WasmFlatVector, FlatVectorElem}; + +use crate::wrappers::{ + field::{WasmPastaFp, WasmPastaFq}, + wires::NapiWire, +}; + +#[napi(object)] +#[derive(Clone, Debug, Default)] +pub struct NapiGateWires { + pub w0: NapiWire, + pub w1: NapiWire, + pub w2: NapiWire, + pub w3: NapiWire, + pub w4: NapiWire, + pub w5: NapiWire, + pub w6: NapiWire, +} + +impl NapiGateWires { + fn into_inner(self) -> GateWires { + [ + KimchiWire::from(self.w0), + KimchiWire::from(self.w1), + KimchiWire::from(self.w2), + KimchiWire::from(self.w3), + KimchiWire::from(self.w4), + KimchiWire::from(self.w5), + KimchiWire::from(self.w6), + ] + } +} + +impl From<&GateWires> for NapiGateWires { + fn from(value: &GateWires) -> Self { + Self { + w0: value[0].into(), + w1: value[1].into(), + w2: value[2].into(), + w3: value[3].into(), + w4: value[4].into(), + w5: value[5].into(), + w6: value[6].into(), + } + } +} + +fn gate_type_from_i32(value: i32) -> Result { + if value < 0 { + return Err(Error::new( + Status::InvalidArg, + format!("invalid GateType discriminant: {}", value), + )); + } + + let variants: &[GateType] = &[ + GateType::Zero, + GateType::Generic, + GateType::Poseidon, + GateType::CompleteAdd, + GateType::VarBaseMul, + GateType::EndoMul, + GateType::EndoMulScalar, + GateType::Lookup, + GateType::CairoClaim, + GateType::CairoInstruction, + GateType::CairoFlags, + GateType::CairoTransition, + GateType::RangeCheck0, + GateType::RangeCheck1, + GateType::ForeignFieldAdd, + GateType::ForeignFieldMul, + GateType::Xor16, + GateType::Rot64, + ]; + + let index = value as usize; + variants.get(index).copied().ok_or_else(|| { + Error::new( + Status::InvalidArg, + format!("invalid GateType discriminant: {}", value), + ) + }) +} + +fn gate_type_to_i32(value: GateType) -> i32 { + value as i32 +} + +macro_rules! impl_gate_support { + ($module:ident, $field:ty, $wasm_field:ty) => { + paste! { + #[napi(object)] + #[derive(Clone, Debug, Default)] + pub struct [] { + pub typ: i32, + pub wires: NapiGateWires, + pub coeffs: Vec, + } + + impl [] { + fn into_inner(self) -> Result> { + let coeffs = WasmFlatVector::<$wasm_field>::from_bytes(self.coeffs) + .into_iter() + .map(Into::into) + .collect(); + + Ok(CircuitGate { + typ: gate_type_from_i32(self.typ)?, + wires: self.wires.into_inner(), + coeffs, + }) + } + + fn from_inner(value: &CircuitGate<$field>) -> Self { + let coeffs = value + .coeffs + .iter() + .cloned() + .map($wasm_field::from) + .flat_map(|elem| elem.flatten()) + .collect(); + + Self { + typ: gate_type_to_i32(value.typ), + wires: (&value.wires).into(), + coeffs, + } + } + } + + #[napi] + #[derive(Clone, Default, Debug)] + pub struct [] { + #[napi(skip)] + pub inner: Vec>, + } + + #[napi] + impl [] { + #[napi(constructor)] + pub fn new() -> Self { + Self { inner: Vec::new() } + } + + #[napi] + pub fn add(&mut self, gate: []) -> Result<()> { + self.inner.push(gate.into_inner()?); + Ok(()) + } + + #[napi] + pub fn get(&self, index: i32) -> [] { + []::from_inner(&self.inner[index as usize]) + } + + #[napi] + pub fn len(&self) -> i32 { + self.inner.len() as i32 + } + + #[napi] + pub fn wrap(&mut self, target: NapiWire, head: NapiWire) { + let row = target.row as usize; + let col = target.col as usize; + self.inner[row].wires[col] = KimchiWire::from(head); + } + + #[napi] + pub fn digest(&self, public_input_size: i32) -> Vec { + Circuit::new(public_input_size as usize, &self.inner) + .digest() + .to_vec() + } + + #[napi] + pub fn serialize(&self, public_input_size: i32) -> Result { + let circuit = Circuit::new(public_input_size as usize, &self.inner); + serde_json::to_string(&circuit).map_err(|err| { + Error::new( + Status::GenericFailure, + format!("failed to serialize circuit: {}", err), + ) + }) + } + } + + #[napi] + pub fn []() -> [] { + []::new() + } + + #[napi] + pub fn []( + vector: &mut [], + gate: [], + ) -> Result<()> { + vector.add(gate) + } + + #[napi] + pub fn []( + vector: &[], + index: i32, + ) -> [] { + vector.get(index) + } + + #[napi] + pub fn []( + vector: &[], + ) -> i32 { + vector.len() + } + + #[napi] + pub fn []( + vector: &mut [], + target: NapiWire, + head: NapiWire, + ) { + vector.wrap(target, head); + } + + #[napi] + pub fn []( + public_input_size: i32, + vector: &[], + ) -> Vec { + vector.digest(public_input_size) + } + + #[napi] + pub fn []( + public_input_size: i32, + vector: &[], + ) -> Result { + vector.serialize(public_input_size) + } + } + }; +} + +impl_gate_support!(fp, Fp, WasmPastaFp); +impl_gate_support!(fq, Fq, WasmPastaFq); diff --git a/plonk-napi/src/lib.rs b/plonk-napi/src/lib.rs index ce7da054aa..e4643fb3b3 100644 --- a/plonk-napi/src/lib.rs +++ b/plonk-napi/src/lib.rs @@ -1,6 +1,13 @@ -mod poseidon; +pub(crate) mod gate_vector; +pub(crate) mod poly_comm; +pub(crate) mod poseidon; +//pub(crate) mod srs; +pub(crate) mod wasm_vector; +pub(crate) mod wrappers; -pub use poseidon::{ - caml_pasta_fp_poseidon_block_cipher, - caml_pasta_fq_poseidon_block_cipher, -}; +pub use poseidon::{caml_pasta_fp_poseidon_block_cipher, caml_pasta_fq_poseidon_block_cipher}; + +pub use poly_comm::{pallas::WasmFqPolyComm, vesta::WasmFpPolyComm}; +//pub use srs::{fp::WasmFpSrs, fq::WasmFqSrs}; +pub use wasm_vector::{fp::WasmVecVecFp, fq::WasmVecVecFq}; +pub use wrappers::group::{WasmGPallas, WasmGVesta}; diff --git a/plonk-napi/src/poly_comm.rs b/plonk-napi/src/poly_comm.rs new file mode 100644 index 0000000000..ab3a162403 --- /dev/null +++ b/plonk-napi/src/poly_comm.rs @@ -0,0 +1,115 @@ +use crate::{ + wasm_vector::WasmVector, + wrappers::group::{WasmGPallas, WasmGVesta}, +}; +use napi_derive::napi; +use paste::paste; +use poly_commitment::commitment::PolyComm; + +macro_rules! impl_poly_comm { + ( + $wasm_g:ty, + $g:ty, + $field_name:ident + ) => { + paste! { + #[napi] + #[derive(Clone)] + pub struct [] { + #[napi(skip)] + pub unshifted: WasmVector<$wasm_g>, + pub shifted: Option<$wasm_g>, + } + + #[napi] + impl [] { + #[napi(constructor)] + pub fn new(unshifted: WasmVector<$wasm_g>, shifted: Option<$wasm_g>) -> Self { + assert!( + shifted.is_none(), + "mina#14628: Shifted commitments are deprecated and must not be used", + ); + Self { unshifted, shifted } + } + + #[napi(getter)] + pub fn unshifted(&self) -> WasmVector<$wasm_g> { + self.unshifted.clone() + } + + #[napi(setter)] + pub fn set_unshifted(&mut self, value: WasmVector<$wasm_g>) { + self.unshifted = value; + } + } + + impl From> for [] { + fn from(value: PolyComm<$g>) -> Self { + let PolyComm { chunks } = value; + let unshifted: Vec<$wasm_g> = chunks.into_iter().map(Into::into).collect(); + Self { + unshifted: unshifted.into(), + shifted: None, + } + } + } + + impl From<&PolyComm<$g>> for [] { + fn from(value: &PolyComm<$g>) -> Self { + let unshifted: Vec<$wasm_g> = value.chunks.iter().map(|chunk| (*chunk).into()).collect(); + Self { + unshifted: unshifted.into(), + shifted: None, + } + } + } + + impl From<[]> for PolyComm<$g> { + fn from(value: []) -> Self { + let [] { unshifted, shifted } = value; + assert!( + shifted.is_none(), + "mina#14628: Shifted commitments are deprecated and must not be used", + ); + PolyComm { + chunks: Vec::<$wasm_g>::from(unshifted) + .into_iter() + .map(Into::into) + .collect(), + } + } + } + + impl From<&[]> for PolyComm<$g> { + fn from(value: &[]) -> Self { + assert!( + value.shifted.is_none(), + "mina#14628: Shifted commitments are deprecated and must not be used", + ); + PolyComm { + chunks: value + .unshifted + .iter() + .cloned() + .map(Into::into) + .collect(), + } + } + } + } + }; +} + +pub mod pallas { + use super::*; + use mina_curves::pasta::Pallas as GAffine; + + impl_poly_comm!(WasmGPallas, GAffine, Fq); +} + +pub mod vesta { + use super::*; + use mina_curves::pasta::Vesta as GAffine; + + impl_poly_comm!(WasmGVesta, GAffine, Fp); +} diff --git a/plonk-napi/src/poseidon.rs b/plonk-napi/src/poseidon.rs index fc612e21bf..ba96f58bfb 100644 --- a/plonk-napi/src/poseidon.rs +++ b/plonk-napi/src/poseidon.rs @@ -1,4 +1,4 @@ -use arkworks::{WasmPastaFp, WasmPastaFq}; +use crate::wrappers::field::{WasmPastaFp, WasmPastaFq}; use mina_curves::pasta::{Fp, Fq}; use mina_poseidon::{constants::PlonkSpongeConstantsKimchi, permutation::poseidon_block_cipher}; use napi::bindgen_prelude::*; diff --git a/plonk-napi/src/srs.rs b/plonk-napi/src/srs.rs new file mode 100644 index 0000000000..7b39a6f14a --- /dev/null +++ b/plonk-napi/src/srs.rs @@ -0,0 +1,284 @@ +use std::{ + fs::{File, OpenOptions}, + io::{BufReader, BufWriter, Seek, SeekFrom}, + sync::Arc, +}; + +use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, EvaluationDomain, Evaluations}; +use napi::bindgen_prelude::*; +use napi_derive::napi; +use paste::paste; +use poly_commitment::{commitment::b_poly_coefficients, hash_map_cache::HashMapCache, ipa::SRS}; +use wasm_types::FlatVector as WasmFlatVector; + +use crate::{ + poly_comm::{pallas::WasmFqPolyComm, vesta::WasmFpPolyComm}, + wasm_vector::WasmVector, + wrappers::field::{WasmPastaFp, WasmPastaFq}, + wrappers::group::{WasmGPallas, WasmGVesta}, +}; + +macro_rules! impl_srs_module { + ( + $mod_name:ident, + $field_ty:ty, + $wasm_field:ty, + $group_ty:ty, + $group_wrapper:ty, + $poly_comm_wrapper:ty, + $struct_ident:ident + ) => { + pub mod $mod_name { + use super::*; + + #[napi] + #[derive(Clone)] + pub struct $struct_ident { + #[napi(skip)] + pub inner: Arc>, + } + + impl $struct_ident { + fn new(inner: SRS<$group_ty>) -> Self { + Self { + inner: Arc::new(inner), + } + } + + fn from_arc(inner: Arc>) -> Self { + Self { inner } + } + } + + fn invalid_domain_error() -> Error { + Error::new(Status::InvalidArg, "invalid domain size") + } + + fn map_error(context: &str, err: impl std::fmt::Display) -> Error { + Error::new(Status::GenericFailure, format!("{}: {}", context, err)) + } + + #[napi] + impl $struct_ident { + #[napi(factory)] + pub fn create(depth: i32) -> Result { + Ok(Self::from_arc(Arc::new(SRS::<$group_ty>::create(depth as usize)))) + } + + #[napi(factory)] + pub fn create_parallel(depth: i32) -> Result { + Ok(Self::from_arc(Arc::new(SRS::<$group_ty>::create_parallel( + depth as usize, + )))) + } + + #[napi] + pub fn add_lagrange_basis(&self, log2_size: i32) -> Result<()> { + let size = 1usize << (log2_size as usize); + let domain = EvaluationDomain::<$field_ty>::new(size).ok_or_else(invalid_domain_error)?; + self.inner.get_lagrange_basis(domain); + Ok(()) + } + + #[napi] + pub fn write(&self, append: Option, path: String) -> Result<()> { + let file = OpenOptions::new() + .write(true) + .create(true) + .append(append.unwrap_or(true)) + .open(&path) + .map_err(|err| map_error("srs_write", err))?; + let mut writer = BufWriter::new(file); + self.inner + .serialize(&mut rmp_serde::Serializer::new(&mut writer)) + .map_err(|err| map_error("srs_write", err)) + } + + #[napi] + pub fn read(offset: Option, path: String) -> Result> { + let file = match File::open(&path) { + Ok(file) => file, + Err(err) => return Err(map_error("srs_read", err)), + }; + let mut reader = BufReader::new(file); + + if let Some(off) = offset { + reader + .seek(SeekFrom::Start(off as u64)) + .map_err(|err| map_error("srs_read", err))?; + } + + match SRS::<$group_ty>::deserialize(&mut rmp_serde::Deserializer::new(reader)) { + Ok(srs) => Ok(Some(Self::from_arc(Arc::new(srs)))), + Err(_) => Ok(None), + } + } + + #[napi] + pub fn get(&self) -> WasmVector<$group_wrapper> { + let mut points: Vec<$group_wrapper> = vec![self.inner.h.into()]; + points.extend(self.inner.g.iter().cloned().map(Into::into)); + points.into() + } + + #[napi] + pub fn set(points: WasmVector<$group_wrapper>) -> Result { + let mut pts: Vec<$group_ty> = points.into_iter().map(Into::into).collect(); + if pts.is_empty() { + return Err(Error::new( + Status::InvalidArg, + "expected at least one element for SRS", + )); + } + let h = pts.remove(0); + let g = pts; + Ok(Self::from_arc(Arc::new(SRS::<$group_ty> { + h, + g, + lagrange_bases: HashMapCache::new(), + }))) + } + + #[napi] + pub fn maybe_lagrange_commitment( + &self, + domain_size: i32, + index: i32, + ) -> Option<$poly_comm_wrapper> { + if !self + .inner + .lagrange_bases + .contains_key(&(domain_size as usize)) + { + return None; + } + let basis = self + .inner + .get_lagrange_basis_from_domain_size(domain_size as usize); + basis.get(index as usize).cloned().map(Into::into) + } + + #[napi] + pub fn set_lagrange_basis( + &self, + domain_size: i32, + bases: WasmVector<$poly_comm_wrapper>, + ) { + let domain = domain_size as usize; + let commitments: Vec<_> = bases.into_iter().map(Into::into).collect(); + self.inner + .lagrange_bases + .get_or_generate(domain, || commitments.clone()); + } + + #[napi] + pub fn get_lagrange_basis( + &self, + domain_size: i32, + ) -> Result> { + let domain = EvaluationDomain::<$field_ty>::new(domain_size as usize) + .ok_or_else(invalid_domain_error)?; + let basis = self.inner.get_lagrange_basis(domain); + Ok(basis.iter().cloned().map(Into::into).collect()) + } + + #[napi] + pub fn commit_evaluations( + &self, + domain_size: i32, + evaluations: Uint8Array, + ) -> Result<$poly_comm_wrapper> { + let elems: Vec<$field_ty> = WasmFlatVector::<$wasm_field>::from_bytes( + evaluations.as_ref().to_vec(), + ) + .into_iter() + .map(Into::into) + .collect(); + let domain = EvaluationDomain::<$field_ty>::new(domain_size as usize) + .ok_or_else(invalid_domain_error)?; + let evals = Evaluations::from_vec_and_domain(elems, domain); + let poly = evals.interpolate(); + Ok(self.inner.commit(&poly, None).into()) + } + + #[napi] + pub fn b_poly_commitment(&self, chals: Uint8Array) -> Result<$poly_comm_wrapper> { + let elements: Vec<$field_ty> = WasmFlatVector::<$wasm_field>::from_bytes( + chals.as_ref().to_vec(), + ) + .into_iter() + .map(Into::into) + .collect(); + let coeffs = b_poly_coefficients(&elements); + let poly = DensePolynomial::<$field_ty>::from_coefficients_vec(coeffs); + Ok(self.inner.commit_non_hiding(&poly, 1).into()) + } + + #[napi] + pub fn batch_accumulator_check( + &self, + commitments: WasmVector<$group_wrapper>, + chals: Uint8Array, + ) -> Result { + let comms: Vec<$group_ty> = commitments.into_iter().map(Into::into).collect(); + let chals: Vec<$field_ty> = WasmFlatVector::<$wasm_field>::from_bytes( + chals.as_ref().to_vec(), + ) + .into_iter() + .map(Into::into) + .collect(); + Ok(poly_commitment::utils::batch_dlog_accumulator_check( + &self.inner, + &comms, + &chals, + )) + } + + #[napi] + pub fn batch_accumulator_generate( + &self, + count: i32, + chals: Uint8Array, + ) -> Result> { + let chals: Vec<$field_ty> = WasmFlatVector::<$wasm_field>::from_bytes( + chals.as_ref().to_vec(), + ) + .into_iter() + .map(Into::into) + .collect(); + let points = poly_commitment::utils::batch_dlog_accumulator_generate::<$group_ty>( + &self.inner, + count as usize, + &chals, + ); + Ok(points.into_iter().map(Into::into).collect()) + } + + #[napi] + pub fn h(&self) -> $group_wrapper { + self.inner.h.into() + } + } + } + }; +} + +impl_srs_module!( + fp, + mina_curves::pasta::Fp, + WasmPastaFp, + mina_curves::pasta::Vesta, + WasmGVesta, + WasmFpPolyComm, + WasmFpSrs +); + +impl_srs_module!( + fq, + mina_curves::pasta::Fq, + WasmPastaFq, + mina_curves::pasta::Pallas, + WasmGPallas, + WasmFqPolyComm, + WasmFqSrs +); diff --git a/plonk-napi/src/wasm_vector.rs b/plonk-napi/src/wasm_vector.rs new file mode 100644 index 0000000000..d83400a04a --- /dev/null +++ b/plonk-napi/src/wasm_vector.rs @@ -0,0 +1,199 @@ +use std::{iter::FromIterator, ops::Deref}; + +use napi::{bindgen_prelude::*, sys}; +use wasm_types::{FlatVector, FlatVectorElem}; + +#[derive(Clone, Debug, Default)] +pub struct WasmVector(pub Vec); + +impl WasmVector { + pub fn into_inner(self) -> Vec { + self.0 + } +} + +impl Deref for WasmVector { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for WasmVector { + fn from(value: Vec) -> Self { + WasmVector(value) + } +} + +impl From> for Vec { + fn from(value: WasmVector) -> Self { + value.0 + } +} + +impl<'a, T> From<&'a WasmVector> for &'a Vec { + fn from(value: &'a WasmVector) -> Self { + &value.0 + } +} + +impl IntoIterator for WasmVector { + type Item = T; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, T> IntoIterator for &'a WasmVector { + type Item = &'a T; + type IntoIter = <&'a Vec as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl FromIterator for WasmVector { + fn from_iter>(iter: I) -> Self { + WasmVector(Vec::from_iter(iter)) + } +} + +impl Extend for WasmVector { + fn extend>(&mut self, iter: I) { + self.0.extend(iter); + } +} + +impl TypeName for WasmVector +where + Vec: TypeName, +{ + fn type_name() -> &'static str { + as TypeName>::type_name() + } + + fn value_type() -> ValueType { + as TypeName>::value_type() + } +} + +impl ValidateNapiValue for WasmVector +where + Vec: ValidateNapiValue, + T: FromNapiValue, +{ + unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + as ValidateNapiValue>::validate(env, napi_val) + } +} + +impl FromNapiValue for WasmVector +where + Vec: FromNapiValue, +{ + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + Ok(WasmVector( as FromNapiValue>::from_napi_value( + env, napi_val, + )?)) + } +} + +impl ToNapiValue for WasmVector +where + Vec: ToNapiValue, +{ + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + as ToNapiValue>::to_napi_value(env, val.0) + } +} + +macro_rules! impl_vec_vec_fp { + ($name:ident, $field:ty, $wasm_field:ty) => { + #[napi] + #[derive(Clone, Debug, Default)] + pub struct $name(#[napi(skip)] pub Vec>); + + #[napi] + impl $name { + #[napi(constructor)] + pub fn create(capacity: i32) -> Self { + Self(Vec::with_capacity(capacity as usize)) + } + + #[napi] + pub fn push(&mut self, vector: Uint8Array) -> Result<()> { + let flattened = vector.as_ref().to_vec(); + let values = FlatVector::<$wasm_field>::from_bytes(flattened) + .into_iter() + .map(Into::into) + .collect(); + self.0.push(values); + Ok(()) + } + + #[napi] + pub fn get(&self, index: i32) -> Result { + let slice = self.0.get(index as usize).ok_or_else(|| { + Error::new(Status::InvalidArg, "index out of bounds".to_string()) + })?; + + let bytes = slice + .iter() + .cloned() + .map(<$wasm_field>::from) + .flat_map(FlatVectorElem::flatten) + .collect::>(); + + Ok(Uint8Array::from(bytes)) + } + + #[napi] + pub fn set(&mut self, index: i32, vector: Uint8Array) -> Result<()> { + let entry = self.0.get_mut(index as usize).ok_or_else(|| { + Error::new(Status::InvalidArg, "index out of bounds".to_string()) + })?; + + let flattened = vector.as_ref().to_vec(); + *entry = FlatVector::<$wasm_field>::from_bytes(flattened) + .into_iter() + .map(Into::into) + .collect(); + Ok(()) + } + } + + impl From>> for $name { + fn from(value: Vec>) -> Self { + Self(value) + } + } + + impl From<$name> for Vec> { + fn from(value: $name) -> Self { + value.0 + } + } + }; +} + +pub mod fp { + use super::*; + use crate::wrappers::field::WasmPastaFp; + use mina_curves::pasta::Fp; + use napi_derive::napi; + + impl_vec_vec_fp!(WasmVecVecFp, Fp, WasmPastaFp); +} + +pub mod fq { + use super::*; + use crate::wrappers::field::WasmPastaFq; + use mina_curves::pasta::Fq; + use napi_derive::napi; + + impl_vec_vec_fp!(WasmVecVecFq, Fq, WasmPastaFq); +} diff --git a/plonk-napi/src/wrappers/feature_flags.rs b/plonk-napi/src/wrappers/feature_flags.rs new file mode 100644 index 0000000000..64f93895bf --- /dev/null +++ b/plonk-napi/src/wrappers/feature_flags.rs @@ -0,0 +1,63 @@ +use kimchi::circuits::{ + constraints::FeatureFlags as KimchiFeatureFlags, + lookup::lookups::{LookupFeatures, LookupPatterns}, +}; +use napi_derive::napi; + +#[napi(object)] +#[derive(Clone, Copy, Debug, Default)] +pub struct NapiFeatureFlags { + pub range_check0: bool, + pub range_check1: bool, + pub foreign_field_add: bool, + pub foreign_field_mul: bool, + pub xor: bool, + pub rot: bool, + pub lookup: bool, + pub runtime_tables: bool, +} + +impl From for NapiFeatureFlags { + fn from(value: KimchiFeatureFlags) -> Self { + let LookupPatterns { + xor, + lookup, + range_check, + foreign_field_mul, + } = value.lookup_features.patterns; + + Self { + range_check0: value.range_check0, + range_check1: value.range_check1, + foreign_field_add: value.foreign_field_add, + foreign_field_mul: value.foreign_field_mul, + xor: value.xor, + rot: value.rot, + lookup: lookup || range_check || foreign_field_mul || xor, + runtime_tables: value.lookup_features.uses_runtime_tables, + } + } +} + +impl From for KimchiFeatureFlags { + fn from(value: NapiFeatureFlags) -> Self { + KimchiFeatureFlags { + range_check0: value.range_check0, + range_check1: value.range_check1, + foreign_field_add: value.foreign_field_add, + foreign_field_mul: value.foreign_field_mul, + xor: value.xor, + rot: value.rot, + lookup_features: LookupFeatures { + patterns: LookupPatterns { + xor: value.lookup && value.xor, + lookup: value.lookup, + range_check: value.lookup && (value.range_check0 || value.range_check1), + foreign_field_mul: value.lookup && value.foreign_field_mul, + }, + joint_lookup_used: value.lookup, + uses_runtime_tables: value.runtime_tables, + }, + } + } +} diff --git a/plonk-napi/src/wrappers/field.rs b/plonk-napi/src/wrappers/field.rs new file mode 100644 index 0000000000..d1b0d35e0b --- /dev/null +++ b/plonk-napi/src/wrappers/field.rs @@ -0,0 +1,99 @@ +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use mina_curves::pasta::{Fp, Fq}; +use napi::bindgen_prelude::*; +use wasm_types::FlatVectorElem; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct WasmPastaFp(pub Fp); + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct WasmPastaFq(pub Fq); + +macro_rules! impl_field_wrapper { + ($name:ident, $field:ty) => { + impl $name { + fn serialize(&self) -> Vec { + let mut bytes = Vec::with_capacity(core::mem::size_of::<$field>()); + self.0 + .serialize_compressed(&mut bytes) + .expect("serialization failure"); + bytes + } + + fn deserialize(bytes: &[u8]) -> Self { + let value = + <$field>::deserialize_compressed(bytes).expect("deserialization failure"); + Self(value) + } + } + + impl From<$field> for $name { + fn from(value: $field) -> Self { + Self(value) + } + } + + impl From<$name> for $field { + fn from(value: $name) -> Self { + value.0 + } + } + + impl<'a> From<&'a $name> for &'a $field { + fn from(value: &'a $name) -> Self { + &value.0 + } + } + + impl FlatVectorElem for $name { + const FLATTENED_SIZE: usize = core::mem::size_of::<$field>(); + + fn flatten(self) -> Vec { + self.serialize() + } + + fn unflatten(flat: Vec) -> Self { + Self::deserialize(&flat) + } + } + + impl TypeName for $name { + fn type_name() -> &'static str { + ::type_name() + } + + fn value_type() -> ValueType { + ::value_type() + } + } + + impl ValidateNapiValue for $name { + unsafe fn validate( + env: sys::napi_env, + napi_val: sys::napi_value, + ) -> Result { + ::validate(env, napi_val) + } + } + + impl FromNapiValue for $name { + unsafe fn from_napi_value( + env: sys::napi_env, + napi_val: sys::napi_value, + ) -> Result { + let buffer = ::from_napi_value(env, napi_val)?; + Ok(Self::deserialize(buffer.as_ref())) + } + } + + impl ToNapiValue for $name { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + let buffer = Buffer::from(val.serialize()); + ::to_napi_value(env, buffer) + } + } + }; +} + +impl_field_wrapper!(WasmPastaFp, Fp); +impl_field_wrapper!(WasmPastaFq, Fq); diff --git a/plonk-napi/src/wrappers/group.rs b/plonk-napi/src/wrappers/group.rs new file mode 100644 index 0000000000..602d92e715 --- /dev/null +++ b/plonk-napi/src/wrappers/group.rs @@ -0,0 +1,123 @@ +use crate::wrappers::field::{WasmPastaFp, WasmPastaFq}; +use mina_curves::pasta::{ + curves::{ + pallas::{G_GENERATOR_X as GeneratorPallasX, G_GENERATOR_Y as GeneratorPallasY}, + vesta::{G_GENERATOR_X as GeneratorVestaX, G_GENERATOR_Y as GeneratorVestaY}, + }, + Pallas as AffinePallas, Vesta as AffineVesta, +}; +use napi_derive::napi; + +#[napi(object)] +#[derive(Clone, Debug)] +pub struct WasmGPallas { + pub x: WasmPastaFp, + pub y: WasmPastaFp, + pub infinity: bool, +} + +#[napi(object)] +#[derive(Clone, Debug)] +pub struct WasmGVesta { + pub x: WasmPastaFq, + pub y: WasmPastaFq, + pub infinity: bool, +} + +impl From for WasmGPallas { + fn from(point: AffinePallas) -> Self { + Self { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +impl From<&AffinePallas> for WasmGPallas { + fn from(point: &AffinePallas) -> Self { + Self { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +impl From for AffinePallas { + fn from(point: WasmGPallas) -> Self { + AffinePallas { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +impl From<&WasmGPallas> for AffinePallas { + fn from(point: &WasmGPallas) -> Self { + AffinePallas { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +impl From for WasmGVesta { + fn from(point: AffineVesta) -> Self { + Self { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +impl From<&AffineVesta> for WasmGVesta { + fn from(point: &AffineVesta) -> Self { + Self { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +impl From for AffineVesta { + fn from(point: WasmGVesta) -> Self { + AffineVesta { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +impl From<&WasmGVesta> for AffineVesta { + fn from(point: &WasmGVesta) -> Self { + AffineVesta { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +#[napi] +pub fn caml_pallas_affine_one() -> WasmGPallas { + WasmGPallas { + x: WasmPastaFp::from(GeneratorPallasX), + y: WasmPastaFp::from(GeneratorPallasY), + infinity: false, + } +} + +#[napi] +pub fn caml_vesta_affine_one() -> WasmGVesta { + WasmGVesta { + x: WasmPastaFq::from(GeneratorVestaX), + y: WasmPastaFq::from(GeneratorVestaY), + infinity: false, + } +} diff --git a/plonk-napi/src/wrappers/lookups.rs b/plonk-napi/src/wrappers/lookups.rs new file mode 100644 index 0000000000..cd9b7a4fbe --- /dev/null +++ b/plonk-napi/src/wrappers/lookups.rs @@ -0,0 +1,296 @@ +use kimchi::circuits::lookup::{ + lookups::{ + LookupFeatures as KimchiLookupFeatures, LookupInfo as KimchiLookupInfo, + LookupPatterns as KimchiLookupPatterns, + }, + runtime_tables::{ + RuntimeTable as KimchiRuntimeTable, RuntimeTableCfg as KimchiRuntimeTableCfg, + }, + tables::LookupTable as KimchiLookupTable, +}; +use mina_curves::pasta::{Fp, Fq}; +use napi::bindgen_prelude::*; +use napi_derive::napi; +use paste::paste; +use wasm_types::{FlatVector, FlatVectorElem}; + +use crate::{ + wasm_vector::{fp::WasmVecVecFp, fq::WasmVecVecFq}, + wrappers::field::{WasmPastaFp, WasmPastaFq}, +}; + +// ----------------- +// Lookup pattern and info wrappers +// ----------------- + +#[napi(object)] +#[derive(Clone, Copy, Debug, Default)] +pub struct NapiLookupPatterns { + pub xor: bool, + pub lookup: bool, + pub range_check: bool, + pub foreign_field_mul: bool, +} + +impl From for NapiLookupPatterns { + fn from(value: KimchiLookupPatterns) -> Self { + Self { + xor: value.xor, + lookup: value.lookup, + range_check: value.range_check, + foreign_field_mul: value.foreign_field_mul, + } + } +} + +impl From for KimchiLookupPatterns { + fn from(value: NapiLookupPatterns) -> Self { + KimchiLookupPatterns { + xor: value.xor, + lookup: value.lookup, + range_check: value.range_check, + foreign_field_mul: value.foreign_field_mul, + } + } +} + +#[napi(object)] +#[derive(Clone, Debug, Default)] +pub struct NapiLookupFeatures { + pub patterns: NapiLookupPatterns, + pub joint_lookup_used: bool, + pub uses_runtime_tables: bool, +} + +impl From for NapiLookupFeatures { + fn from(value: KimchiLookupFeatures) -> Self { + Self { + patterns: value.patterns.into(), + joint_lookup_used: value.joint_lookup_used, + uses_runtime_tables: value.uses_runtime_tables, + } + } +} + +impl From for KimchiLookupFeatures { + fn from(value: NapiLookupFeatures) -> Self { + KimchiLookupFeatures { + patterns: value.patterns.into(), + joint_lookup_used: value.joint_lookup_used, + uses_runtime_tables: value.uses_runtime_tables, + } + } +} + +#[napi(object)] +#[derive(Clone, Debug, Default)] +pub struct NapiLookupInfo { + pub max_per_row: u32, + pub max_joint_size: u32, + pub features: NapiLookupFeatures, +} + +impl From for NapiLookupInfo { + fn from(value: KimchiLookupInfo) -> Self { + Self { + max_per_row: value.max_per_row as u32, + max_joint_size: value.max_joint_size as u32, + features: value.features.into(), + } + } +} + +impl From for KimchiLookupInfo { + fn from(value: NapiLookupInfo) -> Self { + KimchiLookupInfo { + max_per_row: value.max_per_row as usize, + max_joint_size: value.max_joint_size, + features: value.features.into(), + } + } +} + +// ----------------- +// Lookup tables & runtime tables +// ----------------- + +macro_rules! impl_lookup_wrappers { + ($name:ident, $field:ty, $wasm_field:ty, $vec_vec:ty) => { + paste! { + #[napi] + #[derive(Clone)] + pub struct [] { + id: i32, + data: $vec_vec, + } + + #[napi] + impl [] { + #[napi(constructor)] + pub fn new(id: i32, data: External<$vec_vec>) -> Self { + Self { + id, + data: data.as_ref().clone(), + } + } + + #[napi(getter)] + pub fn id(&self) -> i32 { + self.id + } + + #[napi(setter)] + pub fn set_id(&mut self, id: i32) { + self.id = id; + } + + #[napi(getter)] + pub fn data(&self) -> External<$vec_vec> { + External::new(self.data.clone()) + } + + #[napi(setter)] + pub fn set_data(&mut self, data: External<$vec_vec>) { + self.data = data.as_ref().clone(); + } + } + + impl From> for [] { + fn from(value: KimchiLookupTable<$field>) -> Self { + Self { + id: value.id, + data: value.data.into(), + } + } + } + + impl From<[]> for KimchiLookupTable<$field> { + fn from(value: []) -> Self { + Self { + id: value.id, + data: value.data.into(), + } + } + } + + #[napi] + #[derive(Clone)] + pub struct [] { + id: i32, + first_column: Vec<$field>, + } + + #[napi] + impl [] { + #[napi(constructor)] + pub fn new(id: i32, first_column: Uint8Array) -> Result { + let bytes = first_column.as_ref().to_vec(); + let elements: Vec<$field> = FlatVector::<$wasm_field>::from_bytes(bytes) + .into_iter() + .map(Into::into) + .collect(); + Ok(Self { id, first_column: elements }) + } + + #[napi(getter)] + pub fn id(&self) -> i32 { + self.id + } + + #[napi(setter)] + pub fn set_id(&mut self, id: i32) { + self.id = id; + } + + #[napi(getter)] + pub fn first_column(&self) -> Result { + let mut bytes = Vec::with_capacity(self.first_column.len() * <$wasm_field>::FLATTENED_SIZE); + for value in &self.first_column { + let element = <$wasm_field>::from(*value); + bytes.extend(element.flatten()); + } + Ok(Uint8Array::from(bytes)) + } + } + + impl From> for [] { + fn from(value: KimchiRuntimeTableCfg<$field>) -> Self { + Self { + id: value.id, + first_column: value.first_column, + } + } + } + + impl From<[]> for KimchiRuntimeTableCfg<$field> { + fn from(value: []) -> Self { + Self { + id: value.id, + first_column: value.first_column, + } + } + } + + #[napi] + #[derive(Clone)] + pub struct [] { + id: i32, + data: Vec<$field>, + } + + #[napi] + impl [] { + #[napi(constructor)] + pub fn new(id: i32, data: Uint8Array) -> Result { + let bytes = data.as_ref().to_vec(); + let elements: Vec<$field> = FlatVector::<$wasm_field>::from_bytes(bytes) + .into_iter() + .map(Into::into) + .collect(); + Ok(Self { id, data: elements }) + } + + #[napi(getter)] + pub fn id(&self) -> i32 { + self.id + } + + #[napi(setter)] + pub fn set_id(&mut self, id: i32) { + self.id = id; + } + + #[napi(getter)] + pub fn data(&self) -> Result { + let mut bytes = Vec::with_capacity(self.data.len() * <$wasm_field>::FLATTENED_SIZE); + for value in &self.data { + let element = <$wasm_field>::from(*value); + bytes.extend(element.flatten()); + } + Ok(Uint8Array::from(bytes)) + } + } + + impl From> for [] { + fn from(value: KimchiRuntimeTable<$field>) -> Self { + Self { + id: value.id, + data: value.data, + } + } + } + + impl From<[]> for KimchiRuntimeTable<$field> { + fn from(value: []) -> Self { + Self { + id: value.id, + data: value.data, + } + } + } + } + }; +} + +impl_lookup_wrappers!(Fp, Fp, WasmPastaFp, WasmVecVecFp); +impl_lookup_wrappers!(Fq, Fq, WasmPastaFq, WasmVecVecFq); diff --git a/plonk-napi/src/wrappers/mod.rs b/plonk-napi/src/wrappers/mod.rs new file mode 100644 index 0000000000..36a2ce209a --- /dev/null +++ b/plonk-napi/src/wrappers/mod.rs @@ -0,0 +1,5 @@ +pub(crate) mod feature_flags; +pub(crate) mod field; +pub(crate) mod group; +pub(crate) mod lookups; +pub(crate) mod wires; diff --git a/plonk-napi/src/wrappers/wires.rs b/plonk-napi/src/wrappers/wires.rs new file mode 100644 index 0000000000..9a2f67ea11 --- /dev/null +++ b/plonk-napi/src/wrappers/wires.rs @@ -0,0 +1,27 @@ +use kimchi::circuits::wires::Wire as KimchiWire; +use napi_derive::napi; + +#[napi(object)] +#[derive(Clone, Copy, Debug, Default)] +pub struct NapiWire { + pub row: u32, + pub col: u32, +} + +impl From for KimchiWire { + fn from(value: NapiWire) -> Self { + KimchiWire { + row: value.row as usize, + col: value.col as usize, + } + } +} + +impl From for NapiWire { + fn from(value: KimchiWire) -> Self { + Self { + row: value.row as u32, + col: value.col as u32, + } + } +}