alg-bn
v0.4.2
Published
Baretto-Naehrig (BN) curve arithmetic, defaulting to the alt_bn128 (BN254) curve used by Ethereum precompiles. Built on top of [`alg-field`](https://www.npmjs.com/package/alg-field).
Readme
alg-bn
Baretto-Naehrig (BN) curve arithmetic, defaulting to the alt_bn128 (BN254) curve
used by Ethereum precompiles. Built on top of alg-field.
- Package: https://www.npmjs.com/package/alg-bn
- Scoped mirror: https://www.npmjs.com/package/@wa1one/alg-bn
Install
npm install alg-bnUsage
const { BN128Fp, BN128Fp2 } = require('alg-bn')
const G = BN128Fp.create(1n, 2n)
G.double().eq(G.add(G)) // true
G.isOnCurve() // trueAPI
BN128Fp
A point on the base curve y² = x³ + 3 over Fp, held in Jacobian
coordinates (x, y, z).
new BN128Fp(x, y, z)—x/y/zmust beFieldinstances (fromalg-field); otherwise the point is left uninitialized.static n— the curve order (bigint).static B— the curve coefficient,Field(3n).static ZERO— the point at infinity,(0, 0, 0).static create(x, y)— builds a point from rawbigintaffine coordinates withz = 1. ReturnsBN128Fp.ZEROfor(0, 0),nullif the coordinates aren't on the curve, or the point otherwise..add(o)— Jacobian point addition..double()— Jacobian point doubling..multiply(s)— scalar multiplication by abigint..neg()— the additive inverse (negatesy)..isZero()—trueifzis0(point at infinity)..isOnCurve()— checksy² = x³ + b·z⁶..isValid()—trueif the coordinates areFieldinstances and the point is on the curve..toAffine()— normalizeszto1(or(0, 1, 0)for the point at infinity)..toEthNotation()— like.toAffine(), but encodes the point at infinity as(0, 0, 0)instead of(0, 1, 0)..eq(o)— comparesx,y,zdirectly (not affine-normalized); two Jacobian representations of the same affine point can compare unequal unless you call.toAffine()first..toString()—"x, y, z".
BN128Fp2
A point on the sextic twist curve over Fp2, mirroring BN128Fp but with
Fp2 coordinates. Same Jacobian-coordinate caveats apply to .eq().
new BN128Fp2(x, y, z)—x/y/zmust beFp2instances.static n— the curve order (bigint).static ZERO— the point at infinity,(0, 0, 0).static TWIST— the twist parameter,Fp2(9n, 1n).static B_Fp2— the twisted curve coefficient.static create(a, b, c, d)— builds a point from rawbigintaffine coordinatesx = a + b·u,y = c + d·u, withz = 1. ReturnsBN128Fp2.ZEROfor all-zero input,nullif not on the curve, or the point otherwise..add(o)— Jacobian point addition..double()— Jacobian point doubling..multiply(s)— scalar multiplication;sis coerced tobigintif it isn't one already..isZero()—trueifzis0..isOnCurve()— checksy² = x³ + B_Fp2·z⁶..isValid()—trueif the coordinates areFp2instances and the point is on the curve..toAffine()— normalizeszto1(or(0, 1, 0)for the point at infinity)..toEthNotation()— like.toAffine(), but encodes the point at infinity as(0, 0, 0)..mulByP()— applies the Frobenius twist (used when pairing with the base-field group)..eq(o)— comparesx,y,zdirectly; see theBN128Fpnote above..toString()—"[x, y, z]", where each coordinate is itself"a, b".
Curve, Curve2, Point, Point2, Point12
A second, affine-coordinate point representation, built on alg-field's
tunable Fp2 tower rather than Field/Fp2 directly. Unlike BN128Fp/
BN128Fp2, .eq() here compares canonical affine coordinates directly, so
points computed via different code paths (e.g. .multiply() vs. repeated
.add()) compare equal without any extra normalization step. This is the
family to use if you need a curve other than the BN254 default — nothing is
hardcoded inside Curve/Curve2; every curve-specific constant (generator,
b coefficient) comes from the params object you pass in.
const { Curve, Curve2, Bls12381Parameters } = require('alg-bn')
const curve = new Curve() // defaults to BN254 (Bn254Parameters)
curve.contains(curve.G) // true
curve.G.multiply(3n).eq(curve.G.add(curve.G.double())) // true
// Any curve with a matching params shape works, e.g. the bundled BLS12-381:
const blsCurve = new Curve(Bls12381Parameters)
const blsCurve2 = new Curve2(blsCurve)
blsCurve2.contains(blsCurve2.Gt) // trueBn254Parameters,Bls12381Parameters— ready-made params objects forCurve/Curve2, each bundling{ p, n, fp2Params, xiRe, twistType, b, Gx, Gy, G2b, G2x, G2y }: the field modulus and subgroup order, the derivedFp2tower params everyFp2value on that curve must share, the real part of the curve's Fp6 sextic non-residue and its twist type ('D'for BN254,'M'for BLS12-381 — see thePoint12note below), and the base (G1) and twisted (G2) curve coefficients/generators. Named distinctly fromalg-field's ownParameters/Bls12381Parameters(which are field-only,{ p, n }) to avoid a collision when both packages are required together. To use a curve that isn't bundled, build an object with this same shape —fp2Paramscomes fromalg-field'sderiveFp2Params(p).Curve— the base curvey² = x³ + boverFp.new Curve(bn = Bn254Parameters)exposes.G(the generator, aPoint),.infinity(the identityPoint), and.contains(P).Curve2— the sextic twist curve (extends Curve).new Curve2(E = new Curve())takes a baseCurveinstance and reads its twist coefficient and generator directly fromE.bn.G2b/G2x/G2y— no curve-specific branching or derivation happens here. Exposes.Gt(the twist generator, aPoint2) and.Fp12_1(theField12multiplicative identity, used to build thePoint12identity).Point— a point onCurve, in affine(x, y)Fp2coordinates (base- curve points useim = 0)..add(),.double(),.multiply(n)(accepts abigintor coercible value),.neg(),.twice(n),.eq(),.toString().Point2— a point onCurve2(extends Point); the 3-argument constructor validates curve membership viaCurve2.contains()and throwspointNotOnCurveotherwise.Point12,.toF12()(onPoint/Point2) — embeds a G1 or G2 point into theFp12pairing target group, inField12coordinates. Works for any curve whose params object suppliesxiRe/twistType(both bundled parameters do) —alg-field'sField12(>= 0.3.0) derives its degree-12 modulus polynomial frombn.pautomatically, andPoint2.toF12()'s untwist map branches ontwistTypeto match whichever convention that curve'sG2bwas built with (G2b = b / xifor a D-twist,G2b = b * xifor an M-twist — these aren't a free choice, they're a real per-curve-family convention, so a curve you add yourself needs to get this right for its ownG2b).alg-bnstill has no complete Miller-loop pairing implementation for any curve —toF12()/Point12are the embedding building block, not a full pairing.
JacobianPoint, JacobianPoint2
Same curve/params contract as Point/Point2 (built on the same Curve/
Curve2 instances, same tunable bn shape), but in Jacobian (x, y, z)
coordinates instead of affine. Point/Point2 pay a full field inversion
(.divide()) on every single .add()/.double(); Jacobian coordinates
need none until you actually want the affine x/y back (.toAffine()),
so .multiply() does its doublings/additions inversion-free and normalizes
once at the end. Note: on alg-field's native-BigInt-backed fields, this
is a smaller win than the classical "inversion costs 10-100x a
multiplication" rule of thumb suggests — V8's BigInt modular inverse is
close to multiplication cost at these prime sizes, so .multiply() here is
roughly on par with Point.multiply() for BN254-sized primes and only
modestly faster for larger ones (BLS12-381 in informal local benchmarks).
Add it where you specifically want to avoid per-operation inversions (e.g.
a much larger custom prime) rather than as a default performance upgrade.
const { Curve, JacobianPoint } = require('alg-bn')
const curve = new Curve()
const JG = JacobianPoint.fromAffine(curve, curve.G)
JG.multiply(12345n).toAffine().eq(curve.G.multiply(12345n)) // truenew JacobianPoint(E, x, y, z)—x/y(bothFp2) withzomitted default toz = 1; all ofx/y/zomitted builds the point at infinity. No on-curve validation (matchingPoint's 4-argument form) — useJacobianPoint.fromAffineorCurve.contains(P.toAffine())if you need it.JacobianPoint.fromAffine(E, P)— builds aJacobianPointfrom an existing (already-validated)Point..toAffine()— normalizes toz = 1and returns aPoint..add(),.double(),.twice(n),.multiply(n),.neg(),.eq()(compares canonical Jacobian coordinates directly — likePoint, notBN128Fp, no normalization needed first),.zero(),.toString(),.toF12()(both delegate to.toAffine()).JacobianPoint2— theCurve2counterpart (extends JacobianPoint).new JacobianPoint2(E, x, y)(zomitted, i.e. a fresh affine-equivalent point) validates viaCurve2.contains()and throwspointNotOnCurve, matchingPoint2; the 4-argument(E, x, y, z)form used internally by.add()/.twice()does not re-validate every intermediate value.JacobianPoint2.fromAffine(E, P)mirrorsJacobianPoint.fromAffine.
Development
To run tests
npm testTo lint
npm run lintTo build
npm run build