UNPKG

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
"use strict"; // /** // * 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