From 1efb16bcec5a8d66b29bee2505d7bab0693ce04e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Tue, 30 Sep 2025 17:07:09 +0200 Subject: [PATCH 1/7] scripts: update build to account for napi --- .../scripts/build-o1js-node-artifacts.sh | 17 +++++++++++++++++ src/build/copy-to-dist.js | 1 + 2 files changed, 18 insertions(+) diff --git a/src/bindings/scripts/build-o1js-node-artifacts.sh b/src/bindings/scripts/build-o1js-node-artifacts.sh index 045a8d9922..4c2698afe3 100755 --- a/src/bindings/scripts/build-o1js-node-artifacts.sh +++ b/src/bindings/scripts/build-o1js-node-artifacts.sh @@ -51,6 +51,10 @@ run_cmd cp "${MINA_PATH}"/src/config.mlh "src" run_cmd cp -r "${MINA_PATH}"/src/config "src/config" ok "Mina config files copied" +info "Building Kimchi native bindings for Node.js..." +run_cmd dune b "${KIMCHI_BINDINGS}"/js/native +ok "Kimchi native bindings built" + info "Building Kimchi bindings for Node.js..." run_cmd dune b "${KIMCHI_BINDINGS}"/js/node_js ok "Kimchi bindings built" @@ -96,6 +100,14 @@ run_cmd mkdir -p "${BINDINGS_PATH}" run_cmd chmod -R 777 "${BINDINGS_PATH}" ok "Output directory prepared" +info "Preparing native bindings directory..." +run_cmd mkdir -p src/bindings/compiled/native +ok "Native bindings directory prepared" + +info "Copying N-API bindings..." +run_cmd cp _build/default/"${KIMCHI_BINDINGS}"/js/native/plonk_napi* "${BINDINGS_PATH}" +ok "N-API bindings copied" + info "Copying WASM bindings..." run_cmd cp _build/default/"${KIMCHI_BINDINGS}"/js/node_js/plonk_wasm* "${BINDINGS_PATH}" run_cmd mv -f "${BINDINGS_PATH}"/plonk_wasm.js "${BINDINGS_PATH}"/plonk_wasm.cjs @@ -114,6 +126,11 @@ fi run_cmd mv -f "${BINDINGS_PATH}"/o1js_node.bc.js "${BINDINGS_PATH}"/o1js_node.bc.cjs ok "Node.js bindings copied" +info "Copying native bindings..." +run_cmd cp _build/default/"${KIMCHI_BINDINGS}"/js/native/plonk_napi.node src/bindings/compiled/native/ +run_cmd chmod 777 src/bindings/compiled/native/plonk_napi.node +ok "Native bindings copied" + info "Updating WASM references in bindings..." run_cmd sed -i 's/plonk_wasm.js/plonk_wasm.cjs/' "${BINDINGS_PATH}"/o1js_node.bc.cjs ok "WASM references updated" diff --git a/src/build/copy-to-dist.js b/src/build/copy-to-dist.js index d2786c8582..6a3db7a1bd 100644 --- a/src/build/copy-to-dist.js +++ b/src/build/copy-to-dist.js @@ -5,6 +5,7 @@ await copyFromTo( [ 'src/bindings.d.ts', 'src/bindings/compiled/_node_bindings', + 'src/bindings/compiled/native', 'src/bindings/compiled/node_bindings/plonk_wasm.d.cts', ], 'src/', From 093414a9cfa51bcd415ad5fd4adbd8bb76e9de0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Tue, 30 Sep 2025 17:07:20 +0200 Subject: [PATCH 2/7] submodule: update mina --- src/mina | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mina b/src/mina index 569e99c958..e986a9eb0d 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 569e99c95834841d64858644c5d80ac7e63d0157 +Subproject commit e986a9eb0da4cce0eeda2b61624f91f290fd7502 From 2f3d2c2ff65d106d5eb50e092d38e86888c6ea93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Thu, 2 Oct 2025 16:41:46 +0200 Subject: [PATCH 3/7] bindings: initialize native conversion --- src/bindings/crypto/bindings.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/bindings/crypto/bindings.ts b/src/bindings/crypto/bindings.ts index 8e9fcb35ff..f98c8dcc6f 100644 --- a/src/bindings/crypto/bindings.ts +++ b/src/bindings/crypto/bindings.ts @@ -17,7 +17,7 @@ import { oraclesConversion } from './bindings/conversion-oracles.js'; import { jsEnvironment } from './bindings/env.js'; import { srs } from './bindings/srs.js'; -export { getRustConversion, RustConversion, Wasm }; +export { getRustConversion, RustConversion, Wasm, createNativeRustConversion }; const tsBindings = { jsEnvironment, @@ -31,6 +31,7 @@ const tsBindings = { ...FpVectorBindings, ...FqVectorBindings, rustConversion: createRustConversion, + nativeRustConversion: createNativeRustConversion, srs: (wasm: Wasm) => srs(wasm, getRustConversion(wasm)), }; @@ -39,7 +40,15 @@ const tsBindings = { type Wasm = typeof wasmNamespace; +function createNativeRustConversion(wasm: Wasm) { + return buildConversion(wasm); +} + function createRustConversion(wasm: Wasm) { + return buildConversion(wasm); +} + +function buildConversion(wasm: Wasm) { let core = conversionCore(wasm); let verifierIndex = verifierIndexConversion(wasm, core); let oracles = oraclesConversion(wasm); @@ -55,7 +64,7 @@ function createRustConversion(wasm: Wasm) { }; } -type RustConversion = ReturnType; +type RustConversion = ReturnType; let rustConversion: RustConversion | undefined; From 4bd05f9a465f94866ed68d9a0e096b6bc685e7b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Thu, 2 Oct 2025 16:43:00 +0200 Subject: [PATCH 4/7] submodule: update mina --- src/mina | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mina b/src/mina index e986a9eb0d..977fe190be 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit e986a9eb0da4cce0eeda2b61624f91f290fd7502 +Subproject commit 977fe190be271ab03dd8d83a94c76aff2b5b9c48 From 9938faf129eab9be762dc2bfb39bf352f5476d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Tue, 7 Oct 2025 19:55:26 +0200 Subject: [PATCH 5/7] bindings: conversion bundle for rust between wasm and native --- src/bindings/crypto/bindings.ts | 78 ++++++++++++++----- src/bindings/crypto/native/conversion-core.ts | 11 +++ 2 files changed, 71 insertions(+), 18 deletions(-) create mode 100644 src/bindings/crypto/native/conversion-core.ts diff --git a/src/bindings/crypto/bindings.ts b/src/bindings/crypto/bindings.ts index f98c8dcc6f..e752ab9baf 100644 --- a/src/bindings/crypto/bindings.ts +++ b/src/bindings/crypto/bindings.ts @@ -16,8 +16,15 @@ import { verifierIndexConversion } from './bindings/conversion-verifier-index.js import { oraclesConversion } from './bindings/conversion-oracles.js'; import { jsEnvironment } from './bindings/env.js'; import { srs } from './bindings/srs.js'; +// native +import { conversionCore as conversionCoreNative } from './native/conversion-core.js'; +import { fieldsFromRustFlat as fieldsFromRustFlatNative, fieldsToRustFlat as fieldsToRustFlatNative } from './native/conversion-base.js'; +import { proofConversion as proofConversionNative } from './native/conversion-proof.js'; +import { verifierIndexConversion as verifierIndexConversionNative } from './native/conversion-verifier-index.js'; +import { oraclesConversion as oraclesConversionNative } from './native/conversion-oracles.js'; +import { srs as srsNative } from './native/srs.js'; -export { getRustConversion, RustConversion, Wasm, createNativeRustConversion }; +export { getRustConversion, type RustConversion, type NativeConversion, type Wasm }; const tsBindings = { jsEnvironment, @@ -31,8 +38,10 @@ const tsBindings = { ...FpVectorBindings, ...FqVectorBindings, rustConversion: createRustConversion, - nativeRustConversion: createNativeRustConversion, - srs: (wasm: Wasm) => srs(wasm, getRustConversion(wasm)), + srs: (wasm: Wasm) => { + const bundle = getConversionBundle(wasm); + return bundle.srsFactory(wasm, bundle.conversion); + }, }; // this is put in a global variable so that mina/src/lib/crypto/kimchi_bindings/js/bindings.js finds it @@ -40,19 +49,17 @@ const tsBindings = { type Wasm = typeof wasmNamespace; -function createNativeRustConversion(wasm: Wasm) { - return buildConversion(wasm); +function createRustConversion(wasm: Wasm): RustConversion { + return shouldUseNativeConversion(wasm) + ? createNativeConversion(wasm) + : createWasmConversion(wasm); } -function createRustConversion(wasm: Wasm) { - return buildConversion(wasm); -} - -function buildConversion(wasm: Wasm) { - let core = conversionCore(wasm); - let verifierIndex = verifierIndexConversion(wasm, core); - let oracles = oraclesConversion(wasm); - let proof = proofConversion(wasm, core); +function createWasmConversion(wasm: Wasm) { + const core = conversionCore(wasm); + const verifierIndex = verifierIndexConversion(wasm, core); + const oracles = oraclesConversion(wasm); + const proof = proofConversion(wasm, core); return { fp: { ...core.fp, ...verifierIndex.fp, ...oracles.fp, ...proof.fp }, @@ -64,10 +71,45 @@ function buildConversion(wasm: Wasm) { }; } -type RustConversion = ReturnType; +type WasmConversion = ReturnType; +type NativeConversion = ReturnType; +type RustConversion = WasmConversion | NativeConversion; + +function getRustConversion(wasm: Wasm): RustConversion { + return createRustConversion(wasm); +} + +function createNativeConversion(wasm: Wasm) { + const core = conversionCoreNative(wasm); + const verifierIndex = verifierIndexConversionNative(wasm, core); + const oracles = oraclesConversionNative(wasm); + const proof = proofConversionNative(wasm, core); + + return { + fp: { ...core.fp, ...verifierIndex.fp, ...oracles.fp, ...proof.fp }, + fq: { ...core.fq, ...verifierIndex.fq, ...oracles.fq, ...proof.fq }, + fieldsToRustFlatNative, + fieldsFromRustFlatNative, + wireToRust: core.wireToRust, + mapMlArrayToRustVector: core.mapMlArrayToRustVector, + }; +} + +function shouldUseNativeConversion(wasm: Wasm): boolean { + const marker = (wasm as any).__kimchi_use_native; + const globalMarker = + typeof globalThis !== 'undefined' && + (globalThis as any).__kimchi_use_native; + return Boolean(marker || globalMarker); +} -let rustConversion: RustConversion | undefined; +type ConversionBundle = + | { conversion: WasmConversion; srsFactory: typeof srs } + | { conversion: NativeConversion; srsFactory: typeof srsNative }; -function getRustConversion(wasm: Wasm) { - return rustConversion ?? (rustConversion = createRustConversion(wasm)); +function getConversionBundle(wasm: Wasm): ConversionBundle { + if (shouldUseNativeConversion(wasm)) { + return { conversion: createNativeConversion(wasm), srsFactory: srsNative }; + } + return { conversion: createWasmConversion(wasm), srsFactory: srs }; } diff --git a/src/bindings/crypto/native/conversion-core.ts b/src/bindings/crypto/native/conversion-core.ts new file mode 100644 index 0000000000..ca0815376c --- /dev/null +++ b/src/bindings/crypto/native/conversion-core.ts @@ -0,0 +1,11 @@ +import type * as wasmNamespace from '../../compiled/node_bindings/plonk_wasm.cjs'; + +import { conversionCore as conversionCoreWasm, ConversionCores } from '../bindings/conversion-core.js'; + +export type NativeConversionCores = ConversionCores; + +type Wasm = typeof wasmNamespace; + +export function conversionCore(wasm: Wasm): NativeConversionCores { + return conversionCoreWasm(wasm); +} From 82d1f68b4087b7fb702e55f8aa3cd1203752600b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Wed, 8 Oct 2025 13:10:08 +0200 Subject: [PATCH 6/7] conversion: first attempt of napi-friendly core --- src/bindings/crypto/native/conversion-core.ts | 148 +++++++++++++++++- 1 file changed, 142 insertions(+), 6 deletions(-) diff --git a/src/bindings/crypto/native/conversion-core.ts b/src/bindings/crypto/native/conversion-core.ts index ca0815376c..d034d802d3 100644 --- a/src/bindings/crypto/native/conversion-core.ts +++ b/src/bindings/crypto/native/conversion-core.ts @@ -1,11 +1,147 @@ -import type * as wasmNamespace from '../../compiled/node_bindings/plonk_wasm.cjs'; +import type * as napiNamespace from '../../compiled/node_bindings/plonk_wasm.cjs'; +import { MlArray } from '../../../lib/ml/base.js'; +import { OrInfinity, Gate, PolyComm, Wire } from './kimchi-types.js'; +import { + WasmAffine as NapiAffine, + affineFromRust, + affineToRust, + fieldsFromRustFlat, + fieldsToRustFlat, +} from './conversion-base.js'; +import { mapTuple } from './util.js'; -import { conversionCore as conversionCoreWasm, ConversionCores } from '../bindings/conversion-core.js'; -export type NativeConversionCores = ConversionCores; +type Napi = typeof napiNamespace; -type Wasm = typeof wasmNamespace; +type NapiPolyComm = napiNamespace.WasmFpPolyComm | napiNamespace.WasmFqPolyComm; -export function conversionCore(wasm: Wasm): NativeConversionCores { - return conversionCoreWasm(wasm); +type NapiClasses = { + makeAffine: () => NapiAffine; + PolyComm: typeof napiNamespace.WasmFpPolyComm | typeof napiNamespace.WasmFqPolyComm; +}; + +export function conversionCore(napi: Napi) { + const fp = conversionCorePerField({ + makeAffine: napi.caml_vesta_affine_one, + PolyComm: napi.WasmFpPolyComm, + }); + const fq = conversionCorePerField({ + makeAffine: napi.caml_pallas_affine_one, + PolyComm: napi.WasmFqPolyComm, + }); + + return { + fp, + fq, + wireToRust: fp.wireToRust, // doesn't depend on the field + mapMlArrayToRustVector( + [, ...array]: MlArray, + map: (x: TMl) => TRust + ): TRust[] { + return array.map(map); + }, + }; +} + +function conversionCorePerField({ makeAffine, PolyComm: PolyCommClass }: NapiClasses) { + const self = { + wireToRust([, row, col]: Wire) { + return { row, col }; + }, + + vectorToRust: fieldsToRustFlat, + vectorFromRust: fieldsFromRustFlat, + + gateToRust(gate: Gate): any { + const [, typ, [, ...wires], coeffs] = gate; + const mapped = mapTuple(wires, self.wireToRust); + const nativeWires = { + w0: mapped[0], + w1: mapped[1], + w2: mapped[2], + w3: mapped[3], + w4: mapped[4], + w5: mapped[5], + w6: mapped[6], + } as const; + return { + typ, + wires: nativeWires, + coeffs: toBuffer(fieldsToRustFlat(coeffs)), + }; + }, + gateFromRust(nativeGate: any): Gate { + const { typ, wires, coeffs } = nativeGate; + const mlWires: Gate[2] = [ + 0, + [0, wires.w0.row, wires.w0.col], + [0, wires.w1.row, wires.w1.col], + [0, wires.w2.row, wires.w2.col], + [0, wires.w3.row, wires.w3.col], + [0, wires.w4.row, wires.w4.col], + [0, wires.w5.row, wires.w5.col], + [0, wires.w6.row, wires.w6.col], + ]; + const mlCoeffs = fieldsFromRustFlat(toUint8Array(coeffs)); + return [0, typ, mlWires, mlCoeffs]; + }, + + pointToRust(point: OrInfinity) { + return affineToRust(point, makeAffine); + }, + pointFromRust(point: any): OrInfinity { + return affineFromRust(point); + }, + + pointsToRust([, ...points]: MlArray): any[] { + return points.map(self.pointToRust); + }, + pointsFromRust(points: unknown): MlArray { + return [0, ...asArray(points, 'pointsFromRust').map(self.pointFromRust)]; + }, + + polyCommToRust(polyComm: PolyComm): NapiPolyComm { + const [, camlElems] = polyComm; + const rustUnshifted = self.pointsToRust(camlElems); + return new PolyCommClass(rustUnshifted, undefined); + }, + polyCommFromRust(polyComm: NapiPolyComm): PolyComm { + const rustUnshifted = asArray((polyComm as any).unshifted, 'polyComm.unshifted'); + const mlUnshifted = rustUnshifted.map(self.pointFromRust); + return [0, [0, ...mlUnshifted]]; + }, + + polyCommsToRust([, ...comms]: MlArray): NapiPolyComm[] { + return comms.map(self.polyCommToRust); + }, + polyCommsFromRust(rustComms: NapiPolyComm[]): MlArray { + return [0, ...asArray(rustComms, 'polyCommsFromRust').map(self.polyCommFromRust)]; + }, + }; + + return self; +} + +function asArray(value: unknown, context: string): T[] { + if (value == null) return []; + if (Array.isArray(value)) return value as T[]; + throw Error(`${context}: expected array of native values`); +} + +function toUint8Array(value: any): Uint8Array { + if (value instanceof Uint8Array) return value; + if (Array.isArray(value)) return Uint8Array.from(value); + if (value && typeof value === 'object') { + if (ArrayBuffer.isView(value)) { + const view = value as ArrayBufferView; + return new Uint8Array(view.buffer, view.byteOffset, view.byteLength); + } + if (value instanceof ArrayBuffer) return new Uint8Array(value); + } + throw Error('Expected byte array'); +} + +function toBuffer(bytes: Uint8Array): Buffer { + if (Buffer.isBuffer(bytes)) return bytes; + return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength); } From 43078a9464982d2a12ad9c51fc6e6f35f4cc6d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Wed, 8 Oct 2025 13:54:49 +0200 Subject: [PATCH 7/7] conversion: use unknown and any for now --- src/bindings/crypto/native/conversion-core.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/bindings/crypto/native/conversion-core.ts b/src/bindings/crypto/native/conversion-core.ts index d034d802d3..e0ba8e95b9 100644 --- a/src/bindings/crypto/native/conversion-core.ts +++ b/src/bindings/crypto/native/conversion-core.ts @@ -1,3 +1,4 @@ +import { Buffer } from 'buffer'; import type * as napiNamespace from '../../compiled/node_bindings/plonk_wasm.cjs'; import { MlArray } from '../../../lib/ml/base.js'; import { OrInfinity, Gate, PolyComm, Wire } from './kimchi-types.js'; @@ -93,20 +94,21 @@ function conversionCorePerField({ makeAffine, PolyComm: PolyCommClass }: NapiCla return affineFromRust(point); }, - pointsToRust([, ...points]: MlArray): any[] { + pointsToRust([, ...points]: MlArray): NapiAffine[] { return points.map(self.pointToRust); }, pointsFromRust(points: unknown): MlArray { - return [0, ...asArray(points, 'pointsFromRust').map(self.pointFromRust)]; + const list = asArray(points, 'pointsFromRust'); + return [0, ...list.map(self.pointFromRust)]; }, polyCommToRust(polyComm: PolyComm): NapiPolyComm { const [, camlElems] = polyComm; const rustUnshifted = self.pointsToRust(camlElems); - return new PolyCommClass(rustUnshifted, undefined); + return new PolyCommClass(rustUnshifted as any, undefined as any); }, polyCommFromRust(polyComm: NapiPolyComm): PolyComm { - const rustUnshifted = asArray((polyComm as any).unshifted, 'polyComm.unshifted'); + const rustUnshifted = asArray((polyComm as any).unshifted, 'polyComm.unshifted'); const mlUnshifted = rustUnshifted.map(self.pointFromRust); return [0, [0, ...mlUnshifted]]; }, @@ -114,8 +116,9 @@ function conversionCorePerField({ makeAffine, PolyComm: PolyCommClass }: NapiCla polyCommsToRust([, ...comms]: MlArray): NapiPolyComm[] { return comms.map(self.polyCommToRust); }, - polyCommsFromRust(rustComms: NapiPolyComm[]): MlArray { - return [0, ...asArray(rustComms, 'polyCommsFromRust').map(self.polyCommFromRust)]; + polyCommsFromRust(rustComms: unknown): MlArray { + const list = asArray(rustComms, 'polyCommsFromRust'); + return [0, ...list.map((comm) => self.polyCommFromRust(comm))]; }, };