o1js-rsa
Version:
This repository exposes the API from the [o1js RSA example](https://github.com/o1-labs/o1js/tree/main/src/examples/crypto/rsa), making it importable and enabling RSA65537 signature verification on the Mina blockchain across various projects that utilize [
239 lines • 7.6 kB
JavaScript
// /**
// * RSA signature verification with o1js
// */
// import {
// Field,
// Gadgets,
// Provable,
// Struct,
// ZkProgram,
// provable,
// } from 'o1js';
// // import { tic, toc } from '../node_modules/o1js/src/lib/util/tic-toc';
// import { Gates } from 'o1js/dist/node/lib/gates.js';
// import { FieldConst } from 'o1js/dist/node/lib/field.js';
// import { Snarky } from 'o1js/dist/node/snarky.js';
// import { MlArray } from 'o1js/dist/node/lib/ml/base.js';
// import { bitSlice, exists } from 'o1js/dist/node/lib/gadgets/common.js';
// import { TupleN } from 'o1js/dist/node/lib/util/types.js';
// const mask = (1n << 116n) - 1n;
// /**
// * We use 116-bit limbs, which means 18 limbs for a 2048-bit numbers as used in RSA.
// */
// const Field18 = Provable.Array(Field, 18);
// class Bigint2048 extends Struct({ fields: Field18, value: BigInt }) {
// modmul(x: Bigint2048, y: Bigint2048) {
// return multiply(x, y, this);
// }
// modsquare(x: Bigint2048) {
// return multiply(x, x, this, { isSquare: true });
// }
// toBigInt() {
// return this.value;
// }
// static from(x: bigint) {
// let fields = [];
// let value = x;
// for (let i = 0; i < 18; i++) {
// fields.push(Field(x & mask));
// x >>= 116n;
// }
// return new Bigint2048({ fields, value });
// }
// static check(x: { fields: Field[] }) {
// for (let i = 0; i < 18; i++) {
// rangeCheck116(x.fields[i]);
// }
// }
// }
// /**
// * x*y mod p
// */
// function multiply(
// x: Bigint2048,
// y: Bigint2048,
// p: Bigint2048,
// { isSquare = false } = {}
// ) {
// if (isSquare) y = x;
// // witness q, r so that x*y = q*p + r
// // this also adds the range checks in `check()`
// let { q, r } = Provable.witness(
// provable({ q: Bigint2048, r: Bigint2048 }),
// () => {
// let xy = x.toBigInt() * y.toBigInt();
// let p0 = p.toBigInt();
// let q = xy / p0;
// let r = xy - q * p0;
// return { q: Bigint2048.from(q), r: Bigint2048.from(r) };
// }
// );
// // compute res = xy - qp - r
// // we can use a sum of native field products for each result limb, because
// // input limbs are range-checked to 116 bits, and 2*116 + log(2*18-1) = 232 + 6 fits the native field.
// let res: Field[] = Array.from({ length: 2 * 18 - 1 }, () => Field(0));
// let [X, Y, Q, R, P] = [x.fields, y.fields, q.fields, r.fields, p.fields];
// for (let i = 0; i < 18; i++) {
// // when squaring, we can save constraints by not computing xi * xj twice
// if (isSquare) {
// for (let j = 0; j < i; j++) {
// res[i + j] = res[i + j].add(X[i].mul(X[j]).mul(2n));
// }
// res[2 * i] = res[2 * i].add(X[i].mul(X[i]));
// } else {
// for (let j = 0; j < 18; j++) {
// res[i + j] = res[i + j].add(X[i].mul(Y[j]));
// }
// }
// for (let j = 0; j < 18; j++) {
// res[i + j] = res[i + j].sub(Q[i].mul(P[j]));
// }
// res[i] = res[i].sub(R[i]);
// }
// // perform carrying on res to show that it is zero
// let carry = Field(0);
// for (let i = 0; i < 2 * 18 - 2; i++) {
// let res_i = res[i].add(carry);
// [carry] = witnessFields(1, () => {
// let res_in = res_i.toBigInt();
// if (res_in > 1n << 128n) res_in -= Field.ORDER;
// return [res_in >> 116n];
// });
// rangeCheck128Signed(carry);
// // (xy - qp - r)_i + c_(i-1) === c_i * 2^116
// // proves that bits i*116 to (i+1)*116 of res are zero
// res_i.assertEquals(carry.mul(1n << 116n));
// }
// // last carry is 0 ==> all of res is 0 ==> x*y = q*p + r as integers
// res[2 * 18 - 2].add(carry).assertEquals(0n);
// return r;
// }
// /**
// * RSA signature verification
// *
// * TODO this is a bit simplistic; according to RSA spec, message must be 256 bits
// * and the remaining bits must follow a specific pattern.
// */
// function rsaVerify65537(
// message: Bigint2048,
// signature: Bigint2048,
// modulus: Bigint2048
// ) {
// // compute signature^(2^16 + 1) mod modulus
// // square 16 times
// let x = signature;
// for (let i = 0; i < 16; i++) {
// x = modulus.modsquare(x);
// }
// // multiply by signature
// x = modulus.modmul(x, signature);
// // check that x == message
// Provable.assertEqual(Bigint2048, message, x);
// }
// /**
// * Custom range check for a single limb, x in [0, 2^116)
// */
// function rangeCheck116(x: Field) {
// let [x0, x1] = witnessFields(2, () => [
// x.toBigInt() & ((1n << 64n) - 1n),
// x.toBigInt() >> 64n,
// ]);
// Gadgets.rangeCheck64(x0);
// let [x52] = rangeCheck64(x1);
// x52.assertEquals(0n); // => x1 is 52 bits
// // 64 + 52 = 116
// x0.add(x1.mul(1n << 64n)).assertEquals(x);
// }
// /**
// * Custom range check for carries, x in [-2^127, 2^127)
// */
// function rangeCheck128Signed(xSigned: Field) {
// let x = xSigned.add(1n << 127n);
// let [x0, x1] = witnessFields(2, () => [
// x.toBigInt() & ((1n << 64n) - 1n),
// x.toBigInt() >> 64n,
// ]);
// Gadgets.rangeCheck64(x0);
// Gadgets.rangeCheck64(x1);
// x0.add(x1.mul(1n << 64n)).assertEquals(x);
// }
// let rsa = ZkProgram({
// name: 'rsa-verify',
// methods: {
// verify: {
// privateInputs: [Bigint2048, Bigint2048, Bigint2048],
// method(message: Bigint2048, signature: Bigint2048, modulus: Bigint2048) {
// rsaVerify65537(message, signature, modulus);
// },
// },
// },
// });
// let { verify } = rsa.analyzeMethods();
// console.log(verify.summary());
// console.log('rows', verify.rows);
// // tic('compile');
// await rsa.compile();
// // toc();
// function witnessFields<N extends number, C extends () => TupleN<bigint, N>>(
// n: N,
// compute: C
// ) {
// let varsMl = Snarky.exists(n, () =>
// MlArray.mapTo(compute(), FieldConst.fromBigint)
// );
// let vars = MlArray.mapFrom(varsMl, (v) => new Field(v));
// return TupleN.fromArray(n, vars);
// }
// /**
// * Asserts that x is in the range [0, 2^64).
// *
// * Returns the 4 highest 12-bit limbs of x in reverse order: [x52, x40, x28, x16].
// */
// function rangeCheck64(x: Field): TupleN<Field, 4> {
// if (x.isConstant()) {
// let xx = x.toBigInt();
// if (xx >= 1n << 64n) {
// throw Error(`rangeCheck64: expected field to fit in 64 bits, got ${x}`);
// }
// // returned for consistency with the provable case
// return [
// new Field(bitSlice(xx, 52, 12)),
// new Field(bitSlice(xx, 40, 12)),
// new Field(bitSlice(xx, 28, 12)),
// new Field(bitSlice(xx, 16, 12)),
// ];
// }
// // crumbs (2-bit limbs)
// let [x0, x2, x4, x6, x8, x10, x12, x14] = exists(8, () => {
// let xx = x.toBigInt();
// return [
// bitSlice(xx, 0, 2),
// bitSlice(xx, 2, 2),
// bitSlice(xx, 4, 2),
// bitSlice(xx, 6, 2),
// bitSlice(xx, 8, 2),
// bitSlice(xx, 10, 2),
// bitSlice(xx, 12, 2),
// bitSlice(xx, 14, 2),
// ];
// });
// // 12-bit limbs
// let [x16, x28, x40, x52] = exists(4, () => {
// let xx = x.toBigInt();
// return [
// bitSlice(xx, 16, 12),
// bitSlice(xx, 28, 12),
// bitSlice(xx, 40, 12),
// bitSlice(xx, 52, 12),
// ];
// });
// Gates.rangeCheck0(
// x,
// [new Field(0), new Field(0), x52, x40, x28, x16],
// [x14, x12, x10, x8, x6, x4, x2, x0],
// false // not using compact mode
// );
// return [x52, x40, x28, x16];
// }
//# sourceMappingURL=trial.js.map
;