@silvana-one/mina-utils
Version:
Silvana Mina Utils
106 lines • 5.36 kB
JavaScript
/// Optimized scalar exponentiation using lookup tables
import { getR, getTable0Entry, getTable1Entry, getTable2Entry, } from "./constants.js";
import { UInt32, Field, Gadgets, Provable, assert } from "o1js";
import { WITNESSES0, WITNESSES1, WITNESSES2, TABLE0_ROOT, TABLE1_ROOT, TABLE2_ROOT, } from "./witnesses.js";
import { Witness } from "./tree.js";
import { deserializeFields } from "../utils/index.js";
import { blsCommitment } from "./commitment.js";
import { Fr } from "./constants.js";
/// Optimized exponentiation using three 1024-element lookup tables
/// Computes R^exp using base-1024 decomposition:
/// exp = i0 + 1024*i1 + 1024^2*i2
/// R^exp = TABLE2[i2] * TABLE1[i1] * TABLE0[i0]
///
/// Time complexity: O(1) - constant time with 3 table lookups + 2 multiplications
/// Space complexity: 3 * 1024 * 32 bytes = 96 KiB for all tables
/// Range: supports exponents up to 1024^3 - 1 = 1,073,741,823
export function rScalarPow(exp) {
const expNum = typeof exp === "bigint" ? Number(exp) : exp;
if (expNum < 0 || expNum >= 1024 ** 3) {
throw new Error(`Exponent out of range: ${expNum}`);
}
// Decompose exponent in base-1024 (10 bits per component)
const i0 = expNum & 0x3ff; // exp mod 1024 (lowest 10 bits)
const i1 = (expNum >> 10) & 0x3ff; // next 10 bits
const i2 = (expNum >> 20) & 0x3ff; // highest 10 bits
// Constant-time table lookups
const t0 = getTable0Entry(i0);
const t1 = getTable1Entry(i1);
const t2 = getTable2Entry(i2);
// Combine results with 2 field multiplications
let result = t2.mul(t1).assertCanonical(); // R^(1024^2*i2 + 1024*i1)
result = result.mul(t0).assertCanonical(); // + i0
return result;
}
/// Provable version of scalar exponentiation for use in ZkPrograms
/// Uses o1js gadgets for bitwise operations instead of JavaScript operators
export function rScalarPowProvable(exp) {
// Range check to ensure exponent is within valid bounds
assert(exp.lessThan(UInt32.from(1024 * 1024 * 1024)), "Exponent out of range (non-provable check)");
// Decompose exponent in base-1024 using provable bitwise operations
// Each component is 10 bits, so we extract 3 components of 10 bits each
// Create bitmask for 10 bits (0x3FF = 1023)
const mask = Field(0x3ff);
// Extract components using provable bitwise operations
const i0 = Gadgets.and(exp.value, mask, 10); // exp & 0x3FF (lowest 10 bits)
const shifted10 = Gadgets.rightShift64(exp.value, 10); // exp >> 10
const i1 = Gadgets.and(shifted10, mask, 10); // (exp >> 10) & 0x3FF
const shifted20 = Gadgets.rightShift64(exp.value, 20); // exp >> 20
const i2 = Gadgets.and(shifted20, mask, 10); // (exp >> 20) & 0x3FF
// Convert Field indices to witness values for table lookups
// TODO: use lookup tables instead of witness to make code fully provable
// or add all TABLEX to Merkle tree and check the value in the tree
const t0 = Provable.witness(Fr.Canonical.provable, () => {
const idx = Number(i0.toBigInt());
return getTable0Entry(idx);
});
const witness0 = Provable.witness(Witness, () => {
const idx = Number(i0.toBigInt());
return Witness.fromFields(deserializeFields(WITNESSES0[idx]));
});
const root0 = Field(TABLE0_ROOT);
const witness0root = witness0.calculateRoot(blsCommitment(t0));
const witness0index = witness0.calculateIndex();
witness0root.assertEquals(root0, "Witness0 root should match root0");
witness0index.assertEquals(i0, "Witness0 index should match i0");
const t1 = Provable.witness(Fr.Canonical.provable, () => {
const idx = Number(i1.toBigInt());
return getTable1Entry(idx);
});
const witness1 = Provable.witness(Witness, () => {
const idx = Number(i1.toBigInt());
return Witness.fromFields(deserializeFields(WITNESSES1[idx]));
});
const root1 = Field(TABLE1_ROOT);
const witness1root = witness1.calculateRoot(blsCommitment(t1));
const witness1index = witness1.calculateIndex();
witness1root.assertEquals(root1, "Witness1 root should match root1");
witness1index.assertEquals(i1, "Witness1 index should match i1");
const t2 = Provable.witness(Fr.Canonical.provable, () => {
const idx = Number(i2.toBigInt());
return getTable2Entry(idx);
});
const witness2 = Provable.witness(Witness, () => {
const idx = Number(i2.toBigInt());
return Witness.fromFields(deserializeFields(WITNESSES2[idx]));
});
const root2 = Field(TABLE2_ROOT);
const witness2root = witness2.calculateRoot(blsCommitment(t2));
const witness2index = witness2.calculateIndex();
witness2root.assertEquals(root2, "Witness2 root should match root2");
witness2index.assertEquals(i2, "Witness2 index should match i2");
// Combine results with 2 field multiplications
let result = t2.mul(t1).assertCanonical(); // R^(1024^2*i2 + 1024*i1)
result = result.mul(t0).assertCanonical(); // + i0
return result;
}
/// Legacy function (inefficient, use rScalarPow instead)
export function rScalarPowLegacy(exp) {
let acc = Fr.from(1n); // Start with 1
const r = getR(); // Get R when needed, not at startup
for (let i = 0; i < exp; i++) {
acc = acc.mul(r).assertCanonical();
}
return acc;
}
//# sourceMappingURL=exp.js.map