diff --git a/Cargo.lock b/Cargo.lock index 462fb6eab4..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", @@ -2949,6 +2950,7 @@ dependencies = [ "rayon", "rmp-serde", "serde", + "serde_json", "serde_with", "wasm-types", ] diff --git a/plonk-napi/Cargo.toml b/plonk-napi/Cargo.toml index 199795e561..21d4471017 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" } +kimchi = { path = "../kimchi" } poly-commitment = { path = "../poly-commitment" } getrandom.workspace = true @@ -37,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 2bcede7c7c..7925f04231 100644 --- a/plonk-napi/src/lib.rs +++ b/plonk-napi/src/lib.rs @@ -1,7 +1,8 @@ +pub(crate) mod gate_vector; +pub(crate) mod poly_comm; pub(crate) mod poseidon; -pub(crate) mod wrappers; pub(crate) mod wasm_vector; -pub(crate) mod poly_comm; +pub(crate) mod wrappers; pub use poseidon::{ caml_pasta_fp_poseidon_block_cipher, @@ -10,4 +11,4 @@ pub use poseidon::{ pub use wrappers::group::{WasmGPallas, WasmGVesta}; pub use wasm_vector::{fp::WasmVecVecFp, fq::WasmVecVecFq}; -pub use poly_comm::{pallas::WasmFqPolyComm, vesta::WasmFpPolyComm}; \ No newline at end of file +pub use poly_comm::{pallas::WasmFqPolyComm, vesta::WasmFpPolyComm}; diff --git a/plonk-napi/src/poly_comm.rs b/plonk-napi/src/poly_comm.rs index b90e9df978..ab3a162403 100644 --- a/plonk-napi/src/poly_comm.rs +++ b/plonk-napi/src/poly_comm.rs @@ -1,6 +1,6 @@ use crate::{ - wrappers::group::{WasmGPallas, WasmGVesta}, wasm_vector::WasmVector, + wrappers::group::{WasmGPallas, WasmGVesta}, }; use napi_derive::napi; use paste::paste; diff --git a/plonk-napi/src/wasm_vector.rs b/plonk-napi/src/wasm_vector.rs index 278b408a21..d83400a04a 100644 --- a/plonk-napi/src/wasm_vector.rs +++ b/plonk-napi/src/wasm_vector.rs @@ -196,4 +196,4 @@ pub mod fq { use napi_derive::napi; impl_vec_vec_fp!(WasmVecVecFq, Fq, WasmPastaFq); -} \ No newline at end of file +} 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 index 8f396ab97e..d1b0d35e0b 100644 --- a/plonk-napi/src/wrappers/field.rs +++ b/plonk-napi/src/wrappers/field.rs @@ -96,4 +96,4 @@ macro_rules! impl_field_wrapper { } impl_field_wrapper!(WasmPastaFp, Fp); -impl_field_wrapper!(WasmPastaFq, Fq); \ No newline at end of file +impl_field_wrapper!(WasmPastaFq, Fq); diff --git a/plonk-napi/src/wrappers/group.rs b/plonk-napi/src/wrappers/group.rs index be8cbff4a0..602d92e715 100644 --- a/plonk-napi/src/wrappers/group.rs +++ b/plonk-napi/src/wrappers/group.rs @@ -120,4 +120,4 @@ pub fn caml_vesta_affine_one() -> WasmGVesta { y: WasmPastaFq::from(GeneratorVestaY), infinity: false, } -} \ No newline at end of file +} 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 index 15443ca3d8..36a2ce209a 100644 --- a/plonk-napi/src/wrappers/mod.rs +++ b/plonk-napi/src/wrappers/mod.rs @@ -1,2 +1,5 @@ +pub(crate) mod feature_flags; pub(crate) mod field; -pub(crate) mod group; \ No newline at end of file +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, + } + } +}