diff --git a/plonk-napi/src/lib.rs b/plonk-napi/src/lib.rs index aeb83cfbab5..74c704a9c8a 100644 --- a/plonk-napi/src/lib.rs +++ b/plonk-napi/src/lib.rs @@ -1,12 +1,25 @@ mod build_info; mod circuit; -pub mod plonk_verifier_index; +mod plonk_verifier_index; +mod poly_comm; mod poseidon; mod prover_index; mod types; - -pub use poseidon::{caml_pasta_fp_poseidon_block_cipher, caml_pasta_fq_poseidon_block_cipher}; +mod vector; +mod wrappers; pub use circuit::prover_to_json; +pub use plonk_verifier_index::{ + caml_pasta_fp_plonk_verifier_index_shifts, caml_pasta_fq_plonk_verifier_index_shifts, +}; +pub use poly_comm::{ + pallas::NapiFqPolyComm as WasmFqPolyComm, vesta::NapiFpPolyComm as WasmFpPolyComm, +}; +pub use poseidon::{caml_pasta_fp_poseidon_block_cipher, caml_pasta_fq_poseidon_block_cipher}; pub use prover_index::{prover_index_from_bytes, prover_index_to_bytes}; pub use types::WasmPastaFpPlonkIndex; +pub use vector::{fp::NapiVecVecFp as WasmVecVecFp, fq::NapiVecVecFq as WasmVecVecFq}; +pub use wrappers::{ + field::{NapiPastaFp as WasmPastaFp, NapiPastaFq as WasmPastaFq}, + group::{NapiGPallas as WasmGPallas, NapiGVesta as WasmGVesta}, +}; diff --git a/plonk-napi/src/poly_comm.rs b/plonk-napi/src/poly_comm.rs new file mode 100644 index 00000000000..58d4bdeff20 --- /dev/null +++ b/plonk-napi/src/poly_comm.rs @@ -0,0 +1,138 @@ +use crate::vector::NapiVector; +use napi::bindgen_prelude::{ClassInstance, FromNapiValue}; +use napi_derive::napi; +use paste::paste; +use poly_commitment::commitment::PolyComm; +use serde::{Deserialize, Serialize}; + +macro_rules! impl_poly_comm { + ( + $NapiG:ty, + $g:ty, + $field_name:ident + ) => { + paste! { + #[napi(js_name = [<"Wasm" $field_name "PolyComm">])] + #[derive(Clone, Debug, Serialize, Deserialize, Default)] + pub struct [] { + #[napi(skip)] + pub unshifted: NapiVector<$NapiG>, + #[napi(skip)] + pub shifted: Option<$NapiG>, + } + + #[napi] + impl [] { + #[napi(constructor)] + pub fn new(unshifted: NapiVector<$NapiG>, shifted: Option<$NapiG>) -> 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) -> NapiVector<$NapiG> { + self.unshifted.clone() + } + + #[napi(setter, js_name = "set_unshifted")] + pub fn set_unshifted(&mut self, x: NapiVector<$NapiG>) { + self.unshifted = x; + } + + #[napi(getter)] + pub fn shifted(&self) -> Option<$NapiG> { + self.shifted.clone() + } + + #[napi(setter, js_name = "set_shifted")] + pub fn set_shifted(&mut self, value: Option<$NapiG>) { + self.shifted = value; + } + } + + impl From> for [] { + fn from(x: PolyComm<$g>) -> Self { + let PolyComm { chunks } = x; + let unshifted: Vec<$NapiG> = chunks.into_iter().map(Into::into).collect(); + Self { + unshifted: unshifted.into(), + shifted: None, + } + } + } + + impl From<&PolyComm<$g>> for [] { + fn from(x: &PolyComm<$g>) -> Self { + let unshifted: Vec<$NapiG> = x.chunks.iter().map(|chunk| (*chunk).into()).collect(); + Self { + unshifted: unshifted.into(), + shifted: None, + } + } + } + + impl From<[]> for PolyComm<$g> { + fn from(x: []) -> Self { + let [] { unshifted, shifted } = x; + assert!( + shifted.is_none(), + "mina#14628: Shifted commitments are deprecated and must not be used", + ); + PolyComm { + chunks: Vec::<$NapiG>::from(unshifted) + .into_iter() + .map(Into::into) + .collect(), + } + } + } + + impl From<&[]> for PolyComm<$g> { + fn from(x: &[]) -> Self { + assert!( + x.shifted.is_none(), + "mina#14628: Shifted commitments are deprecated and must not be used", + ); + PolyComm { + chunks: x + .unshifted + .iter() + .cloned() + .map(Into::into) + .collect(), + } + } + } + + impl FromNapiValue for [] { + unsafe fn from_napi_value( + env: napi::sys::napi_env, + napi_val: napi::sys::napi_value, + ) -> napi::Result { + let instance = ]> as FromNapiValue>::from_napi_value(env, napi_val)?; + Ok((*instance).clone()) + } + } + + } + }; +} + +pub mod pallas { + use super::*; + use crate::wrappers::group::NapiGPallas; + use mina_curves::pasta::Pallas; + + impl_poly_comm!(NapiGPallas, Pallas, Fq); +} + +pub mod vesta { + use super::*; + use crate::wrappers::group::NapiGVesta; + use mina_curves::pasta::Vesta; + + impl_poly_comm!(NapiGVesta, Vesta, Fp); +} diff --git a/plonk-napi/src/poseidon.rs b/plonk-napi/src/poseidon.rs index 13a64ab86a2..cc81d93de8c 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::{NapiPastaFp, NapiPastaFq}; use mina_curves::pasta::{Fp, Fq}; use mina_poseidon::{constants::PlonkSpongeConstantsKimchi, permutation::poseidon_block_cipher}; use napi::bindgen_prelude::*; @@ -15,7 +15,7 @@ pub fn caml_pasta_fp_poseidon_block_cipher(state: Uint8Array) -> Result = FlatVector::::from_bytes(state.to_vec()) + let mut state_vec: Vec = FlatVector::::from_bytes(state.to_vec()) .into_iter() .map(Into::into) .collect(); @@ -27,7 +27,7 @@ pub fn caml_pasta_fp_poseidon_block_cipher(state: Uint8Array) -> Result = state_vec .into_iter() - .map(WasmPastaFp) + .map(NapiPastaFp) .flat_map(FlatVectorElem::flatten) .collect(); @@ -42,7 +42,7 @@ pub fn caml_pasta_fq_poseidon_block_cipher(state: Uint8Array) -> Result = FlatVector::::from_bytes(state.to_vec()) + let mut state_vec: Vec = FlatVector::::from_bytes(state.to_vec()) .into_iter() .map(Into::into) .collect(); @@ -54,7 +54,7 @@ pub fn caml_pasta_fq_poseidon_block_cipher(state: Uint8Array) -> Result = state_vec .into_iter() - .map(WasmPastaFq) + .map(NapiPastaFq) .flat_map(FlatVectorElem::flatten) .collect(); diff --git a/plonk-napi/src/vector.rs b/plonk-napi/src/vector.rs new file mode 100644 index 00000000000..23cf87cbb8a --- /dev/null +++ b/plonk-napi/src/vector.rs @@ -0,0 +1,221 @@ +use napi::{bindgen_prelude::*, sys}; +use serde::{Deserialize, Serialize}; +use std::{iter::FromIterator, ops::Deref}; +use wasm_types::{FlatVector, FlatVectorElem}; + +#[derive(Clone, Debug, Serialize, Deserialize, Default)] +pub struct NapiVector(pub Vec); + +impl NapiVector { + pub fn into_inner(self) -> Vec { + self.0 + } +} + +impl Deref for NapiVector { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for NapiVector { + fn from(value: Vec) -> Self { + NapiVector(value) + } +} + +impl From> for Vec { + fn from(value: NapiVector) -> Self { + value.0 + } +} + +impl<'a, T> From<&'a NapiVector> for &'a Vec { + fn from(value: &'a NapiVector) -> Self { + &value.0 + } +} + +impl IntoIterator for NapiVector { + type Item = T; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, T> IntoIterator for &'a NapiVector { + type Item = &'a T; + type IntoIter = <&'a Vec as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl FromIterator for NapiVector { + fn from_iter>(iter: I) -> Self { + NapiVector(Vec::from_iter(iter)) + } +} + +impl Extend for NapiVector { + fn extend>(&mut self, iter: I) { + self.0.extend(iter); + } +} + +impl TypeName for NapiVector +where + Vec: TypeName, +{ + fn type_name() -> &'static str { + as TypeName>::type_name() + } + + fn value_type() -> ValueType { + as TypeName>::value_type() + } +} + +impl ValidateNapiValue for NapiVector +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 NapiVector +where + Vec: FromNapiValue, +{ + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + Ok(NapiVector( as FromNapiValue>::from_napi_value( + env, napi_val, + )?)) + } +} + +impl ToNapiValue for NapiVector +where + Vec: ToNapiValue, +{ + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + as ToNapiValue>::to_napi_value(env, val.0) + } +} + +impl<'a, T> ToNapiValue for &'a NapiVector +where + Vec: ToNapiValue, + T: Clone, +{ + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + let cloned: Vec = val.0.clone(); + as ToNapiValue>::to_napi_value(env, cloned) + } +} + +impl<'a, T> ToNapiValue for &'a mut NapiVector +where + Vec: ToNapiValue, + T: Clone, +{ + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + let cloned: Vec = val.0.clone(); + as ToNapiValue>::to_napi_value(env, cloned) + } +} + +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::NapiPastaFp; + use mina_curves::pasta::Fp; + use napi_derive::napi; + + impl_vec_vec_fp!(NapiVecVecFp, Fp, NapiPastaFp); +} + +pub mod fq { + use super::*; + use crate::wrappers::field::NapiPastaFq; + use mina_curves::pasta::Fq; + use napi_derive::napi; + + impl_vec_vec_fp!(NapiVecVecFq, Fq, NapiPastaFq); +} diff --git a/plonk-napi/src/wrappers/field.rs b/plonk-napi/src/wrappers/field.rs new file mode 100644 index 00000000000..022636f39d1 --- /dev/null +++ b/plonk-napi/src/wrappers/field.rs @@ -0,0 +1,120 @@ +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use mina_curves::pasta::{Fp, Fq}; +use napi::bindgen_prelude::*; +use serde::{Deserialize, Serialize}; +use wasm_types::FlatVectorElem; + +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] +pub struct NapiPastaFp(pub Fp); + +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] +pub struct NapiPastaFq(pub Fq); + +macro_rules! impl_field_wrapper { + ($name:ident, $field:ty) => { + impl $name { + fn from_bytes(bytes: &[u8]) -> Self { + let value = + <$field>::deserialize_compressed(bytes).expect("deserialization failure"); + Self(value) + } + + fn to_bytes(&self) -> Vec { + let mut bytes = Vec::with_capacity(core::mem::size_of::<$field>()); + self.0 + .serialize_compressed(&mut bytes) + .expect("serialization failure"); + bytes + } + } + + impl Serialize for $name { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + serializer.serialize_bytes(&self.to_bytes()) + } + } + impl<'de> Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + let bytes: Vec = Vec::::deserialize(deserializer)?; + <$field>::deserialize_compressed(bytes.as_slice()) + .map(Self) + .map_err(serde::de::Error::custom) + } + } + + 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.to_bytes() + } + + fn unflatten(flat: Vec) -> Self { + Self::from_bytes(&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::from_bytes(buffer.as_ref())) + } + } + + impl ToNapiValue for $name { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + let buffer = Buffer::from(val.to_bytes()); + ::to_napi_value(env, buffer) + } + } + }; +} + +impl_field_wrapper!(NapiPastaFp, Fp); +impl_field_wrapper!(NapiPastaFq, Fq); diff --git a/plonk-napi/src/wrappers/group.rs b/plonk-napi/src/wrappers/group.rs new file mode 100644 index 00000000000..cf192b5c0cc --- /dev/null +++ b/plonk-napi/src/wrappers/group.rs @@ -0,0 +1,124 @@ +use crate::wrappers::field::{NapiPastaFp, NapiPastaFq}; +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; +use serde::{Deserialize, Serialize}; + +#[napi(object, js_name = "WasmGPallas")] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct NapiGPallas { + pub x: NapiPastaFp, + pub y: NapiPastaFp, + pub infinity: bool, +} + +#[napi(object, js_name = "WasmGVesta")] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct NapiGVesta { + pub x: NapiPastaFq, + pub y: NapiPastaFq, + pub infinity: bool, +} + +impl From for NapiGPallas { + fn from(point: AffinePallas) -> Self { + Self { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +impl From<&AffinePallas> for NapiGPallas { + fn from(point: &AffinePallas) -> Self { + Self { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +impl From for AffinePallas { + fn from(point: NapiGPallas) -> Self { + AffinePallas { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +impl From<&NapiGPallas> for AffinePallas { + fn from(point: &NapiGPallas) -> Self { + AffinePallas { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +impl From for NapiGVesta { + fn from(point: AffineVesta) -> Self { + Self { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +impl From<&AffineVesta> for NapiGVesta { + fn from(point: &AffineVesta) -> Self { + Self { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +impl From for AffineVesta { + fn from(point: NapiGVesta) -> Self { + AffineVesta { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +impl From<&NapiGVesta> for AffineVesta { + fn from(point: &NapiGVesta) -> Self { + AffineVesta { + x: point.x.into(), + y: point.y.into(), + infinity: point.infinity, + } + } +} + +#[napi(js_name = "caml_pallas_affine_one")] +pub fn caml_pallas_affine_one() -> NapiGPallas { + NapiGPallas { + x: NapiPastaFp::from(GeneratorPallasX), + y: NapiPastaFp::from(GeneratorPallasY), + infinity: false, + } +} + +#[napi(js_name = "caml_vesta_affine_one")] +pub fn caml_vesta_affine_one() -> NapiGVesta { + NapiGVesta { + x: NapiPastaFq::from(GeneratorVestaX), + y: NapiPastaFq::from(GeneratorVestaY), + infinity: false, + } +} diff --git a/plonk-napi/src/wrappers/mod.rs b/plonk-napi/src/wrappers/mod.rs new file mode 100644 index 00000000000..15443ca3d87 --- /dev/null +++ b/plonk-napi/src/wrappers/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod field; +pub(crate) mod group; \ No newline at end of file