Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions src/bindings/crypto/native/bigint256.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { MlBool } from '../../../lib/ml/base.js';
import { withPrefix } from './util.js';

/**
* TS implementation of Pasta_bindings.BigInt256
*/
export {
Bigint256Bindings,
Bigint256,
toMlStringAscii,
fromMlString,
MlBytes,
mlBytesFromUint8Array,
mlBytesToUint8Array,
};

type Bigint256 = [0, bigint];

const Bigint256Bindings = withPrefix('caml_bigint_256', {
// TODO
of_numeral(s: MlBytes, i: number, j: number): Bigint256 {
throw Error('caml_bigint_256_of_numeral not implemented');
},
of_decimal_string(s: MlBytes): Bigint256 {
return [0, BigInt(fromMlString(s))];
},
num_limbs(): number {
return 4;
},
bytes_per_limb(): number {
return 8;
},
div([, x]: Bigint256, [, y]: Bigint256): Bigint256 {
return [0, x / y];
},
compare([, x]: Bigint256, [, y]: Bigint256): number {
if (x < y) return -1;
if (x === y) return 0;
return 1;
},
print([, x]: Bigint256): void {
console.log(x.toString());
},
to_string(x: Bigint256) {
return toMlStringAscii(x[1].toString());
},
// TODO performance critical
test_bit(b: Bigint256, i: number): MlBool {
return MlBool(!!(b[1] & (1n << BigInt(i))));
},
to_bytes([, x]: Bigint256) {
let ocamlBytes = caml_create_bytes(32);
for (let i = 0; i < 32; i++) {
let byte = Number(x & 0xffn);
caml_bytes_unsafe_set(ocamlBytes, i, byte);
x >>= 8n;
}
if (x !== 0n) throw Error("bigint256 doesn't fit into 32 bytes.");
return ocamlBytes;
},
of_bytes(ocamlBytes: MlBytes): Bigint256 {
let length = ocamlBytes.l;
if (length > 32) throw Error(length + " bytes don't fit into bigint256");
let x = 0n;
let bitPosition = 0n;
for (let i = 0; i < length; i++) {
let byte = caml_bytes_unsafe_get(ocamlBytes, i);
x |= BigInt(byte) << bitPosition;
bitPosition += 8n;
}
return [0, x];
},
deep_copy([, x]: Bigint256): Bigint256 {
return [0, x];
},
});

// TODO clean up all this / make type-safe and match JSOO in all relevant cases

function fromMlString(s: MlBytes) {
// TODO doesn't handle all cases
return s.c;
}
function toMlStringAscii(s: string) {
return new MlBytes(9, s, s.length);
}

function caml_bytes_unsafe_get(s: MlBytes, i: number): number {
switch (s.t & 6) {
default: /* PARTIAL */
if (i >= s.c.length) return 0;
case 0 /* BYTES */:
return s.c.charCodeAt(i);
case 4 /* ARRAY */:
return s.c[i] as any as number;
}
}

function caml_bytes_unsafe_set(s: MlBytes, i: number, c: number) {
// The OCaml compiler uses Char.unsafe_chr on integers larger than 255!
c &= 0xff;
if (s.t != 4 /* ARRAY */) {
if (i == s.c.length) {
s.c += String.fromCharCode(c);
if (i + 1 == s.l) s.t = 0; /*BYTES | UNKNOWN*/
return 0;
}
caml_convert_bytes_to_array(s);
}
// TODO
(s.c as any)[i] = c;
return 0;
}

function caml_create_bytes(len: number) {
return new MlBytes(2, '', len);
}

function caml_convert_bytes_to_array(s: MlBytes) {
/* Assumes not ARRAY */
let a = new Uint8Array(s.l);
let b = s.c,
l = b.length,
i = 0;
for (; i < l; i++) a[i] = b.charCodeAt(i);
for (l = s.l; i < l; i++) a[i] = 0;
(s as any).c = a;
// TODO
s.t = 4; /* ARRAY */
return a;
}

function mlBytesFromUint8Array(uint8array: Uint8Array | number[]) {
let length = uint8array.length;
let ocaml_bytes = caml_create_bytes(length);
for (let i = 0; i < length; i++) {
// No need to convert here: OCaml Char.t is just an int under the hood.
caml_bytes_unsafe_set(ocaml_bytes, i, uint8array[i]);
}
return ocaml_bytes;
}

function mlBytesToUint8Array(ocaml_bytes: MlBytes) {
let length = ocaml_bytes.l;
let bytes = new Uint8Array(length);
for (let i = 0; i < length; i++) {
// No need to convert here: OCaml Char.t is just an int under the hood.
bytes[i] = caml_bytes_unsafe_get(ocaml_bytes, i);
}
return bytes;
}

class MlBytes {
t: number;
c: string;
l: number;

constructor(tag: number, content: string, length: number) {
this.t = tag;
this.c = content;
this.l = length;
}

toString() {
if (this.t === 9) return this.c;
throw Error('todo');
}

toUtf16() {
return this.toString();
}

slice() {
let content = this.t == 4 ? this.c.slice() : this.c;
return new MlBytes(this.t, content, this.l);
}
}
97 changes: 97 additions & 0 deletions src/bindings/crypto/native/conversion-base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Field } from './field.js';
import { bigintToBytes32, bytesToBigint32 } from '../bigint-helpers.js';
import type {
WasmGPallas,
WasmGVesta,
WasmPallasGProjective,
WasmVestaGProjective,
} from '../../compiled/node_bindings/plonk_wasm.cjs';
import type { MlArray } from '../../../lib/ml/base.js';
import { OrInfinity, Infinity } from './curve.js';

export {
fieldToRust,
fieldFromRust,
fieldsToRustFlat,
fieldsFromRustFlat,
maybeFieldToRust,
affineToRust,
affineFromRust,
WasmAffine,
WasmProjective,
};

// TODO: Hardcoding this is a little brittle
// TODO read from field
const fieldSizeBytes = 32;

// field, field vectors

function fieldToRust([, x]: Field, dest = new Uint8Array(32)): Uint8Array {
return bigintToBytes32(x, dest);
}
function fieldFromRust(x: Uint8Array): Field {
return [0, bytesToBigint32(x)];
}

function fieldsToRustFlat([, ...fields]: MlArray<Field>): Uint8Array {
let n = fields.length;
let flatBytes = new Uint8Array(n * fieldSizeBytes);
for (let i = 0, offset = 0; i < n; i++, offset += fieldSizeBytes) {
fieldToRust(fields[i], flatBytes.subarray(offset, offset + fieldSizeBytes));
}
return flatBytes;
}

function fieldsFromRustFlat(fieldBytes: Uint8Array): MlArray<Field> {
let n = fieldBytes.length / fieldSizeBytes;
if (!Number.isInteger(n)) {
throw Error('fieldsFromRustFlat: invalid bytes');
}
let fields: Field[] = Array(n);
for (let i = 0, offset = 0; i < n; i++, offset += fieldSizeBytes) {
let fieldView = new Uint8Array(fieldBytes.buffer, offset, fieldSizeBytes);
fields[i] = fieldFromRust(fieldView);
}
return [0, ...fields];
}

function maybeFieldToRust(x?: Field): Uint8Array | undefined {
return x && fieldToRust(x);
}

// affine

type WasmAffine = WasmGVesta | WasmGPallas;

function affineFromRust<A extends WasmAffine>(pt: A): OrInfinity {
if (pt.infinity) {
pt.free();
return 0;
} else {
let x = fieldFromRust(pt.x);
let y = fieldFromRust(pt.y);
pt.free();
return [0, [0, x, y]];
}
}

const tmpBytes = new Uint8Array(32);

function affineToRust<A extends WasmAffine>(pt: OrInfinity, makeAffine: () => A) {
let res = makeAffine();
if (pt === Infinity) {
res.infinity = true;
} else {
let [, [, x, y]] = pt;
// we can use the same bytes here every time,
// because x and y setters copy the bytes into wasm memory
res.x = fieldToRust(x, tmpBytes);
res.y = fieldToRust(y, tmpBytes);
}
return res;
}

// projective

type WasmProjective = WasmVestaGProjective | WasmPallasGProjective;
123 changes: 123 additions & 0 deletions src/bindings/crypto/native/conversion-oracles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import type {
WasmFpOracles,
WasmFpRandomOracles,
WasmFqOracles,
WasmFqRandomOracles,
} from '../../compiled/node_bindings/plonk_wasm.cjs';
import type * as wasmNamespace from '../../compiled/node_bindings/plonk_wasm.cjs';
import { MlOption } from '../../../lib/ml/base.js';
import { Field, Oracles, RandomOracles, ScalarChallenge } from './kimchi-types.js';
import {
fieldFromRust,
fieldToRust,
fieldsFromRustFlat,
fieldsToRustFlat,
maybeFieldToRust,
} from './conversion-base.js';

export { oraclesConversion };

type wasm = typeof wasmNamespace;

type WasmRandomOracles = WasmFpRandomOracles | WasmFqRandomOracles;
type WasmOracles = WasmFpOracles | WasmFqOracles;

type WasmClasses = {
RandomOracles: typeof WasmFpRandomOracles | typeof WasmFqRandomOracles;
Oracles: typeof WasmFpOracles | typeof WasmFqOracles;
};

function oraclesConversion(wasm: wasm) {
return {
fp: oraclesConversionPerField({
RandomOracles: wasm.WasmFpRandomOracles,
Oracles: wasm.WasmFpOracles,
}),
fq: oraclesConversionPerField({
RandomOracles: wasm.WasmFqRandomOracles,
Oracles: wasm.WasmFqOracles,
}),
};
}

function oraclesConversionPerField({ RandomOracles, Oracles }: WasmClasses) {
function randomOraclesToRust(ro: RandomOracles): WasmRandomOracles {
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: WasmRandomOracles): 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): WasmOracles {
let [, o, pEval, openingPrechallenges, digestBeforeEvaluations] = oracles;
return new Oracles(
randomOraclesToRust(o),
fieldToRust(pEval[1]),
fieldToRust(pEval[2]),
fieldsToRustFlat(openingPrechallenges),
fieldToRust(digestBeforeEvaluations)
);
},
oraclesFromRust(oracles: WasmOracles): 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;
},
};
}
Loading
Loading