From 53fef4ffe076cb7f6f10c957fba066453d2fa04b Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 12 Nov 2025 15:09:21 +0100 Subject: [PATCH 01/11] conversions: attempt proof conversions --- src/bindings/crypto/napi-conversion-proof.ts | 176 ++++++++++++++++++- 1 file changed, 173 insertions(+), 3 deletions(-) diff --git a/src/bindings/crypto/napi-conversion-proof.ts b/src/bindings/crypto/napi-conversion-proof.ts index 830ad61f8b..b09f17fa20 100644 --- a/src/bindings/crypto/napi-conversion-proof.ts +++ b/src/bindings/crypto/napi-conversion-proof.ts @@ -7,29 +7,62 @@ import type { WasmPastaFqRuntimeTableCfg, WasmVecVecFp, WasmVecVecFq, + WasmFpProverCommitments, + WasmFqProverCommitments, + WasmFpOpeningProof, + WasmFqOpeningProof, + WasmFpProverProof, + WasmFqProverProof, + WasmFpLookupCommitments, + WasmFqLookupCommitments, } from '../compiled/node_bindings/plonk_wasm.cjs'; import type * as napiNamespace from '../compiled/node_bindings/plonk_wasm.cjs'; import type { RuntimeTable, RuntimeTableCfg, LookupTable, + ProofWithPublic, + ProverProof, + LookupCommitments, + PolyComm, PointEvaluations, ProofEvaluations, + RecursionChallenge, + ProverCommitments, + OpeningProof, + OrInfinity, } from './bindings/kimchi-types.js'; -import { MlArray} from '../../lib/ml/base.js'; +import { MlArray, MlTuple, MlOption} from '../../lib/ml/base.js'; import { fieldsToRustFlat, + fieldToRust, + fieldsFromRustFlat, + fieldFromRust, } from './bindings/conversion-base.js'; import { ConversionCore, ConversionCores } from './napi-conversion-core.js'; export { napiProofConversion }; + +type NapiProofEvaluations = [ + 0, + MlOption>, + ...RemoveLeadingZero>, +]; type napi = typeof napiNamespace; +type NapiProverCommitments = WasmFpProverCommitments | WasmFqProverCommitments; +type NapiOpeningProof = WasmFpOpeningProof | WasmFqOpeningProof; +type NapiProverProof = WasmFpProverProof | WasmFqProverProof; +type NapiLookupCommitments = WasmFpLookupCommitments | WasmFqLookupCommitments; type NapiRuntimeTable = WasmFpRuntimeTable | WasmFqRuntimeTable; type NapiRuntimeTableCfg = WasmPastaFpRuntimeTableCfg | WasmPastaFqRuntimeTableCfg; type NapiLookupTable = WasmPastaFpLookupTable | WasmPastaFqLookupTable; type NapiClasses = { + ProverCommitments: typeof WasmFpProverCommitments | typeof WasmFqProverCommitments; + OpeningProof: typeof WasmFpOpeningProof | typeof WasmFqOpeningProof; VecVec: typeof WasmVecVecFp | typeof WasmVecVecFq; + ProverProof: typeof WasmFpProverProof | typeof WasmFqProverProof; + LookupCommitments: typeof WasmFpLookupCommitments | typeof WasmFqLookupCommitments; RuntimeTable: typeof WasmFpRuntimeTable | typeof WasmFqRuntimeTable; RuntimeTableCfg: typeof WasmPastaFpRuntimeTableCfg | typeof WasmPastaFqRuntimeTableCfg; LookupTable: typeof WasmPastaFpLookupTable | typeof WasmPastaFqLookupTable; @@ -38,13 +71,21 @@ type NapiClasses = { function napiProofConversion(napi: napi, core: ConversionCores) { return { fp: proofConversionPerField(core.fp, { + ProverCommitments: napi.WasmFpProverCommitments, + OpeningProof: napi.WasmFpOpeningProof, VecVec: napi.WasmVecVecFp, + ProverProof: napi.WasmFpProverProof, + LookupCommitments: napi.WasmFpLookupCommitments, RuntimeTable: napi.WasmFpRuntimeTable, RuntimeTableCfg: napi.WasmPastaFpRuntimeTableCfg, LookupTable: napi.WasmPastaFpLookupTable, }), fq: proofConversionPerField(core.fq, { + ProverCommitments: napi.WasmFqProverCommitments, + OpeningProof: napi.WasmFqOpeningProof, VecVec: napi.WasmVecVecFq, + ProverProof: napi.WasmFqProverProof, + LookupCommitments: napi.WasmFqLookupCommitments, RuntimeTable: napi.WasmFqRuntimeTable, RuntimeTableCfg: napi.WasmPastaFqRuntimeTableCfg, LookupTable: napi.WasmPastaFqLookupTable, @@ -55,13 +96,78 @@ function napiProofConversion(napi: napi, core: ConversionCores) { function proofConversionPerField( core: ConversionCore, { + ProverCommitments, + OpeningProof, VecVec, + ProverProof, + LookupCommitments, RuntimeTable, RuntimeTableCfg, LookupTable, }: NapiClasses ) { - + function commitmentsToRust(commitments: ProverCommitments): NapiProverCommitments { + let wComm = core.polyCommsToRust(commitments[1]); + let zComm = core.polyCommToRust(commitments[2]); + let tComm = core.polyCommToRust(commitments[3]); + let lookup = MlOption.mapFrom(commitments[4], lookupCommitmentsToRust); + return new ProverCommitments(wComm, zComm, tComm, lookup); + } + function commitmentsFromRust(commitments: NapiProverCommitments): ProverCommitments { + let wComm = core.polyCommsFromRust(commitments.w_comm); + let zComm = core.polyCommFromRust(commitments.z_comm); + let tComm = core.polyCommFromRust(commitments.t_comm); + let lookup = MlOption.mapTo(commitments.lookup, lookupCommitmentsFromRust); + commitments.free(); + return [0, wComm as MlTuple, zComm, tComm, lookup]; + } + + function lookupCommitmentsToRust(lookup: LookupCommitments): NapiLookupCommitments { + let sorted = core.polyCommsToRust(lookup[1]); + let aggreg = core.polyCommToRust(lookup[2]); + let runtime = MlOption.mapFrom(lookup[3], core.polyCommToRust); + return new LookupCommitments(sorted, aggreg, runtime); + } + function lookupCommitmentsFromRust(lookup: NapiLookupCommitments): LookupCommitments { + let sorted = core.polyCommsFromRust(lookup.sorted); + let aggreg = core.polyCommFromRust(lookup.aggreg); + let runtime = MlOption.mapTo(lookup.runtime, core.polyCommFromRust); + lookup.free(); + return [0, sorted, aggreg, runtime]; + } + + function openingProofToRust(proof: OpeningProof): NapiOpeningProof { + let [_, [, ...lr], delta, z1, z2, sg] = proof; + // We pass l and r as separate vectors over the FFI + let l: MlArray = [0]; + let r: MlArray = [0]; + for (let [, li, ri] of lr) { + l.push(li); + r.push(ri); + } + return new OpeningProof( + core.pointsToRust(l), + core.pointsToRust(r), + core.pointToRust(delta), + fieldToRust(z1), + fieldToRust(z2), + core.pointToRust(sg) + ); + } + function openingProofFromRust(proof: NapiOpeningProof): OpeningProof { + let [, ...l] = core.pointsFromRust(proof.lr_0); + let [, ...r] = core.pointsFromRust(proof.lr_1); + let n = l.length; + if (n !== r.length) throw Error('openingProofFromRust: l and r length mismatch.'); + let lr = l.map<[0, OrInfinity, OrInfinity]>((li, i) => [0, li, r[i]]); + let delta = core.pointFromRust(proof.delta); + let z1 = fieldFromRust(proof.z1); + let z2 = fieldFromRust(proof.z2); + let sg = core.pointFromRust(proof.sg); + proof.free(); + return [0, [0, ...lr], delta, z1, z2, sg]; + } + function runtimeTableToRust([, id, data]: RuntimeTable): NapiRuntimeTable { return new RuntimeTable(id, core.vectorToRust(data)); } @@ -78,8 +184,68 @@ function proofConversionPerField( } return new LookupTable(id, wasmData); } + + return { + proofToRust([, public_evals, proof]: ProofWithPublic): NapiProverProof { + let commitments = commitmentsToRust(proof[1]); + let openingProof = openingProofToRust(proof[2]); + let [, ...evals] = proofEvaluationsToRust(proof[3]); + let publicEvals = pointEvalsOptionToRust(public_evals); + // TODO typed as `any` in wasm-bindgen, this has the correct type + let evalsActual: NapiProofEvaluations = [0, publicEvals, ...evals]; + + let ftEval1 = fieldToRust(proof[4]); + let public_ = fieldsToRustFlat(proof[5]); + let [, ...prevChallenges] = proof[6]; + let n = prevChallenges.length; + let prevChallengeScalars = new VecVec(n); + let prevChallengeCommsMl: MlArray = [0]; + for (let [, scalars, comms] of prevChallenges) { + prevChallengeScalars.push(fieldsToRustFlat(scalars)); + prevChallengeCommsMl.push(comms); + } + let prevChallengeComms = core.polyCommsToRust(prevChallengeCommsMl); + return new ProverProof( + commitments, + openingProof, + evalsActual, + ftEval1, + public_, + prevChallengeScalars, + prevChallengeComms + ); + }, + proofFromRust(wasmProof: NapiProverProof): ProofWithPublic { + let commitments = commitmentsFromRust(wasmProof.commitments); + let openingProof = openingProofFromRust(wasmProof.proof); + // TODO typed as `any` in wasm-bindgen, this is the correct type + let [, wasmPublicEvals, ...wasmEvals]: NapiProofEvaluations = wasmProof.evals; + let publicEvals = pointEvalsOptionFromRust(wasmPublicEvals); + let evals = proofEvaluationsFromRust([0, ...wasmEvals]); + + let ftEval1 = fieldFromRust(wasmProof.ft_eval1); + let public_ = fieldsFromRustFlat(wasmProof.public_); + let prevChallengeScalars = wasmProof.prev_challenges_scalars; + let [, ...prevChallengeComms] = core.polyCommsFromRust(wasmProof.prev_challenges_comms); + let prevChallenges = prevChallengeComms.map((comms, i) => { + let scalars = fieldsFromRustFlat(prevChallengeScalars.get(i)); + return [0, scalars, comms]; + }); + wasmProof.free(); + let proof: ProverProof = [ + 0, + commitments, + openingProof, + evals, + ftEval1, + public_, + [0, ...prevChallenges], + ]; + return [0, publicEvals, proof]; + }, + runtimeTablesToRust([, ...tables]: MlArray): NapiRuntimeTable[] { return tables.map(runtimeTableToRust); }, @@ -92,4 +258,8 @@ function proofConversionPerField( return tables.map(lookupTableToRust); }, }; -} \ No newline at end of file +} + +// helper + +type RemoveLeadingZero = T extends [0, ...infer U] ? U : never; From f4528083226a6d878da9856d537dc7a71f9b6b58 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 13 Nov 2025 12:45:32 +0100 Subject: [PATCH 02/11] bindings: remove types conversions for proof --- src/bindings/crypto/napi-conversion-proof.ts | 194 +------------------ 1 file changed, 6 insertions(+), 188 deletions(-) diff --git a/src/bindings/crypto/napi-conversion-proof.ts b/src/bindings/crypto/napi-conversion-proof.ts index b09f17fa20..adf08d7a10 100644 --- a/src/bindings/crypto/napi-conversion-proof.ts +++ b/src/bindings/crypto/napi-conversion-proof.ts @@ -1,68 +1,29 @@ import type { - WasmPastaFpLookupTable, WasmFpRuntimeTable, WasmPastaFpRuntimeTableCfg, - WasmPastaFqLookupTable, + WasmPastaFpLookupTable, WasmFqRuntimeTable, WasmPastaFqRuntimeTableCfg, + WasmPastaFqLookupTable, WasmVecVecFp, WasmVecVecFq, - WasmFpProverCommitments, - WasmFqProverCommitments, - WasmFpOpeningProof, - WasmFqOpeningProof, - WasmFpProverProof, - WasmFqProverProof, - WasmFpLookupCommitments, - WasmFqLookupCommitments, } from '../compiled/node_bindings/plonk_wasm.cjs'; import type * as napiNamespace from '../compiled/node_bindings/plonk_wasm.cjs'; -import type { - RuntimeTable, - RuntimeTableCfg, - LookupTable, - ProofWithPublic, - ProverProof, - LookupCommitments, - PolyComm, PointEvaluations, ProofEvaluations, - RecursionChallenge, - ProverCommitments, - OpeningProof, - OrInfinity, -} from './bindings/kimchi-types.js'; -import { MlArray, MlTuple, MlOption} from '../../lib/ml/base.js'; -import { - fieldsToRustFlat, - fieldToRust, - fieldsFromRustFlat, - fieldFromRust, -} from './bindings/conversion-base.js'; +import type { RuntimeTable, RuntimeTableCfg, LookupTable } from './bindings/kimchi-types.js'; +import { MlArray } from '../../lib/ml/base.js'; +import { fieldsToRustFlat } from './bindings/conversion-base.js'; import { ConversionCore, ConversionCores } from './napi-conversion-core.js'; export { napiProofConversion }; - -type NapiProofEvaluations = [ - 0, - MlOption>, - ...RemoveLeadingZero>, -]; type napi = typeof napiNamespace; -type NapiProverCommitments = WasmFpProverCommitments | WasmFqProverCommitments; -type NapiOpeningProof = WasmFpOpeningProof | WasmFqOpeningProof; -type NapiProverProof = WasmFpProverProof | WasmFqProverProof; -type NapiLookupCommitments = WasmFpLookupCommitments | WasmFqLookupCommitments; type NapiRuntimeTable = WasmFpRuntimeTable | WasmFqRuntimeTable; type NapiRuntimeTableCfg = WasmPastaFpRuntimeTableCfg | WasmPastaFqRuntimeTableCfg; type NapiLookupTable = WasmPastaFpLookupTable | WasmPastaFqLookupTable; type NapiClasses = { - ProverCommitments: typeof WasmFpProverCommitments | typeof WasmFqProverCommitments; - OpeningProof: typeof WasmFpOpeningProof | typeof WasmFqOpeningProof; VecVec: typeof WasmVecVecFp | typeof WasmVecVecFq; - ProverProof: typeof WasmFpProverProof | typeof WasmFqProverProof; - LookupCommitments: typeof WasmFpLookupCommitments | typeof WasmFqLookupCommitments; RuntimeTable: typeof WasmFpRuntimeTable | typeof WasmFqRuntimeTable; RuntimeTableCfg: typeof WasmPastaFpRuntimeTableCfg | typeof WasmPastaFqRuntimeTableCfg; LookupTable: typeof WasmPastaFpLookupTable | typeof WasmPastaFqLookupTable; @@ -71,21 +32,13 @@ type NapiClasses = { function napiProofConversion(napi: napi, core: ConversionCores) { return { fp: proofConversionPerField(core.fp, { - ProverCommitments: napi.WasmFpProverCommitments, - OpeningProof: napi.WasmFpOpeningProof, VecVec: napi.WasmVecVecFp, - ProverProof: napi.WasmFpProverProof, - LookupCommitments: napi.WasmFpLookupCommitments, RuntimeTable: napi.WasmFpRuntimeTable, RuntimeTableCfg: napi.WasmPastaFpRuntimeTableCfg, LookupTable: napi.WasmPastaFpLookupTable, }), fq: proofConversionPerField(core.fq, { - ProverCommitments: napi.WasmFqProverCommitments, - OpeningProof: napi.WasmFqOpeningProof, VecVec: napi.WasmVecVecFq, - ProverProof: napi.WasmFqProverProof, - LookupCommitments: napi.WasmFqLookupCommitments, RuntimeTable: napi.WasmFqRuntimeTable, RuntimeTableCfg: napi.WasmPastaFqRuntimeTableCfg, LookupTable: napi.WasmPastaFqLookupTable, @@ -95,79 +48,8 @@ function napiProofConversion(napi: napi, core: ConversionCores) { function proofConversionPerField( core: ConversionCore, - { - ProverCommitments, - OpeningProof, - VecVec, - ProverProof, - LookupCommitments, - RuntimeTable, - RuntimeTableCfg, - LookupTable, - }: NapiClasses + { VecVec, RuntimeTable, RuntimeTableCfg, LookupTable }: NapiClasses ) { - function commitmentsToRust(commitments: ProverCommitments): NapiProverCommitments { - let wComm = core.polyCommsToRust(commitments[1]); - let zComm = core.polyCommToRust(commitments[2]); - let tComm = core.polyCommToRust(commitments[3]); - let lookup = MlOption.mapFrom(commitments[4], lookupCommitmentsToRust); - return new ProverCommitments(wComm, zComm, tComm, lookup); - } - function commitmentsFromRust(commitments: NapiProverCommitments): ProverCommitments { - let wComm = core.polyCommsFromRust(commitments.w_comm); - let zComm = core.polyCommFromRust(commitments.z_comm); - let tComm = core.polyCommFromRust(commitments.t_comm); - let lookup = MlOption.mapTo(commitments.lookup, lookupCommitmentsFromRust); - commitments.free(); - return [0, wComm as MlTuple, zComm, tComm, lookup]; - } - - function lookupCommitmentsToRust(lookup: LookupCommitments): NapiLookupCommitments { - let sorted = core.polyCommsToRust(lookup[1]); - let aggreg = core.polyCommToRust(lookup[2]); - let runtime = MlOption.mapFrom(lookup[3], core.polyCommToRust); - return new LookupCommitments(sorted, aggreg, runtime); - } - function lookupCommitmentsFromRust(lookup: NapiLookupCommitments): LookupCommitments { - let sorted = core.polyCommsFromRust(lookup.sorted); - let aggreg = core.polyCommFromRust(lookup.aggreg); - let runtime = MlOption.mapTo(lookup.runtime, core.polyCommFromRust); - lookup.free(); - return [0, sorted, aggreg, runtime]; - } - - function openingProofToRust(proof: OpeningProof): NapiOpeningProof { - let [_, [, ...lr], delta, z1, z2, sg] = proof; - // We pass l and r as separate vectors over the FFI - let l: MlArray = [0]; - let r: MlArray = [0]; - for (let [, li, ri] of lr) { - l.push(li); - r.push(ri); - } - return new OpeningProof( - core.pointsToRust(l), - core.pointsToRust(r), - core.pointToRust(delta), - fieldToRust(z1), - fieldToRust(z2), - core.pointToRust(sg) - ); - } - function openingProofFromRust(proof: NapiOpeningProof): OpeningProof { - let [, ...l] = core.pointsFromRust(proof.lr_0); - let [, ...r] = core.pointsFromRust(proof.lr_1); - let n = l.length; - if (n !== r.length) throw Error('openingProofFromRust: l and r length mismatch.'); - let lr = l.map<[0, OrInfinity, OrInfinity]>((li, i) => [0, li, r[i]]); - let delta = core.pointFromRust(proof.delta); - let z1 = fieldFromRust(proof.z1); - let z2 = fieldFromRust(proof.z2); - let sg = core.pointFromRust(proof.sg); - proof.free(); - return [0, [0, ...lr], delta, z1, z2, sg]; - } - function runtimeTableToRust([, id, data]: RuntimeTable): NapiRuntimeTable { return new RuntimeTable(id, core.vectorToRust(data)); } @@ -184,68 +66,8 @@ function proofConversionPerField( } return new LookupTable(id, wasmData); } - - return { - proofToRust([, public_evals, proof]: ProofWithPublic): NapiProverProof { - let commitments = commitmentsToRust(proof[1]); - let openingProof = openingProofToRust(proof[2]); - let [, ...evals] = proofEvaluationsToRust(proof[3]); - let publicEvals = pointEvalsOptionToRust(public_evals); - // TODO typed as `any` in wasm-bindgen, this has the correct type - let evalsActual: NapiProofEvaluations = [0, publicEvals, ...evals]; - - let ftEval1 = fieldToRust(proof[4]); - let public_ = fieldsToRustFlat(proof[5]); - let [, ...prevChallenges] = proof[6]; - let n = prevChallenges.length; - let prevChallengeScalars = new VecVec(n); - let prevChallengeCommsMl: MlArray = [0]; - for (let [, scalars, comms] of prevChallenges) { - prevChallengeScalars.push(fieldsToRustFlat(scalars)); - prevChallengeCommsMl.push(comms); - } - let prevChallengeComms = core.polyCommsToRust(prevChallengeCommsMl); - return new ProverProof( - commitments, - openingProof, - evalsActual, - ftEval1, - public_, - prevChallengeScalars, - prevChallengeComms - ); - }, - proofFromRust(wasmProof: NapiProverProof): ProofWithPublic { - let commitments = commitmentsFromRust(wasmProof.commitments); - let openingProof = openingProofFromRust(wasmProof.proof); - // TODO typed as `any` in wasm-bindgen, this is the correct type - let [, wasmPublicEvals, ...wasmEvals]: NapiProofEvaluations = wasmProof.evals; - let publicEvals = pointEvalsOptionFromRust(wasmPublicEvals); - let evals = proofEvaluationsFromRust([0, ...wasmEvals]); - - let ftEval1 = fieldFromRust(wasmProof.ft_eval1); - let public_ = fieldsFromRustFlat(wasmProof.public_); - let prevChallengeScalars = wasmProof.prev_challenges_scalars; - let [, ...prevChallengeComms] = core.polyCommsFromRust(wasmProof.prev_challenges_comms); - let prevChallenges = prevChallengeComms.map((comms, i) => { - let scalars = fieldsFromRustFlat(prevChallengeScalars.get(i)); - return [0, scalars, comms]; - }); - wasmProof.free(); - let proof: ProverProof = [ - 0, - commitments, - openingProof, - evals, - ftEval1, - public_, - [0, ...prevChallenges], - ]; - return [0, publicEvals, proof]; - }, - runtimeTablesToRust([, ...tables]: MlArray): NapiRuntimeTable[] { return tables.map(runtimeTableToRust); }, @@ -259,7 +81,3 @@ function proofConversionPerField( }, }; } - -// helper - -type RemoveLeadingZero = T extends [0, ...infer U] ? U : never; From 0c0f8eba24bb51c7498ab9e00e3d42815e4f55aa Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 13 Nov 2025 12:45:39 +0100 Subject: [PATCH 03/11] Revert "bindings: remove types conversions for proof" This reverts commit f4528083226a6d878da9856d537dc7a71f9b6b58. Because we need access to the proof structure we cannot avoid the conversions. --- src/bindings/crypto/napi-conversion-proof.ts | 194 ++++++++++++++++++- 1 file changed, 188 insertions(+), 6 deletions(-) diff --git a/src/bindings/crypto/napi-conversion-proof.ts b/src/bindings/crypto/napi-conversion-proof.ts index adf08d7a10..b09f17fa20 100644 --- a/src/bindings/crypto/napi-conversion-proof.ts +++ b/src/bindings/crypto/napi-conversion-proof.ts @@ -1,29 +1,68 @@ import type { + WasmPastaFpLookupTable, WasmFpRuntimeTable, WasmPastaFpRuntimeTableCfg, - WasmPastaFpLookupTable, + WasmPastaFqLookupTable, WasmFqRuntimeTable, WasmPastaFqRuntimeTableCfg, - WasmPastaFqLookupTable, WasmVecVecFp, WasmVecVecFq, + WasmFpProverCommitments, + WasmFqProverCommitments, + WasmFpOpeningProof, + WasmFqOpeningProof, + WasmFpProverProof, + WasmFqProverProof, + WasmFpLookupCommitments, + WasmFqLookupCommitments, } from '../compiled/node_bindings/plonk_wasm.cjs'; import type * as napiNamespace from '../compiled/node_bindings/plonk_wasm.cjs'; -import type { RuntimeTable, RuntimeTableCfg, LookupTable } from './bindings/kimchi-types.js'; -import { MlArray } from '../../lib/ml/base.js'; -import { fieldsToRustFlat } from './bindings/conversion-base.js'; +import type { + RuntimeTable, + RuntimeTableCfg, + LookupTable, + ProofWithPublic, + ProverProof, + LookupCommitments, + PolyComm, PointEvaluations, ProofEvaluations, + RecursionChallenge, + ProverCommitments, + OpeningProof, + OrInfinity, +} from './bindings/kimchi-types.js'; +import { MlArray, MlTuple, MlOption} from '../../lib/ml/base.js'; +import { + fieldsToRustFlat, + fieldToRust, + fieldsFromRustFlat, + fieldFromRust, +} from './bindings/conversion-base.js'; import { ConversionCore, ConversionCores } from './napi-conversion-core.js'; export { napiProofConversion }; + +type NapiProofEvaluations = [ + 0, + MlOption>, + ...RemoveLeadingZero>, +]; type napi = typeof napiNamespace; +type NapiProverCommitments = WasmFpProverCommitments | WasmFqProverCommitments; +type NapiOpeningProof = WasmFpOpeningProof | WasmFqOpeningProof; +type NapiProverProof = WasmFpProverProof | WasmFqProverProof; +type NapiLookupCommitments = WasmFpLookupCommitments | WasmFqLookupCommitments; type NapiRuntimeTable = WasmFpRuntimeTable | WasmFqRuntimeTable; type NapiRuntimeTableCfg = WasmPastaFpRuntimeTableCfg | WasmPastaFqRuntimeTableCfg; type NapiLookupTable = WasmPastaFpLookupTable | WasmPastaFqLookupTable; type NapiClasses = { + ProverCommitments: typeof WasmFpProverCommitments | typeof WasmFqProverCommitments; + OpeningProof: typeof WasmFpOpeningProof | typeof WasmFqOpeningProof; VecVec: typeof WasmVecVecFp | typeof WasmVecVecFq; + ProverProof: typeof WasmFpProverProof | typeof WasmFqProverProof; + LookupCommitments: typeof WasmFpLookupCommitments | typeof WasmFqLookupCommitments; RuntimeTable: typeof WasmFpRuntimeTable | typeof WasmFqRuntimeTable; RuntimeTableCfg: typeof WasmPastaFpRuntimeTableCfg | typeof WasmPastaFqRuntimeTableCfg; LookupTable: typeof WasmPastaFpLookupTable | typeof WasmPastaFqLookupTable; @@ -32,13 +71,21 @@ type NapiClasses = { function napiProofConversion(napi: napi, core: ConversionCores) { return { fp: proofConversionPerField(core.fp, { + ProverCommitments: napi.WasmFpProverCommitments, + OpeningProof: napi.WasmFpOpeningProof, VecVec: napi.WasmVecVecFp, + ProverProof: napi.WasmFpProverProof, + LookupCommitments: napi.WasmFpLookupCommitments, RuntimeTable: napi.WasmFpRuntimeTable, RuntimeTableCfg: napi.WasmPastaFpRuntimeTableCfg, LookupTable: napi.WasmPastaFpLookupTable, }), fq: proofConversionPerField(core.fq, { + ProverCommitments: napi.WasmFqProverCommitments, + OpeningProof: napi.WasmFqOpeningProof, VecVec: napi.WasmVecVecFq, + ProverProof: napi.WasmFqProverProof, + LookupCommitments: napi.WasmFqLookupCommitments, RuntimeTable: napi.WasmFqRuntimeTable, RuntimeTableCfg: napi.WasmPastaFqRuntimeTableCfg, LookupTable: napi.WasmPastaFqLookupTable, @@ -48,8 +95,79 @@ function napiProofConversion(napi: napi, core: ConversionCores) { function proofConversionPerField( core: ConversionCore, - { VecVec, RuntimeTable, RuntimeTableCfg, LookupTable }: NapiClasses + { + ProverCommitments, + OpeningProof, + VecVec, + ProverProof, + LookupCommitments, + RuntimeTable, + RuntimeTableCfg, + LookupTable, + }: NapiClasses ) { + function commitmentsToRust(commitments: ProverCommitments): NapiProverCommitments { + let wComm = core.polyCommsToRust(commitments[1]); + let zComm = core.polyCommToRust(commitments[2]); + let tComm = core.polyCommToRust(commitments[3]); + let lookup = MlOption.mapFrom(commitments[4], lookupCommitmentsToRust); + return new ProverCommitments(wComm, zComm, tComm, lookup); + } + function commitmentsFromRust(commitments: NapiProverCommitments): ProverCommitments { + let wComm = core.polyCommsFromRust(commitments.w_comm); + let zComm = core.polyCommFromRust(commitments.z_comm); + let tComm = core.polyCommFromRust(commitments.t_comm); + let lookup = MlOption.mapTo(commitments.lookup, lookupCommitmentsFromRust); + commitments.free(); + return [0, wComm as MlTuple, zComm, tComm, lookup]; + } + + function lookupCommitmentsToRust(lookup: LookupCommitments): NapiLookupCommitments { + let sorted = core.polyCommsToRust(lookup[1]); + let aggreg = core.polyCommToRust(lookup[2]); + let runtime = MlOption.mapFrom(lookup[3], core.polyCommToRust); + return new LookupCommitments(sorted, aggreg, runtime); + } + function lookupCommitmentsFromRust(lookup: NapiLookupCommitments): LookupCommitments { + let sorted = core.polyCommsFromRust(lookup.sorted); + let aggreg = core.polyCommFromRust(lookup.aggreg); + let runtime = MlOption.mapTo(lookup.runtime, core.polyCommFromRust); + lookup.free(); + return [0, sorted, aggreg, runtime]; + } + + function openingProofToRust(proof: OpeningProof): NapiOpeningProof { + let [_, [, ...lr], delta, z1, z2, sg] = proof; + // We pass l and r as separate vectors over the FFI + let l: MlArray = [0]; + let r: MlArray = [0]; + for (let [, li, ri] of lr) { + l.push(li); + r.push(ri); + } + return new OpeningProof( + core.pointsToRust(l), + core.pointsToRust(r), + core.pointToRust(delta), + fieldToRust(z1), + fieldToRust(z2), + core.pointToRust(sg) + ); + } + function openingProofFromRust(proof: NapiOpeningProof): OpeningProof { + let [, ...l] = core.pointsFromRust(proof.lr_0); + let [, ...r] = core.pointsFromRust(proof.lr_1); + let n = l.length; + if (n !== r.length) throw Error('openingProofFromRust: l and r length mismatch.'); + let lr = l.map<[0, OrInfinity, OrInfinity]>((li, i) => [0, li, r[i]]); + let delta = core.pointFromRust(proof.delta); + let z1 = fieldFromRust(proof.z1); + let z2 = fieldFromRust(proof.z2); + let sg = core.pointFromRust(proof.sg); + proof.free(); + return [0, [0, ...lr], delta, z1, z2, sg]; + } + function runtimeTableToRust([, id, data]: RuntimeTable): NapiRuntimeTable { return new RuntimeTable(id, core.vectorToRust(data)); } @@ -66,8 +184,68 @@ function proofConversionPerField( } return new LookupTable(id, wasmData); } + + return { + proofToRust([, public_evals, proof]: ProofWithPublic): NapiProverProof { + let commitments = commitmentsToRust(proof[1]); + let openingProof = openingProofToRust(proof[2]); + let [, ...evals] = proofEvaluationsToRust(proof[3]); + let publicEvals = pointEvalsOptionToRust(public_evals); + // TODO typed as `any` in wasm-bindgen, this has the correct type + let evalsActual: NapiProofEvaluations = [0, publicEvals, ...evals]; + + let ftEval1 = fieldToRust(proof[4]); + let public_ = fieldsToRustFlat(proof[5]); + let [, ...prevChallenges] = proof[6]; + let n = prevChallenges.length; + let prevChallengeScalars = new VecVec(n); + let prevChallengeCommsMl: MlArray = [0]; + for (let [, scalars, comms] of prevChallenges) { + prevChallengeScalars.push(fieldsToRustFlat(scalars)); + prevChallengeCommsMl.push(comms); + } + let prevChallengeComms = core.polyCommsToRust(prevChallengeCommsMl); + return new ProverProof( + commitments, + openingProof, + evalsActual, + ftEval1, + public_, + prevChallengeScalars, + prevChallengeComms + ); + }, + proofFromRust(wasmProof: NapiProverProof): ProofWithPublic { + let commitments = commitmentsFromRust(wasmProof.commitments); + let openingProof = openingProofFromRust(wasmProof.proof); + // TODO typed as `any` in wasm-bindgen, this is the correct type + let [, wasmPublicEvals, ...wasmEvals]: NapiProofEvaluations = wasmProof.evals; + let publicEvals = pointEvalsOptionFromRust(wasmPublicEvals); + let evals = proofEvaluationsFromRust([0, ...wasmEvals]); + + let ftEval1 = fieldFromRust(wasmProof.ft_eval1); + let public_ = fieldsFromRustFlat(wasmProof.public_); + let prevChallengeScalars = wasmProof.prev_challenges_scalars; + let [, ...prevChallengeComms] = core.polyCommsFromRust(wasmProof.prev_challenges_comms); + let prevChallenges = prevChallengeComms.map((comms, i) => { + let scalars = fieldsFromRustFlat(prevChallengeScalars.get(i)); + return [0, scalars, comms]; + }); + wasmProof.free(); + let proof: ProverProof = [ + 0, + commitments, + openingProof, + evals, + ftEval1, + public_, + [0, ...prevChallenges], + ]; + return [0, publicEvals, proof]; + }, + runtimeTablesToRust([, ...tables]: MlArray): NapiRuntimeTable[] { return tables.map(runtimeTableToRust); }, @@ -81,3 +259,7 @@ function proofConversionPerField( }, }; } + +// helper + +type RemoveLeadingZero = T extends [0, ...infer U] ? U : never; From b59aba1b25ea9fa8cc5dee0d48659c5017ea6d5f Mon Sep 17 00:00:00 2001 From: querolita Date: Fri, 14 Nov 2025 12:37:56 +0100 Subject: [PATCH 04/11] submodule: update mina --- src/mina | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mina b/src/mina index a7b1ba0a1e..ef9d64fbce 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit a7b1ba0a1e64f9f36ed8df51998d4b53dd63cef1 +Subproject commit ef9d64fbce56e86c6a8b000813324cee3d646fbc From d741ec68d619a831beb90bd85a3eab3f9c47cb2e Mon Sep 17 00:00:00 2001 From: Shigoto-dev19 Date: Fri, 14 Nov 2025 19:57:31 +0300 Subject: [PATCH 05/11] Native Rust conversion for oracles --- src/bindings/crypto/bindings.ts | 6 +- .../bindings/napi-conversion-oracles.ts | 123 ++++++++++++++++++ 2 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 src/bindings/crypto/bindings/napi-conversion-oracles.ts diff --git a/src/bindings/crypto/bindings.ts b/src/bindings/crypto/bindings.ts index ee231d6261..a1c5e8a202 100644 --- a/src/bindings/crypto/bindings.ts +++ b/src/bindings/crypto/bindings.ts @@ -14,6 +14,7 @@ import { verifierIndexConversion } from './bindings/conversion-verifier-index.js import { PallasBindings, VestaBindings } from './bindings/curve.js'; import { jsEnvironment } from './bindings/env.js'; import { FpBindings, FqBindings } from './bindings/field.js'; +import { napiOraclesConversion } from './bindings/napi-conversion-oracles.js'; import { srs } from './bindings/srs.js'; import { FpVectorBindings, FqVectorBindings } from './bindings/vector.js'; import { napiConversionCore } from './napi-conversion-core.js'; @@ -85,9 +86,10 @@ function buildWasmConversion(wasm: Wasm) { function createNativeRustConversion(napi: any) { let core = napiConversionCore(napi); let proof = napiProofConversion(napi, core); + let oracles = napiOraclesConversion(napi); return { - fp: { ...core.fp, ...proof.fp }, - fq: { ...core.fq, ...proof.fq }, + fp: { ...core.fp, ...proof.fp, ...oracles.fp }, + fq: { ...core.fq, ...proof.fq, ...oracles.fq }, }; } diff --git a/src/bindings/crypto/bindings/napi-conversion-oracles.ts b/src/bindings/crypto/bindings/napi-conversion-oracles.ts new file mode 100644 index 0000000000..13bcb42f96 --- /dev/null +++ b/src/bindings/crypto/bindings/napi-conversion-oracles.ts @@ -0,0 +1,123 @@ +import { MlOption } from '../../../lib/ml/base.js'; +import type * as napiNamespace from '../../compiled/node_bindings/plonk_wasm.cjs'; +import type { + WasmFpOracles, + WasmFpRandomOracles, + WasmFqOracles, + WasmFqRandomOracles, +} from '../../compiled/node_bindings/plonk_wasm.cjs'; +import { + fieldFromRust, + fieldToRust, + fieldsFromRustFlat, + fieldsToRustFlat, + maybeFieldToRust, +} from './conversion-base.js'; +import { Field, Oracles, RandomOracles, ScalarChallenge } from './kimchi-types.js'; + +export { napiOraclesConversion }; + +type napi = typeof napiNamespace; + +type NapiRandomOracles = WasmFpRandomOracles | WasmFqRandomOracles; +type NapiOracles = WasmFpOracles | WasmFqOracles; + +type NapiClasses = { + RandomOracles: typeof WasmFpRandomOracles | typeof WasmFqRandomOracles; + Oracles: typeof WasmFpOracles | typeof WasmFqOracles; +}; + +function napiOraclesConversion(napi: napi) { + return { + fp: oraclesConversionPerField({ + RandomOracles: napi.WasmFpRandomOracles, + Oracles: napi.WasmFpOracles, + }), + fq: oraclesConversionPerField({ + RandomOracles: napi.WasmFqRandomOracles, + Oracles: napi.WasmFqOracles, + }), + }; +} + +function oraclesConversionPerField({ RandomOracles, Oracles }: NapiClasses) { + function randomOraclesToRust(ro: RandomOracles): NapiRandomOracles { + let jointCombinerMl = MlOption.from(ro[1]); + let jointCombinerChal = maybeFieldToRust(jointCombinerMl?.[1][1]); + let jointCombiner = maybeFieldToRust(jointCombinerMl?.[2]); + let beta = fieldToRust(ro[2]); + let gamma = fieldToRust(ro[3]); + let alphaChal = fieldToRust(ro[4][1]); + let alpha = fieldToRust(ro[5]); + let zeta = fieldToRust(ro[6]); + let v = fieldToRust(ro[7]); + let u = fieldToRust(ro[8]); + let zetaChal = fieldToRust(ro[9][1]); + let vChal = fieldToRust(ro[10][1]); + let uChal = fieldToRust(ro[11][1]); + return new RandomOracles( + jointCombinerChal, + jointCombiner, + beta, + gamma, + alphaChal, + alpha, + zeta, + v, + u, + zetaChal, + vChal, + uChal + ); + } + function randomOraclesFromRust(ro: NapiRandomOracles): RandomOracles { + let jointCombinerChal = ro.joint_combiner_chal; + let jointCombiner = ro.joint_combiner; + let jointCombinerOption = MlOption<[0, ScalarChallenge, Field]>( + jointCombinerChal && + jointCombiner && [0, [0, fieldFromRust(jointCombinerChal)], fieldFromRust(jointCombiner)] + ); + let mlRo: RandomOracles = [ + 0, + jointCombinerOption, + fieldFromRust(ro.beta), + fieldFromRust(ro.gamma), + [0, fieldFromRust(ro.alpha_chal)], + fieldFromRust(ro.alpha), + fieldFromRust(ro.zeta), + fieldFromRust(ro.v), + fieldFromRust(ro.u), + [0, fieldFromRust(ro.zeta_chal)], + [0, fieldFromRust(ro.v_chal)], + [0, fieldFromRust(ro.u_chal)], + ]; + // TODO: do we not want to free? + // ro.free(); + return mlRo; + } + + return { + oraclesToRust(oracles: Oracles): NapiOracles { + let [, o, pEval, openingPrechallenges, digestBeforeEvaluations] = oracles; + return new Oracles( + randomOraclesToRust(o), + fieldToRust(pEval[1]), + fieldToRust(pEval[2]), + fieldsToRustFlat(openingPrechallenges), + fieldToRust(digestBeforeEvaluations) + ); + }, + oraclesFromRust(oracles: NapiOracles): Oracles { + let mlOracles: Oracles = [ + 0, + randomOraclesFromRust(oracles.o), + [0, fieldFromRust(oracles.p_eval0), fieldFromRust(oracles.p_eval1)], + fieldsFromRustFlat(oracles.opening_prechallenges), + fieldFromRust(oracles.digest_before_evaluations), + ]; + // TODO: do we not want to free? + // oracles.free(); + return mlOracles; + }, + }; +} From cba0e65cadce66294c95609dcb88b072b368115d Mon Sep 17 00:00:00 2001 From: querolita Date: Fri, 14 Nov 2025 23:09:56 +0100 Subject: [PATCH 06/11] bindings: silent compiler using any in napi proof conversion --- src/bindings/crypto/napi-conversion-proof.ts | 99 ++++++++++++++++++-- 1 file changed, 93 insertions(+), 6 deletions(-) diff --git a/src/bindings/crypto/napi-conversion-proof.ts b/src/bindings/crypto/napi-conversion-proof.ts index b09f17fa20..690b416783 100644 --- a/src/bindings/crypto/napi-conversion-proof.ts +++ b/src/bindings/crypto/napi-conversion-proof.ts @@ -38,9 +38,17 @@ import { fieldFromRust, } from './bindings/conversion-base.js'; import { ConversionCore, ConversionCores } from './napi-conversion-core.js'; +import type { Field } from './bindings/field.js'; export { napiProofConversion }; +const fieldToRust_ = (x: Field) => fieldToRust(x); +const proofEvaluationsToRust = mapProofEvaluations(fieldToRust_); +const proofEvaluationsFromRust = mapProofEvaluations(fieldFromRust); +const pointEvalsOptionToRust = mapPointEvalsOption(fieldToRust_); +const pointEvalsOptionFromRust = mapPointEvalsOption(fieldFromRust); + + type NapiProofEvaluations = [ 0, MlOption>, @@ -111,7 +119,7 @@ function proofConversionPerField( let zComm = core.polyCommToRust(commitments[2]); let tComm = core.polyCommToRust(commitments[3]); let lookup = MlOption.mapFrom(commitments[4], lookupCommitmentsToRust); - return new ProverCommitments(wComm, zComm, tComm, lookup); + return new ProverCommitments(wComm as any, zComm as any, tComm as any, lookup as any); } function commitmentsFromRust(commitments: NapiProverCommitments): ProverCommitments { let wComm = core.polyCommsFromRust(commitments.w_comm); @@ -126,7 +134,7 @@ function proofConversionPerField( let sorted = core.polyCommsToRust(lookup[1]); let aggreg = core.polyCommToRust(lookup[2]); let runtime = MlOption.mapFrom(lookup[3], core.polyCommToRust); - return new LookupCommitments(sorted, aggreg, runtime); + return new LookupCommitments(sorted as any, aggreg as any, runtime as any); } function lookupCommitmentsFromRust(lookup: NapiLookupCommitments): LookupCommitments { let sorted = core.polyCommsFromRust(lookup.sorted); @@ -146,15 +154,15 @@ function proofConversionPerField( r.push(ri); } return new OpeningProof( - core.pointsToRust(l), - core.pointsToRust(r), + core.pointsToRust(l) as any, + core.pointsToRust(r) as any, core.pointToRust(delta), fieldToRust(z1), fieldToRust(z2), core.pointToRust(sg) ); } - function openingProofFromRust(proof: NapiOpeningProof): OpeningProof { + function openingProofFromRust(proof: any): OpeningProof { let [, ...l] = core.pointsFromRust(proof.lr_0); let [, ...r] = core.pointsFromRust(proof.lr_1); let n = l.length; @@ -214,7 +222,7 @@ function proofConversionPerField( ftEval1, public_, prevChallengeScalars, - prevChallengeComms + prevChallengeComms as any ); }, proofFromRust(wasmProof: NapiProverProof): ProofWithPublic { @@ -260,6 +268,85 @@ function proofConversionPerField( }; } +function createMapPointEvals(map: (x: Field1) => Field2) { + return (evals: PointEvaluations): PointEvaluations => { + let [, zeta, zeta_omega] = evals; + return [0, MlArray.map(zeta, map), MlArray.map(zeta_omega, map)]; + }; +} + +function mapPointEvalsOption(map: (x: Field1) => Field2) { + return (evals: MlOption>) => + MlOption.map(evals, createMapPointEvals(map)); +} + +function mapProofEvaluations(map: (x: Field1) => Field2) { + const mapPointEvals = createMapPointEvals(map); + + const mapPointEvalsOption = ( + evals: MlOption> + ): MlOption> => MlOption.map(evals, mapPointEvals); + + return function mapProofEvaluations(evals: ProofEvaluations): ProofEvaluations { + let [ + , + w, + z, + s, + coeffs, + genericSelector, + poseidonSelector, + completeAddSelector, + mulSelector, + emulSelector, + endomulScalarSelector, + rangeCheck0Selector, + rangeCheck1Selector, + foreignFieldAddSelector, + foreignFieldMulSelector, + xorSelector, + rotSelector, + lookupAggregation, + lookupTable, + lookupSorted, + runtimeLookupTable, + runtimeLookupTableSelector, + xorLookupSelector, + lookupGateLookupSelector, + rangeCheckLookupSelector, + foreignFieldMulLookupSelector, + ] = evals; + return [ + 0, + MlTuple.map(w, mapPointEvals), + mapPointEvals(z), + MlTuple.map(s, mapPointEvals), + MlTuple.map(coeffs, mapPointEvals), + mapPointEvals(genericSelector), + mapPointEvals(poseidonSelector), + mapPointEvals(completeAddSelector), + mapPointEvals(mulSelector), + mapPointEvals(emulSelector), + mapPointEvals(endomulScalarSelector), + mapPointEvalsOption(rangeCheck0Selector), + mapPointEvalsOption(rangeCheck1Selector), + mapPointEvalsOption(foreignFieldAddSelector), + mapPointEvalsOption(foreignFieldMulSelector), + mapPointEvalsOption(xorSelector), + mapPointEvalsOption(rotSelector), + mapPointEvalsOption(lookupAggregation), + mapPointEvalsOption(lookupTable), + MlArray.map(lookupSorted, mapPointEvalsOption), + mapPointEvalsOption(runtimeLookupTable), + mapPointEvalsOption(runtimeLookupTableSelector), + mapPointEvalsOption(xorLookupSelector), + mapPointEvalsOption(lookupGateLookupSelector), + mapPointEvalsOption(rangeCheckLookupSelector), + mapPointEvalsOption(foreignFieldMulLookupSelector), + ]; + }; +} + // helper type RemoveLeadingZero = T extends [0, ...infer U] ? U : never; From 0e5f958f574b7b6bfe807b2cba1850d7eb3a7a60 Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 19 Nov 2025 00:49:09 +0100 Subject: [PATCH 07/11] bindings: create napi srs bindings --- src/bindings/crypto/bindings.ts | 7 +- src/bindings/crypto/napi-srs.ts | 343 ++++++++++++++++++++++++++++++++ tests/native/native.ts | 2 + 3 files changed, 350 insertions(+), 2 deletions(-) create mode 100644 src/bindings/crypto/napi-srs.ts diff --git a/src/bindings/crypto/bindings.ts b/src/bindings/crypto/bindings.ts index 23b133cdc7..97b65cd470 100644 --- a/src/bindings/crypto/bindings.ts +++ b/src/bindings/crypto/bindings.ts @@ -15,11 +15,13 @@ import { PallasBindings, VestaBindings } from './bindings/curve.js'; import { jsEnvironment } from './bindings/env.js'; import { FpBindings, FqBindings } from './bindings/field.js'; import { srs } from './bindings/srs.js'; +import { srs as napiSrs } from './napi-srs.js'; import { FpVectorBindings, FqVectorBindings } from './bindings/vector.js'; import { napiConversionCore } from './napi-conversion-core.js'; import { napiProofConversion } from './napi-conversion-proof.js'; +import type * as napiNamespace from '../compiled/node_bindings/plonk_wasm.cjs'; -export { RustConversion, Wasm, createNativeRustConversion, getRustConversion }; +export { RustConversion, Wasm, Napi, createNativeRustConversion, getRustConversion }; /* TODO: Uncomment in phase 2 of conversion layer import { conversionCore as conversionCoreNative } from './native/conversion-core.js'; @@ -49,13 +51,14 @@ const tsBindings = { return bundle.srsFactory(wasm, bundle.conversion); },*/ srs: (wasm: Wasm) => srs(wasm, getRustConversion(wasm)), - srsNative: (napi: Wasm) => srs(napi, createNativeRustConversion(napi) as any), + srsNative: (napi: Napi) => napiSrs(napi, createNativeRustConversion(napi) as any), }; // this is put in a global variable so that mina/src/lib/crypto/kimchi_bindings/js/bindings.js finds it (globalThis as any).__snarkyTsBindings = tsBindings; type Wasm = typeof wasmNamespace; +type Napi = typeof napiNamespace; type RustConversion = ReturnType; diff --git a/src/bindings/crypto/napi-srs.ts b/src/bindings/crypto/napi-srs.ts new file mode 100644 index 0000000000..fe61e75b4b --- /dev/null +++ b/src/bindings/crypto/napi-srs.ts @@ -0,0 +1,343 @@ +import { MlArray } from '../../lib/ml/base.js'; +import { + readCache, + withVersion, + writeCache, + type Cache, + type CacheHeader, +} from '../../lib/proof-system/cache.js'; +import { assert } from '../../lib/util/errors.js'; +import { type WasmFpSrs, type WasmFqSrs } from '../compiled/node_bindings/plonk_wasm.cjs'; +import type { RustConversion, Napi } from './bindings.js'; +import { OrInfinity, OrInfinityJson } from './bindings/curve.js'; +import { PolyComm } from './bindings/kimchi-types.js'; + +export { setSrsCache, srs, unsetSrsCache }; + +type NapiSrs = WasmFpSrs | WasmFqSrs; + +type SrsStore = Record; + +function empty(): SrsStore { + return {}; +} + +const srsStore = { fp: empty(), fq: empty() }; + +const CacheReadRegister = new Map(); + +let cache: Cache | undefined; + +function setSrsCache(c: Cache) { + cache = c; +} +function unsetSrsCache() { + cache = undefined; +} + +const srsVersion = 1; + +function cacheHeaderLagrange(f: 'fp' | 'fq', domainSize: number): CacheHeader { + let id = `lagrange-basis-${f}-${domainSize}`; + return withVersion( + { + kind: 'lagrange-basis', + persistentId: id, + uniqueId: id, + dataType: 'string', + }, + srsVersion + ); +} +function cacheHeaderSrs(f: 'fp' | 'fq', domainSize: number): CacheHeader { + let id = `srs-${f}-${domainSize}`; + return withVersion( + { + kind: 'srs', + persistentId: id, + uniqueId: id, + dataType: 'string', + }, + srsVersion + ); +} + +function srs(napi: Napi , conversion: RustConversion) { + return { + fp: srsPerField('fp', napi, conversion), + fq: srsPerField('fq', napi, conversion), + }; +} + +function srsPerField(f: 'fp' | 'fq', napi: Napi, conversion: RustConversion) { + // note: these functions are properly typed, thanks to TS template literal types + let createSrs = (size: number) => { + try { + console.log(0); + return napi[`caml_${f}_srs_create_parallel`](size); + } catch (error) { + console.error(`Error in SRS get for field ${f}`); + throw error; + } + }; + let getSrs = (srs: NapiSrs) => { + try { + console.log(1); + let v = napi[`caml_${f}_srs_get`](srs); + console.log(2); + return v; + } catch (error) { + console.error(`Error in SRS get for field ${f}`); + throw error; + } + }; + let setSrs = (bytes: any) => { + try { + console.log(2); + return napi[`caml_${f}_srs_set`](bytes); + } catch (error) { + console.error(`Error in SRS set for field ${f} args ${bytes}`); + throw error; + } + }; + + let maybeLagrangeCommitment = (srs: NapiSrs, domain_size: number, i: number) => { + try { + return napi[`caml_${f}_srs_maybe_lagrange_commitment`](srs, domain_size, i); + } catch (error) { + console.error(`Error in SRS maybe lagrange commitment for field ${f}`); + throw error; + } + }; + let lagrangeCommitment = (srs: NapiSrs, domain_size: number, i: number) => { + try { + return napi[`caml_${f}_srs_lagrange_commitment`](srs, domain_size, i); + } catch (error) { + console.error(`Error in SRS lagrange commitment for field ${f}`); + throw error; + } + }; + let setLagrangeBasis = (srs: NapiSrs, domain_size: number, input: any) => { + try { + console.log(6); + return napi[`caml_${f}_srs_set_lagrange_basis`](srs, domain_size, input); + } catch (error) { + console.error(`Error in SRS set lagrange basis for field ${f}`); + throw error; + } + }; + let getLagrangeBasis = (srs: NapiSrs, n: number) => { + try { + return napi[`caml_${f}_srs_get_lagrange_basis`](srs, n); + } catch (error) { + console.error(`Error in SRS get lagrange basis for field ${f}`); + throw error; + } + }; + return { + /** + * returns existing stored SRS or falls back to creating a new one + */ + create(size: number): NapiSrs { + let srs = srsStore[f][size] satisfies NapiSrs as NapiSrs | undefined; + + if (srs === undefined) { + if (cache === undefined) { + // if there is no cache, create SRS in memory + console.log('Creating SRS without cache'); + srs = createSrs(size); + console.log('SRS created without cache:', srs); + } else { + let header = cacheHeaderSrs(f, size); + + // try to read SRS from cache / recompute and write if not found + console.log('Reading SRS from cache'); + srs = readCache(cache, header, (bytes) => { + // TODO: this takes a bit too long, about 300ms for 2^16 + // `pointsToRust` is the clear bottleneck + let jsonSrs: OrInfinityJson[] = JSON.parse(new TextDecoder().decode(bytes)); + let mlSrs = MlArray.mapTo(jsonSrs, OrInfinity.fromJSON); + let wasmSrs = conversion[f].pointsToRust(mlSrs); + return setSrs(wasmSrs); + }); + console.log('SRS read from cache:', srs); + if (srs === undefined) { + // not in cache + console.log(1); + srs = createSrs(size); + console.log('Writing SRS to cache', srs); + + if (cache.canWrite) { + console.log(2); + let wasmSrs = getSrs(srs); + console.log(3); + let mlSrs = conversion[f].pointsFromRust(wasmSrs); + let jsonSrs = MlArray.mapFrom(mlSrs, OrInfinity.toJSON); + let bytes = new TextEncoder().encode(JSON.stringify(jsonSrs)); + + writeCache(cache, header, bytes); + } + } + } + console.log('Storing SRS in memory'); + srsStore[f][size] = srs; + console.log('SRS stored in memory:', srs); + } + + // TODO should we call freeOnFinalize() and expose a function to clean the SRS cache? + console.trace('Returning SRS:', srs); + return srsStore[f][size]; + }, + + /** + * returns ith Lagrange basis commitment for a given domain size + */ + lagrangeCommitment(srs: NapiSrs, domainSize: number, i: number): PolyComm { + console.log('lagrangeCommitment'); + // happy, fast case: if basis is already stored on the srs, return the ith commitment + let commitment = maybeLagrangeCommitment(srs, domainSize, i); + + if (commitment === undefined) { + if (cache === undefined) { + // if there is no cache, recompute and store basis in memory + commitment = lagrangeCommitment(srs, domainSize, i); + } else { + // try to read lagrange basis from cache / recompute and write if not found + let header = cacheHeaderLagrange(f, domainSize); + let didRead = readCacheLazy( + cache, + header, + conversion, + f, + srs, + domainSize, + setLagrangeBasis + ); + if (didRead !== true) { + // not in cache + if (cache.canWrite) { + // TODO: this code path will throw on the web since `caml_${f}_srs_get_lagrange_basis` is not properly implemented + // using a writable cache in the browser seems to be fairly uncommon though, so it's at least an 80/20 solution + let napiComms = getLagrangeBasis(srs, domainSize); + console.log('napiComms', napiComms); + let mlComms = conversion[f].polyCommsFromRust(napiComms); + console.log('mlComms', mlComms); + let comms = polyCommsToJSON(mlComms); + let bytes = new TextEncoder().encode(JSON.stringify(comms)); + writeCache(cache, header, bytes); + } else { + lagrangeCommitment(srs, domainSize, i); + } + } + // here, basis is definitely stored on the srs + let c = maybeLagrangeCommitment(srs, domainSize, i); + assert(c !== undefined, 'commitment exists after setting'); + commitment = c; + } + } + + // edge case for when we have a writeable cache and the basis was already stored on the srs + // but we didn't store it in the cache separately yet + if (commitment && cache && cache.canWrite) { + let header = cacheHeaderLagrange(f, domainSize); + let didRead = readCacheLazy( + cache, + header, + conversion, + f, + srs, + domainSize, + setLagrangeBasis + ); + // only proceed for entries we haven't written to the cache yet + if (didRead !== true) { + // same code as above - write the lagrange basis to the cache if it wasn't there already + // currently we re-generate the basis via `getLagrangeBasis` - we could derive this from the + // already existing `commitment` instead, but this is simpler and the performance impact is negligible + let napiComms = getLagrangeBasis(srs, domainSize); + let mlComms = conversion[f].polyCommsFromRust(napiComms); + let comms = polyCommsToJSON(mlComms); + let bytes = new TextEncoder().encode(JSON.stringify(comms)); + + writeCache(cache, header, bytes); + } + } + return conversion[f].polyCommFromRust(commitment); + }, + + /** + * Returns the Lagrange basis commitments for the whole domain + */ + lagrangeCommitmentsWholeDomain(srs: NapiSrs, domainSize: number) { + console.log('lagrangeCommitmentsWholeDomain'); + try { + let napiComms = napi[`caml_${f}_srs_lagrange_commitments_whole_domain_ptr`](srs, domainSize); + let mlComms = conversion[f].polyCommsFromRust(napiComms as any); + return mlComms; + } catch (error) { + console.error(`Error in SRS lagrange commitments whole domain ptr for field ${f}`); + throw error; + } + }, + + /** + * adds Lagrange basis for a given domain size + */ + addLagrangeBasis(srs: NapiSrs, logSize: number) { + console.log('addLagrangeBasis'); + // this ensures that basis is stored on the srs, no need to duplicate caching logic + this.lagrangeCommitment(srs, 1 << logSize, 0); + }, + }; +} + +type PolyCommJson = { + shifted: OrInfinityJson[]; + unshifted: OrInfinityJson | undefined; +}; + +function polyCommsToJSON(comms: MlArray): PolyCommJson[] { + return MlArray.mapFrom(comms, ([, elems]) => { + return { + shifted: MlArray.mapFrom(elems, OrInfinity.toJSON), + unshifted: undefined, + }; + }); +} + +function polyCommsFromJSON(json: PolyCommJson[]): MlArray { + return MlArray.mapTo(json, ({ shifted, unshifted }) => { + return [0, MlArray.mapTo(shifted, OrInfinity.fromJSON)]; + }); +} + +function readCacheLazy( + cache: Cache, + header: CacheHeader, + conversion: RustConversion, + f: 'fp' | 'fq', + srs: NapiSrs, + domainSize: number, + setLagrangeBasis: (srs: NapiSrs, domainSize: number, comms: Uint32Array) => void +) { + if (CacheReadRegister.get(header.uniqueId) === true) return true; + return readCache(cache, header, (bytes) => { + let comms: PolyCommJson[] = JSON.parse(new TextDecoder().decode(bytes)); + let mlComms = polyCommsFromJSON(comms); + let napiComms = conversion[f].polyCommsToRust(mlComms); + + setLagrangeBasis(srs, domainSize, napiComms); + CacheReadRegister.set(header.uniqueId, true); + return true; + }); +} +function runInTryCatch any>(fn: T): T { + return function (...args: Parameters): ReturnType { + try { + return fn(...args); + } catch (e) { + console.error(`Error in SRS function ${fn.name} with args:`, args); + throw e; + } + } as T; +} diff --git a/tests/native/native.ts b/tests/native/native.ts index 9216ddbb00..1b6da18c17 100644 --- a/tests/native/native.ts +++ b/tests/native/native.ts @@ -1,6 +1,8 @@ import assert from 'node:assert'; import native from '../../src/native/native'; +// run with `./run tests/native/native.ts --bundle` + console.log(native); assert(native.getNativeCalls() == 0n, 'native module starts with no calls'); From ce58b3fdc97d806cff3b3bc4cde1c7d4cfb4087b Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 19 Nov 2025 18:12:33 +0100 Subject: [PATCH 08/11] submodule: update mina --- src/mina | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mina b/src/mina index 55730353ad..6189fa7b99 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 55730353ad13944294ebd9fb457ec33fbcf99239 +Subproject commit 6189fa7b99e90a34673ac19519cb0271816b306a From ca1f429e6e89d43c1a37a2af4f9ba6af16463a49 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 20 Nov 2025 00:29:14 +0100 Subject: [PATCH 09/11] bindings: fix gate vector unit test notation --- .../bindings/gate-vector-napi.unit-test.ts | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/bindings/crypto/bindings/gate-vector-napi.unit-test.ts b/src/bindings/crypto/bindings/gate-vector-napi.unit-test.ts index 8f6939db1f..9b85604c4e 100644 --- a/src/bindings/crypto/bindings/gate-vector-napi.unit-test.ts +++ b/src/bindings/crypto/bindings/gate-vector-napi.unit-test.ts @@ -6,9 +6,11 @@ import type { Field, Gate, Wire } from './kimchi-types.js'; const require = createRequire(import.meta.url); function loadNative() { + const slug = `${process.platform}-${process.arch}`; const candidates = [ - '../../compiled/_node_bindings/plonk_napi.node', + `../../../../../native/${slug}/plonk_napi.node`, '../../compiled/node_bindings/plonk_napi.node', + '../../compiled/_node_bindings/plonk_napi.node', ]; for (const path of candidates) { try { @@ -23,6 +25,21 @@ function loadNative() { const native: any = loadNative(); +const gateVectorCreate = + native.caml_pasta_fp_plonk_gate_vector_create ?? native.camlPastaFpPlonkGateVectorCreate; +const gateVectorLen = + native.caml_pasta_fp_plonk_gate_vector_len ?? native.camlPastaFpPlonkGateVectorLen; +const gateVectorAdd = + native.caml_pasta_fp_plonk_gate_vector_add ?? native.camlPastaFpPlonkGateVectorAdd; +const gateVectorGet = + native.caml_pasta_fp_plonk_gate_vector_get ?? native.camlPastaFpPlonkGateVectorGet; +const gateVectorWrap = + native.caml_pasta_fp_plonk_gate_vector_wrap ?? native.camlPastaFpPlonkGateVectorWrap; +const gateVectorDigest = + native.caml_pasta_fp_plonk_gate_vector_digest ?? native.camlPastaFpPlonkGateVectorDigest; +const circuitSerialize = + native.caml_pasta_fp_plonk_circuit_serialize ?? native.camlPastaFpPlonkCircuitSerialize; + const { fp } = napiConversionCore(native); const zeroField: Field = [0, 0n]; @@ -44,24 +61,24 @@ const sampleGate: Gate = [ [0, zeroField, zeroField, zeroField, zeroField, zeroField, zeroField, zeroField], ]; -const vector = native.camlPastaFpPlonkGateVectorCreate(); -expect(native.camlPastaFpPlonkGateVectorLen(vector)).toBe(0); +const vector = gateVectorCreate(); +expect(gateVectorLen(vector)).toBe(0); -native.camlPastaFpPlonkGateVectorAdd(vector, fp.gateToRust(sampleGate)); -expect(native.camlPastaFpPlonkGateVectorLen(vector)).toBe(1); +gateVectorAdd(vector, fp.gateToRust(sampleGate)); +expect(gateVectorLen(vector)).toBe(1); -const gate0 = native.camlPastaFpPlonkGateVectorGet(vector, 0); +const gate0 = gateVectorGet(vector, 0); expect(gate0.typ).toBe(sampleGate[1]); const rustTarget = fp.wireToRust(mlWire(0, 0)); const rustHead = fp.wireToRust(mlWire(1, 2)); -native.camlPastaFpPlonkGateVectorWrap(vector, rustTarget, rustHead); -const wrapped = native.camlPastaFpPlonkGateVectorGet(vector, 0); +gateVectorWrap(vector, rustTarget, rustHead); +const wrapped = gateVectorGet(vector, 0); expect(wrapped.wires.w0).toEqual({ row: 1, col: 2 }); -native.camlPastaFpPlonkGateVectorDigest(0, vector); -native.camlPastaFpPlonkCircuitSerialize(0, vector); +gateVectorDigest(0, vector); +circuitSerialize(0, vector); -console.log('{}', native.camlPastaFpPlonkGateVectorDigest(0, vector)); +console.log('{}', gateVectorDigest(0, vector)); console.log('gate vector napi bindings (fp) are working ✔️'); From e75b3f364bae9c305f98cc51523885e6ff9adea2 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 20 Nov 2025 21:19:06 +0100 Subject: [PATCH 10/11] submodule: update mina --- src/mina | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mina b/src/mina index 6189fa7b99..6d9b2f6fe2 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 6189fa7b99e90a34673ac19519cb0271816b306a +Subproject commit 6d9b2f6fe2d7f686897062637eb036f17dc41ba8 From e7c5292b92a9572434d8c0eec0f9c0beb92d5a3e Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 20 Nov 2025 21:24:12 +0100 Subject: [PATCH 11/11] submodule: update mina --- src/mina | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mina b/src/mina index 6d9b2f6fe2..3116ffcdb3 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 6d9b2f6fe2d7f686897062637eb036f17dc41ba8 +Subproject commit 3116ffcdb35038a04624112305d9ab618990c7d2