@zktools/fields
v0.1.0
Published
Limb-based BN254 finite field arithmetic in Montgomery form — pure JavaScript, zero runtime dependencies
Downloads
19
Maintainers
Readme
@zktools/fields
BN254 finite field arithmetic in pure JavaScript — no WASM, no native addons, no eval.
Implements Fr (scalar field), Fp (base field), and Fp2 (quadratic extension) using a 10 × 26-bit limb representation in Montgomery form (CIOS). Zero runtime dependencies.
Status: experimental. Not production-hardened, not audited. Built to find the JS runtime ceiling for this class of problem.
Install
npm install @zktools/fieldsQuick example
import { Fr, Fp, Fp2, batchInverse } from "@zktools/fields";
const a = Fr.fromBigInt(12345n);
const b = Fr.fromBigInt(67890n);
Fr.toBigInt(Fr.mul(a, b));
Fr.toBigInt(Fr.inv(a));
batchInverse([a, b], Fr); // 3(n-1) muls + 1 invZero-alloc offset API
Fr and Fp expose mulInto, sqrInto, addInto, subInto. These operate inside a pre-allocated FieldBuf with no per-op allocation. Use this when throughput matters and you can manage buffer lifetimes manually.
import { Fr, allocFieldBuf, writeElement, readElement, ELEMENT_STRIDE } from "@zktools/fields";
const buf = allocFieldBuf(3); // slots 0, 1, 2
writeElement(buf, 1, Fr.fromBigInt(111n));
writeElement(buf, 2, Fr.fromBigInt(222n));
// slot 1 × slot 2 → slot 0, no allocation
Fr.mulInto(buf, 0 * ELEMENT_STRIDE, 1 * ELEMENT_STRIDE, 2 * ELEMENT_STRIDE);
Fr.toBigInt(readElement(buf, 0));Note:
addIntoandsubIntoproduce values in[0, 2p). CalltoBigIntat boundaries to normalise.
Performance
Measured with tinybench on Node.js 22 (2 s per task, 1 k warmup iterations).
Standard API
| Operation | ops/sec | |-----------|--------:| | Fr.mul | 804,000 | | Fr.sqr | 502,000 | | Fr.inv | 1,920 | | Fr.add | 3,360,000 | | Fr.sub | 3,550,000 | | Fp.mul | 796,000 | | Fp.sqr | 517,000 | | Fp.inv | 2,014 | | Fp2.mul | 187,000 |
Offset API (zero allocation)
| Operation | ops/sec | |-----------|--------:| | Fr.mulInto | 1,083,000 | | Fr.sqrInto | 553,000 | | Fr.addInto | 9,269,000 |
When to use
- Serverless / embedded environments where WASM is unavailable or restricted. Any JS runtime works.
- Batch inversion over Fr or Fp without pulling in a full curve library.
@noble/curvesexposes no equivalent.
API
See docs/api.md for full reference.
Exports
| Export | Description |
|--------|-------------|
| Fr | BN254 scalar field |
| Fp | BN254 base field |
| Fp2 | Quadratic extension Fp[u] / (u² + 1) |
| batchInverse(els, field) | 3(n-1) muls + 1 inv across a batch |
| makeField(m, mu0, r2, mulKernel, sqrKernel) | Custom modulus field |
| allocFieldBuf(n) | Allocate a flat buffer for n elements |
| readElement(buf, i) | Zero-copy subarray view of element i |
| writeElement(buf, i, el) | Write element i into buf |
| ELEMENT_STRIDE | Limbs per element (10) |
Fr and Fp implement FieldOps. Fp2 implements Fp2Ops.
Environments
Node ≥ 20, browser (ESM + CJS), Cloudflare Workers, Deno, Bun, Hermes.
Internals
Implementation notes and algorithm derivations:
docs/montgomery.md— CIOS Montgomery multiplication, w=26 limb choice, squaring optimisationdocs/inversion.md— Fermat inversion via addition-squaring chains, slot allocationdocs/fp2.md— Fp2 extension field, Karatsuba reduction, memory layoutdocs/batch-inverse.md— Montgomery trick cost derivation
License
MIT
