From dd3bd042195522d32392256af869d9b41350fad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Tue, 30 Sep 2025 20:12:10 +0200 Subject: [PATCH 1/4] napi: wrapper for field types --- plonk-napi/src/lib.rs | 6 +- plonk-napi/src/poseidon.rs | 2 +- plonk-napi/src/wrappers/field.rs | 99 ++++++++++++++++++++++++++++++++ plonk-napi/src/wrappers/mod.rs | 1 + 4 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 plonk-napi/src/wrappers/field.rs create mode 100644 plonk-napi/src/wrappers/mod.rs diff --git a/plonk-napi/src/lib.rs b/plonk-napi/src/lib.rs index ce7da054aa..fd3eca2d8b 100644 --- a/plonk-napi/src/lib.rs +++ b/plonk-napi/src/lib.rs @@ -1,6 +1,10 @@ -mod poseidon; +pub(crate) mod poseidon; +pub(crate) mod wrappers; pub use poseidon::{ caml_pasta_fp_poseidon_block_cipher, caml_pasta_fq_poseidon_block_cipher, }; + +pub use wrappers::field::{WasmPastaFp, WasmPastaFq}; + diff --git a/plonk-napi/src/poseidon.rs b/plonk-napi/src/poseidon.rs index dc7c9f108d..4f910b5b54 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/wrappers/field.rs b/plonk-napi/src/wrappers/field.rs new file mode 100644 index 0000000000..8f396ab97e --- /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); \ No newline at end of file diff --git a/plonk-napi/src/wrappers/mod.rs b/plonk-napi/src/wrappers/mod.rs new file mode 100644 index 0000000000..70f3d34a62 --- /dev/null +++ b/plonk-napi/src/wrappers/mod.rs @@ -0,0 +1 @@ +pub(crate) mod field; \ No newline at end of file From a64a1ab66c95e4e58fc2382c0140ca1b0273bb78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Tue, 30 Sep 2025 20:14:53 +0200 Subject: [PATCH 2/4] napi: wrapper for group types --- plonk-napi/src/lib.rs | 2 +- plonk-napi/src/wrappers/group.rs | 123 +++++++++++++++++++++++++++++++ plonk-napi/src/wrappers/mod.rs | 3 +- 3 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 plonk-napi/src/wrappers/group.rs diff --git a/plonk-napi/src/lib.rs b/plonk-napi/src/lib.rs index fd3eca2d8b..36a4b8d752 100644 --- a/plonk-napi/src/lib.rs +++ b/plonk-napi/src/lib.rs @@ -7,4 +7,4 @@ pub use poseidon::{ }; pub use wrappers::field::{WasmPastaFp, WasmPastaFq}; - +pub use wrappers::group::{WasmGPallas, WasmGVesta}; \ No newline at end of file diff --git a/plonk-napi/src/wrappers/group.rs b/plonk-napi/src/wrappers/group.rs new file mode 100644 index 0000000000..be8cbff4a0 --- /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, + } +} \ No newline at end of file diff --git a/plonk-napi/src/wrappers/mod.rs b/plonk-napi/src/wrappers/mod.rs index 70f3d34a62..15443ca3d8 100644 --- a/plonk-napi/src/wrappers/mod.rs +++ b/plonk-napi/src/wrappers/mod.rs @@ -1 +1,2 @@ -pub(crate) mod field; \ No newline at end of file +pub(crate) mod field; +pub(crate) mod group; \ No newline at end of file From b9363f49299638f4f0cf476a3f2eab001ee507c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Tue, 30 Sep 2025 20:18:59 +0200 Subject: [PATCH 3/4] napi: impls for wasm vectors --- plonk-napi/src/lib.rs | 4 +- plonk-napi/src/wasm_vector.rs | 199 ++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 plonk-napi/src/wasm_vector.rs diff --git a/plonk-napi/src/lib.rs b/plonk-napi/src/lib.rs index 36a4b8d752..2d1d169b87 100644 --- a/plonk-napi/src/lib.rs +++ b/plonk-napi/src/lib.rs @@ -1,5 +1,6 @@ pub(crate) mod poseidon; pub(crate) mod wrappers; +pub(crate) mod wasm_vector; pub use poseidon::{ caml_pasta_fp_poseidon_block_cipher, @@ -7,4 +8,5 @@ pub use poseidon::{ }; pub use wrappers::field::{WasmPastaFp, WasmPastaFq}; -pub use wrappers::group::{WasmGPallas, WasmGVesta}; \ No newline at end of file +pub use wrappers::group::{WasmGPallas, WasmGVesta}; +pub use wasm_vector::{fp::WasmVecVecFp, fq::WasmVecVecFq}; \ No newline at end of file diff --git a/plonk-napi/src/wasm_vector.rs b/plonk-napi/src/wasm_vector.rs new file mode 100644 index 0000000000..278b408a21 --- /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); +} \ No newline at end of file From e91340875d14df76d44faef823c24d570625f984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Wed, 1 Oct 2025 13:24:13 +0200 Subject: [PATCH 4/4] napi: impls for polycomm --- Cargo.lock | 1 + plonk-napi/Cargo.toml | 1 + plonk-napi/src/lib.rs | 5 +- plonk-napi/src/poly_comm.rs | 115 ++++++++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 plonk-napi/src/poly_comm.rs diff --git a/Cargo.lock b/Cargo.lock index cccd069127..462fb6eab4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2944,6 +2944,7 @@ dependencies = [ "o1-utils", "once_cell", "paste", + "poly-commitment", "rand", "rayon", "rmp-serde", diff --git a/plonk-napi/Cargo.toml b/plonk-napi/Cargo.toml index 87dfbbd0da..199795e561 100644 --- a/plonk-napi/Cargo.toml +++ b/plonk-napi/Cargo.toml @@ -26,6 +26,7 @@ arkworks.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 diff --git a/plonk-napi/src/lib.rs b/plonk-napi/src/lib.rs index 2d1d169b87..2bcede7c7c 100644 --- a/plonk-napi/src/lib.rs +++ b/plonk-napi/src/lib.rs @@ -1,12 +1,13 @@ pub(crate) mod poseidon; pub(crate) mod wrappers; pub(crate) mod wasm_vector; +pub(crate) mod poly_comm; pub use poseidon::{ caml_pasta_fp_poseidon_block_cipher, caml_pasta_fq_poseidon_block_cipher, }; -pub use wrappers::field::{WasmPastaFp, WasmPastaFq}; pub use wrappers::group::{WasmGPallas, WasmGVesta}; -pub use wasm_vector::{fp::WasmVecVecFp, fq::WasmVecVecFq}; \ No newline at end of file +pub use wasm_vector::{fp::WasmVecVecFp, fq::WasmVecVecFq}; +pub use poly_comm::{pallas::WasmFqPolyComm, vesta::WasmFpPolyComm}; \ No newline at end of file diff --git a/plonk-napi/src/poly_comm.rs b/plonk-napi/src/poly_comm.rs new file mode 100644 index 0000000000..b90e9df978 --- /dev/null +++ b/plonk-napi/src/poly_comm.rs @@ -0,0 +1,115 @@ +use crate::{ + wrappers::group::{WasmGPallas, WasmGVesta}, + wasm_vector::WasmVector, +}; +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); +}