o1js
Version:
TypeScript framework for zk-SNARKs and zkApps
423 lines (391 loc) • 15.9 kB
text/typescript
import { MlArray, MlOption, MlTuple } from '../../../lib/ml/base.js';
import {
fieldFromRust,
fieldToRust,
fieldsFromRustFlat,
fieldsToRustFlat,
} from '../bindings/conversion-base.js';
import {
pointEvalsOptionFromRust,
pointEvalsOptionToRust,
proofEvaluationsFromRust,
proofEvaluationsToRust,
} from '../bindings/conversion-proof-shared.js';
import type {
LookupCommitments,
LookupTable,
OpeningProof,
OrInfinity,
PointEvaluations,
PolyComm,
ProofEvaluations,
ProofWithPublic,
ProverCommitments,
ProverProof,
RecursionChallenge,
RuntimeTable,
RuntimeTableCfg,
} from '../bindings/kimchi-types.js';
import { ConversionCore, ConversionCores } from './napi-conversion-core.js';
import { asArrayLike, castCtor, type Ctor } from './napi-ffi.js';
import type {
Napi,
NapiLookupCommitments,
NapiLookupTable,
NapiOpeningProof,
NapiPoint,
NapiPointEvaluationsObject,
NapiPointEvaluationsObjectOption,
NapiPoints,
NapiPolyComm,
NapiPolyComms,
NapiProofClasses,
NapiProofEvaluationsObject,
NapiProverCommitments,
NapiProverProof,
NapiRuntimeTable,
NapiRuntimeTableCfg,
NapiVecVec,
} from './napi-wrappers.js';
export { napiProofConversion };
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,
LookupTable: napi.WasmPastaFpLookupTable,
}),
fq: proofConversionPerField(core.fq, {
ProverCommitments: napi.WasmFqProverCommitments,
OpeningProof: napi.WasmFqOpeningProof,
VecVec: napi.WasmVecVecFq,
ProverProof: napi.WasmFqProverProof,
LookupCommitments: napi.WasmFqLookupCommitments,
LookupTable: napi.WasmPastaFqLookupTable,
}),
};
}
function proofConversionPerField(
core: ConversionCore,
{
ProverCommitments,
OpeningProof,
VecVec,
ProverProof,
LookupCommitments,
LookupTable,
}: NapiProofClasses
) {
type NapiProverCommitmentsCtor = Ctor<
[NapiPolyComms, NapiPolyComm, NapiPolyComm, NapiLookupCommitments | undefined],
NapiProverCommitments
>;
type NapiOpeningProofCtor = Ctor<
[NapiPoints, NapiPoints, NapiPoint, Uint8Array, Uint8Array, NapiPoint],
NapiOpeningProof
>;
type NapiLookupCommitmentsCtor = Ctor<
[NapiPolyComms, NapiPolyComm, NapiPolyComm | undefined],
NapiLookupCommitments
>;
type NapiProverProofCtor = Ctor<
[
NapiProverCommitments,
NapiOpeningProof,
NapiProofEvaluationsObject,
Uint8Array,
Uint8Array,
NapiVecVec,
NapiPolyComms,
],
NapiProverProof
>;
const ProverCommitmentsCtor = castCtor<NapiProverCommitmentsCtor>(ProverCommitments);
const OpeningProofCtor = castCtor<NapiOpeningProofCtor>(OpeningProof);
const LookupCommitmentsCtor = castCtor<NapiLookupCommitmentsCtor>(LookupCommitments);
const ProverProofCtor = castCtor<NapiProverProofCtor>(ProverProof);
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 ProverCommitmentsCtor(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);
// Normalize optional lookup to an MlOption; mapTo expects a value or undefined.
let lookup = MlOption.mapTo(
commitments.lookup ?? undefined,
(lk) => lookupCommitmentsFromRust(lk)!
);
return [0, wComm as MlTuple<PolyComm, 15>, 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 LookupCommitmentsCtor(sorted, aggreg, runtime);
}
function lookupCommitmentsFromRust(
lookup: NapiLookupCommitments | null | undefined
): LookupCommitments | undefined {
if (lookup == null) return undefined;
let sorted = core.polyCommsFromRust(lookup.sorted);
let aggreg = core.polyCommFromRust(lookup.aggreg);
let runtime = MlOption.mapTo(lookup.runtime ?? undefined, core.polyCommFromRust);
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<OrInfinity> = [0];
let r: MlArray<OrInfinity> = [0];
for (let [, li, ri] of lr) {
l.push(li);
r.push(ri);
}
return new OpeningProofCtor(
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);
return [0, [0, ...lr], delta, z1, z2, sg];
}
function runtimeTableToRust([, id, data]: RuntimeTable): NapiRuntimeTable {
return { id, data: core.vectorToRust(data) };
}
function runtimeTableCfgToRust([, id, firstColumn]: RuntimeTableCfg): NapiRuntimeTableCfg {
return { id, firstColumn: core.vectorToRust(firstColumn) };
}
function lookupTableToRust([, id, [, ...data]]: LookupTable): NapiLookupTable {
let n = data.length;
let wasmData = new VecVec(n);
for (let i = 0; i < n; i++) {
wasmData.push(fieldsToRustFlat(data[i]));
}
return new LookupTable(id, wasmData);
}
return {
proofToRust([, public_evals, proof]: ProofWithPublic): NapiProverProof {
let commitments = commitmentsToRust(proof[1]);
let openingProof = openingProofToRust(proof[2]);
// NAPI expects proof evaluations as a plain object (camelCase keys), not an OCaml tuple.
// This matches the `#[napi(object)] Wasm*ProofEvaluationsObject` accepted by Rust via
// `FromNapiValue` for `Wasm*ProofEvaluations`.
const evalsTuple = proofEvaluationsToRust(proof[3]);
const publicEvalsTuple = pointEvalsOptionToRust(public_evals);
const pointEvalsTupleToObject = (
pe: PointEvaluations<Uint8Array>
): NapiPointEvaluationsObject => {
const [, zeta, zeta_omega] = pe;
return {
zeta: MlArray.from(zeta),
zetaOmega: MlArray.from(zeta_omega),
};
};
const optionPointEvalsToObject = (
opt: MlOption<PointEvaluations<Uint8Array>>
): NapiPointEvaluationsObjectOption => {
if (opt == null || opt === 0) return undefined;
return pointEvalsTupleToObject(opt[1]);
};
const publicObj =
publicEvalsTuple === 0 ? undefined : pointEvalsTupleToObject(publicEvalsTuple[1]);
const [
,
w,
z,
s,
coefficients,
genericSelector,
poseidonSelector,
completeAddSelector,
mulSelector,
emulSelector,
endomulScalarSelector,
rangeCheck0Selector,
rangeCheck1Selector,
foreignFieldAddSelector,
foreignFieldMulSelector,
xorSelector,
rotSelector,
lookupAggregation,
lookupTable,
lookupSorted,
runtimeLookupTable,
runtimeLookupTableSelector,
xorLookupSelector,
lookupGateLookupSelector,
rangeCheckLookupSelector,
foreignFieldMulLookupSelector,
] = evalsTuple;
const lookupSortedObjs = MlArray.from(lookupSorted).map((opt) =>
opt === 0 ? undefined : pointEvalsTupleToObject(opt[1])
);
const evalsActual: NapiProofEvaluationsObject = {
public: publicObj,
w: MlTuple.mapFrom(w, pointEvalsTupleToObject),
z: pointEvalsTupleToObject(z),
s: MlTuple.mapFrom(s, pointEvalsTupleToObject),
coefficients: MlTuple.mapFrom(coefficients, pointEvalsTupleToObject),
genericSelector: pointEvalsTupleToObject(genericSelector),
poseidonSelector: pointEvalsTupleToObject(poseidonSelector),
completeAddSelector: pointEvalsTupleToObject(completeAddSelector),
mulSelector: pointEvalsTupleToObject(mulSelector),
emulSelector: pointEvalsTupleToObject(emulSelector),
endomulScalarSelector: pointEvalsTupleToObject(endomulScalarSelector),
rangeCheck0Selector: optionPointEvalsToObject(rangeCheck0Selector),
rangeCheck1Selector: optionPointEvalsToObject(rangeCheck1Selector),
foreignFieldAddSelector: optionPointEvalsToObject(foreignFieldAddSelector),
foreignFieldMulSelector: optionPointEvalsToObject(foreignFieldMulSelector),
xorSelector: optionPointEvalsToObject(xorSelector),
rotSelector: optionPointEvalsToObject(rotSelector),
lookupAggregation: optionPointEvalsToObject(lookupAggregation),
lookupTable: optionPointEvalsToObject(lookupTable),
lookupSorted: lookupSortedObjs,
runtimeLookupTable: optionPointEvalsToObject(runtimeLookupTable),
runtimeLookupTableSelector: optionPointEvalsToObject(runtimeLookupTableSelector),
xorLookupSelector: optionPointEvalsToObject(xorLookupSelector),
lookupGateLookupSelector: optionPointEvalsToObject(lookupGateLookupSelector),
rangeCheckLookupSelector: optionPointEvalsToObject(rangeCheckLookupSelector),
foreignFieldMulLookupSelector: optionPointEvalsToObject(foreignFieldMulLookupSelector),
};
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<PolyComm> = [0];
for (let [, scalars, comms] of prevChallenges) {
prevChallengeScalars.push(fieldsToRustFlat(scalars));
prevChallengeCommsMl.push(comms);
}
let prevChallengeComms = core.polyCommsToRust(prevChallengeCommsMl);
return new ProverProofCtor(
commitments,
openingProof,
evalsActual,
ftEval1,
public_,
prevChallengeScalars,
prevChallengeComms
);
},
proofFromRust(napiProof: NapiProverProof): ProofWithPublic {
// If we received the full prover proof (with commitments field), use it directly.
// Otherwise fall back to an older wrapper shape `{ proof, public_input }`.
let commitments = commitmentsFromRust(napiProof.commitments);
let openingProof = openingProofFromRust(napiProof.proof);
// NAPI returns `evals` as an object with getters; convert it into the OCaml tuple shape
// expected by `proofEvaluationsFromRust`.
const evalsSource = napiProof.evals as NapiProofEvaluationsObject | undefined;
const toPointEvals = (
pe?: NapiPointEvaluationsObject | null
): PointEvaluations<Uint8Array> => {
const zeta = MlArray.to(asArrayLike<Uint8Array>(pe?.zeta));
const zetaOmega = MlArray.to(asArrayLike<Uint8Array>(pe?.zetaOmega));
return [0, zeta, zetaOmega];
};
const toMlOption = <T>(
value: T | null | undefined,
f: (x: T) => PointEvaluations<Uint8Array>
) => MlOption.mapTo(value ?? undefined, f);
const publicEvalsBytes = toMlOption(evalsSource?.public, toPointEvals);
const publicEvals = pointEvalsOptionFromRust(publicEvalsBytes);
const w = MlArray.to(
asArrayLike<NapiPointEvaluationsObject | null | undefined>(evalsSource?.w).map(toPointEvals)
) as MlTuple<PointEvaluations<Uint8Array>, 15>;
const z = toPointEvals(evalsSource?.z);
const s = MlArray.to(
asArrayLike<NapiPointEvaluationsObject | null | undefined>(evalsSource?.s).map(toPointEvals)
) as MlTuple<PointEvaluations<Uint8Array>, 6>;
const coefficients = MlArray.to(
asArrayLike<NapiPointEvaluationsObject | null | undefined>(evalsSource?.coefficients).map(
toPointEvals
)
) as MlTuple<PointEvaluations<Uint8Array>, 15>;
const lookupSorted = MlArray.map(
[
0,
...asArrayLike<NapiPointEvaluationsObject | null | undefined>(evalsSource?.lookupSorted),
] as MlArray<NapiPointEvaluationsObject | null | undefined>,
(x) => toMlOption(x, toPointEvals)
);
const evalsBytes: ProofEvaluations<Uint8Array> = [
0,
w,
z,
s,
coefficients,
toPointEvals(evalsSource?.genericSelector),
toPointEvals(evalsSource?.poseidonSelector),
toPointEvals(evalsSource?.completeAddSelector),
toPointEvals(evalsSource?.mulSelector),
toPointEvals(evalsSource?.emulSelector),
toPointEvals(evalsSource?.endomulScalarSelector),
toMlOption(evalsSource?.rangeCheck0Selector, toPointEvals),
toMlOption(evalsSource?.rangeCheck1Selector, toPointEvals),
toMlOption(evalsSource?.foreignFieldAddSelector, toPointEvals),
toMlOption(evalsSource?.foreignFieldMulSelector, toPointEvals),
toMlOption(evalsSource?.xorSelector, toPointEvals),
toMlOption(evalsSource?.rotSelector, toPointEvals),
toMlOption(evalsSource?.lookupAggregation, toPointEvals),
toMlOption(evalsSource?.lookupTable, toPointEvals),
lookupSorted,
toMlOption(evalsSource?.runtimeLookupTable, toPointEvals),
toMlOption(evalsSource?.runtimeLookupTableSelector, toPointEvals),
toMlOption(evalsSource?.xorLookupSelector, toPointEvals),
toMlOption(evalsSource?.lookupGateLookupSelector, toPointEvals),
toMlOption(evalsSource?.rangeCheckLookupSelector, toPointEvals),
toMlOption(evalsSource?.foreignFieldMulLookupSelector, toPointEvals),
];
const evals = proofEvaluationsFromRust(evalsBytes);
let ftEval1 = fieldFromRust(napiProof.ft_eval1);
let public_ = fieldsFromRustFlat(napiProof.public_);
let prevChallengeScalars = napiProof.prev_challenges_scalars;
let [, ...prevChallengeComms] = core.polyCommsFromRust(napiProof.prev_challenges_comms);
let prevChallenges = prevChallengeComms.map<RecursionChallenge>((comms, i) => {
let scalars = fieldsFromRustFlat(prevChallengeScalars.get(i));
return [0, scalars, comms];
});
let proof: ProverProof = [
0,
commitments,
openingProof,
evals,
ftEval1,
public_,
[0, ...prevChallenges],
];
return [0, publicEvals, proof];
},
runtimeTablesToRust([, ...tables]: MlArray<RuntimeTable>): NapiRuntimeTable[] {
return tables.map(runtimeTableToRust);
},
runtimeTableCfgsToRust([, ...tableCfgs]: MlArray<RuntimeTableCfg>): NapiRuntimeTableCfg[] {
return tableCfgs.map(runtimeTableCfgToRust);
},
lookupTablesToRust([, ...tables]: MlArray<LookupTable>): NapiLookupTable[] {
return tables.map(lookupTableToRust);
},
};
}