Skip to content

Commit a4a8c0c

Browse files
authored
Merge pull request #2564 from o1-labs/native/napi-gatevector
2 parents a891024 + 4e90560 commit a4a8c0c

File tree

6 files changed

+350
-41
lines changed

6 files changed

+350
-41
lines changed

src/bindings/crypto/bindings-napi.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/bindings/crypto/bindings.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@
44
* It gets imported as the first thing in ../../bindings.js so that the global variable is ready by the time JSOO code gets executed.
55
*/
66
import type * as wasmNamespace from '../compiled/node_bindings/plonk_wasm.cjs';
7-
import { prefixHashes, prefixHashesLegacy } from '../crypto/constants.js';
8-
import { bindingsNapi } from './bindings-napi.js';
9-
import { Bigint256Bindings } from './bindings/bigint256.js';
107
import { fieldsFromRustFlat, fieldsToRustFlat } from './bindings/conversion-base.js';
11-
import { conversionCore } from './bindings/conversion-core.js';
12-
import { oraclesConversion } from './bindings/conversion-oracles.js';
138
import { proofConversion } from './bindings/conversion-proof.js';
9+
import { conversionCore } from './bindings/conversion-core.js';
1410
import { verifierIndexConversion } from './bindings/conversion-verifier-index.js';
15-
import { PallasBindings, VestaBindings } from './bindings/curve.js';
11+
import { oraclesConversion } from './bindings/conversion-oracles.js';
1612
import { jsEnvironment } from './bindings/env.js';
17-
import { FpBindings, FqBindings } from './bindings/field.js';
1813
import { srs } from './bindings/srs.js';
14+
import { napiConversionCore } from './napi-conversion-core.js';
15+
import { napiProofConversion } from './napi-conversion-proof.js';
16+
import { prefixHashes, prefixHashesLegacy } from '../crypto/constants.js';
17+
import { Bigint256Bindings } from './bindings/bigint256.js';
18+
import { PallasBindings, VestaBindings } from './bindings/curve.js';
19+
import { FpBindings, FqBindings } from './bindings/field.js';
1920
import { FpVectorBindings, FqVectorBindings } from './bindings/vector.js';
2021

2122
export { createNativeRustConversion, getRustConversion, RustConversion, Wasm };
@@ -82,7 +83,12 @@ function buildWasmConversion(wasm: Wasm) {
8283
}
8384

8485
function createNativeRustConversion(napi: any) {
85-
return bindingsNapi(napi);
86+
let core = napiConversionCore(napi);
87+
let proof = napiProofConversion(napi, core);
88+
return {
89+
fp: { ...core.fp, ...proof.fp },
90+
fq: { ...core.fq, ...proof.fq },
91+
}
8692
}
8793

8894
/* TODO: Uncomment in phase 2 of conversion layer
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { expect } from 'expect';
2+
import { createRequire } from 'node:module';
3+
import { napiConversionCore } from '../napi-conversion-core.js';
4+
import type { Field, Gate, Wire } from './kimchi-types.js';
5+
6+
const require = createRequire(import.meta.url);
7+
8+
function loadNative() {
9+
const candidates = [
10+
'../../compiled/_node_bindings/plonk_napi.node',
11+
'../../compiled/node_bindings/plonk_napi.node',
12+
];
13+
for (const path of candidates) {
14+
try {
15+
// eslint-disable-next-line @typescript-eslint/no-var-requires
16+
return require(path);
17+
} catch (err) {
18+
if ((err as any).code !== 'MODULE_NOT_FOUND') throw err;
19+
}
20+
}
21+
throw new Error('plonk_napi.node not found in compiled bindings');
22+
}
23+
24+
const native: any = loadNative();
25+
26+
const { fp } = napiConversionCore(native);
27+
28+
const zeroField: Field = [0, 0n];
29+
const mlWire = (row: number, col: number): Wire => [0, row, col];
30+
31+
const sampleGate: Gate = [
32+
0,
33+
1,
34+
[
35+
0,
36+
mlWire(0, 0),
37+
mlWire(0, 1),
38+
mlWire(0, 2),
39+
mlWire(0, 3),
40+
mlWire(0, 4),
41+
mlWire(0, 5),
42+
mlWire(0, 6),
43+
],
44+
[0, zeroField, zeroField, zeroField, zeroField, zeroField, zeroField, zeroField],
45+
];
46+
47+
const vector = native.camlPastaFpPlonkGateVectorCreate();
48+
expect(native.camlPastaFpPlonkGateVectorLen(vector)).toBe(0);
49+
50+
native.camlPastaFpPlonkGateVectorAdd(vector, fp.gateToRust(sampleGate));
51+
expect(native.camlPastaFpPlonkGateVectorLen(vector)).toBe(1);
52+
53+
const gate0 = native.camlPastaFpPlonkGateVectorGet(vector, 0);
54+
expect(gate0.typ).toBe(sampleGate[1]);
55+
56+
const rustTarget = fp.wireToRust(mlWire(0, 0));
57+
const rustHead = fp.wireToRust(mlWire(1, 2));
58+
native.camlPastaFpPlonkGateVectorWrap(vector, rustTarget, rustHead);
59+
const wrapped = native.camlPastaFpPlonkGateVectorGet(vector, 0);
60+
expect(wrapped.wires.w0).toEqual({ row: 1, col: 2 });
61+
62+
native.camlPastaFpPlonkGateVectorDigest(0, vector);
63+
native.camlPastaFpPlonkCircuitSerialize(0, vector);
64+
65+
console.log('{}', native.camlPastaFpPlonkGateVectorDigest(0, vector));
66+
67+
console.log('gate vector napi bindings (fp) are working ✔️');
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import { fieldFromRust, fieldToRust, fieldsFromRustFlat, fieldsToRustFlat } from './bindings/conversion-base.js';
2+
import { Gate, OrInfinity, PolyComm, Wire, Field } from './bindings/kimchi-types.js';
3+
import { MlArray } from '../../lib/ml/base.js';
4+
import { mapTuple } from './bindings/util.js';
5+
import type * as napiNamespace from '../compiled/node_bindings/plonk_wasm.cjs';
6+
7+
export { napiConversionCore, ConversionCore, ConversionCores };
8+
9+
type ConversionCore = ReturnType<typeof conversionCorePerField>;
10+
type ConversionCores = ReturnType<typeof napiConversionCore>;
11+
12+
type NapiAffine = napiNamespace.WasmGVesta | napiNamespace.WasmGPallas;
13+
type NapiPolyComm = { unshifted: unknown; shifted?: NapiAffine | undefined };
14+
type PolyCommCtor = new (unshifted: unknown, shifted?: NapiAffine | undefined) => NapiPolyComm;
15+
16+
type NapiClasses = {
17+
CommitmentCurve: typeof napiNamespace.WasmGVesta | typeof napiNamespace.WasmGPallas;
18+
makeAffine: () => NapiAffine;
19+
PolyComm: napiNamespace.WasmFpPolyComm | napiNamespace.WasmFqPolyComm;
20+
}
21+
22+
function napiConversionCore(napi: any) {
23+
const fpCore = conversionCorePerField({
24+
CommitmentCurve: napi.WasmGVesta,
25+
makeAffine: napi.caml_vesta_affine_one,
26+
PolyComm: napi.WasmFpPolyComm,
27+
});
28+
const fqCore = conversionCorePerField({
29+
CommitmentCurve: napi.WasmGPallas,
30+
makeAffine: napi.caml_pallas_affine_one,
31+
PolyComm: napi.WasmFqPolyComm,
32+
});
33+
34+
return {
35+
fp: { ...fpCore },
36+
fq: { ...fqCore },
37+
};
38+
}
39+
40+
function conversionCorePerField({ makeAffine, PolyComm }: NapiClasses) {
41+
const vectorToRust = (fields: MlArray<Field>) => fieldsToRustFlat(fields);
42+
const vectorFromRust = fieldsFromRustFlat;
43+
44+
const wireToRust = ([, row, col]: Wire) => ({ row, col });
45+
const wireFromRust = ({ row, col }: { row: number; col: number }): Wire => [0, row, col];
46+
47+
const gateToRust = (gate: Gate) => {
48+
const [, typ, [, ...wires], coeffs] = gate;
49+
const mapped = mapTuple(wires, wireToRust);
50+
const nativeWires = {
51+
w0: mapped[0],
52+
w1: mapped[1],
53+
w2: mapped[2],
54+
w3: mapped[3],
55+
w4: mapped[4],
56+
w5: mapped[5],
57+
w6: mapped[6],
58+
} as const;
59+
return {
60+
typ,
61+
wires: nativeWires,
62+
coeffs: Array.from(fieldsToRustFlat(coeffs)),
63+
};
64+
};
65+
66+
const gateFromRust = (gate: {
67+
typ: number;
68+
wires: {
69+
w0: { row: number; col: number };
70+
w1: { row: number; col: number };
71+
w2: { row: number; col: number };
72+
w3: { row: number; col: number };
73+
w4: { row: number; col: number };
74+
w5: { row: number; col: number };
75+
w6: { row: number; col: number };
76+
};
77+
coeffs: Uint8Array | number[];
78+
}): Gate => {
79+
const { w0, w1, w2, w3, w4, w5, w6 } = gate.wires;
80+
const wiresTuple: [0, Wire, Wire, Wire, Wire, Wire, Wire, Wire] = [
81+
0,
82+
wireFromRust(w0),
83+
wireFromRust(w1),
84+
wireFromRust(w2),
85+
wireFromRust(w3),
86+
wireFromRust(w4),
87+
wireFromRust(w5),
88+
wireFromRust(w6),
89+
];
90+
const coeffBytes = gate.coeffs instanceof Uint8Array ? gate.coeffs : Uint8Array.from(gate.coeffs);
91+
const coeffs = fieldsFromRustFlat(coeffBytes);
92+
return [0, gate.typ, wiresTuple, coeffs];
93+
};
94+
95+
const affineToRust = (pt: OrInfinity): NapiAffine => {
96+
function isFinitePoint(point: OrInfinity): point is [0, [0, Field, Field]] {
97+
return Array.isArray(point);
98+
}
99+
let res = makeAffine();
100+
if (!isFinitePoint(pt)) {
101+
res.infinity = true;
102+
} else {
103+
const tmpBytes = new Uint8Array(32);
104+
const [, pair] = pt;
105+
const [, x, y] = pair;
106+
res.x = fieldToRust(x, tmpBytes);
107+
res.y = fieldToRust(y, tmpBytes);
108+
}
109+
return res;
110+
};
111+
const affineFromRust = (pt: NapiAffine): OrInfinity => {
112+
if (pt.infinity) return 0;
113+
const xField = fieldFromRust(pt.x);
114+
const yField = fieldFromRust(pt.y);
115+
return [0, [0, xField, yField]];
116+
};
117+
118+
const pointToRust = (point: OrInfinity): NapiAffine => affineToRust(point);
119+
const pointFromRust = (point: NapiAffine): OrInfinity => affineFromRust(point);
120+
121+
const pointsToRust = ([, ...points]: MlArray<OrInfinity>): NapiAffine[] => points.map(affineToRust);
122+
const pointsFromRust = (points: NapiAffine[]): MlArray<OrInfinity> => [0, ...points.map(affineFromRust)];
123+
124+
const polyCommToRust = (polyComm: PolyComm): NapiPolyComm => {
125+
const [, camlElems] = polyComm;
126+
const unshifted = pointsToRust(camlElems);
127+
const PolyCommClass = PolyComm as unknown as PolyCommCtor;
128+
return new PolyCommClass(unshifted as unknown, undefined);
129+
};
130+
131+
const polyCommFromRust = (polyComm: NapiPolyComm): PolyComm => {
132+
const rustUnshifted = asArrayLike<NapiAffine>(polyComm.unshifted, 'polyComm.unshifted');
133+
const mlUnshifted = rustUnshifted.map(affineFromRust);
134+
return [0, [0, ...mlUnshifted]];
135+
};
136+
137+
const polyCommsToRust = ([, ...comms]: MlArray<PolyComm>): NapiPolyComm[] =>
138+
comms.map(polyCommToRust);
139+
140+
const polyCommsFromRust = (rustComms: unknown): MlArray<PolyComm> => {
141+
const comms = asArrayLike<NapiPolyComm>(rustComms, 'polyCommsFromRust');
142+
return [0, ...comms.map(polyCommFromRust)];
143+
};
144+
145+
return {
146+
vectorToRust,
147+
vectorFromRust,
148+
wireToRust,
149+
gateToRust,
150+
gateFromRust,
151+
affineToRust,
152+
affineFromRust,
153+
pointToRust,
154+
pointFromRust,
155+
pointsToRust,
156+
pointsFromRust,
157+
polyCommToRust,
158+
polyCommFromRust,
159+
polyCommsToRust,
160+
polyCommsFromRust,
161+
};
162+
}
163+
164+
function asArrayLike<T>(value: unknown, context: string): T[] {
165+
if (value == null) return [];
166+
if (Array.isArray(value)) return value as T[];
167+
if (ArrayBuffer.isView(value)) return Array.from(value as unknown as ArrayLike<T>);
168+
if (typeof value === 'object' && value !== null && 'length' in (value as { length: unknown })) {
169+
const { length } = value as { length: unknown };
170+
if (typeof length === 'number') return Array.from(value as ArrayLike<T>);
171+
}
172+
throw Error(`${context}: expected array-like native values`);
173+
}

0 commit comments

Comments
 (0)