Go library for gnark · IACR TCHES 2027

polyring toolkit

Fast polynomial ring multiplications in gnark.

polyring is the Polynomial Ring Toolkit for gnark. Its first use is recursion: verify a Groth16 proof inside your circuit with up to 38% fewer constraints than gnark's own pairing.

go get github.com/mistcash/polyring

v0.1, unaudited. Use it for research and on testnets.

F² · L1L2L3 = Q · P12 + R
every loop step,
one batched check
The signed digits of BN254's Miller loop. polyring turns each step into one polynomial identity, then checks them all at once.
The problem

A recursive verifier is mostly pairing.

To check a Groth16 proof inside a circuit, you compute a pairing in emulated field arithmetic. With gnark's pairing, that is 83% of the whole verifier.

gnark's verifier, BN254 inner proof791,400 constraints
Pairing · 656,420Rest
gnark's Miller loop and final exponentiationInputs, point checks
The idea

Check the whole step, not every multiplication.

A pairing loops over dozens of steps, each a handful of multiplications in a degree-12 field. gnark proves every multiplication. polyring writes each step as one polynomial identity and checks all of them together at a single random point.

How it works, in the paper →

F² · L1 L2 L3 = Q · P12 + R
132
constraints per loop step, field multiplication by multiplication
41
constraints per loop step as one identity
The result

A third less work for the prover.

Same circuit, same statement. Only the pairing check changes, and the rest of the verifier stays as it was.

−38%
constraints
791,400 → 491,043
−34%
witness solve time
340 → 223 ms, M3 Pro
−31%
constraints
979,700 → 677,317
−44%
witness solve time
462 → 258 ms, M3 Pro
gnark791,400
656,420
polyring491,043
353,411300k saved
gnark979,700
693,884
polyring677,317
391,451302k saved
gnark pairingpolyring pairingRest of the verifier, unchangedSaved
What it's for

One proof that carries another.

Wrap and compress

Fold a proof, plus your own checks on it, into one BN254 Groth16 proof.

BLS12-381 on Ethereum

Verify a BLS12-381 proof inside a BN254 circuit. The EVM's pairing precompile checks the result.

Any pairing check

The ring pairing isn't tied to Groth16. A general two-pair check costs 31% fewer constraints on BN254.

The trade

Cheaper to prove, dearer to verify on the EVM.

polyring's proof carries three commitments instead of one, so the Solidity verifier does more work: about 460k gas per verifyProof call, against 359k with gnark's pairing. Pick it when proving is your main cost: many proofs, modest hardware, or a proving service that bills by the constraint.

Where a VM runs the pairing itself, verifying gets cheaper too. Garaga on Starknet uses the paper's technique and needs 37% fewer VM steps.

Prover

Less to prove

Constraints791,400 → 491,043
−38%
Committed wires589,535 → 398,959
−32%
EVM verifier

More to check

Verification gas≈359k → ≈460k per verifyProof call
+28%
Proof size384 → 512 bytes
+33%
In your circuit

A few lines in Define.

Bake the inner verifying key into your circuit, verify the inner proof first, then add your own constraints. polyring also generates the Solidity verifier for the outer proof.

outer.go
type Outer struct {
	InnerProof  recursion.Proof[Fp, G2]     `gnark:",secret"`
	InnerPublic recursion.PublicWitness[Fr] `gnark:",public"`
	vk          *recursion.VerifyingKey
}

func (c *Outer) Define(api frontend.API) error {
	v, err := recursion.NewVerifier[Fp, Fr, G2, GTEl](api, c.vk)
	if err != nil {
		return err
	}
	if err := v.AssertProof(c.InnerProof, c.InnerPublic); err != nil {
		return err
	}
	// your own constraints
	return nil
}
Scope of v0.1

Before you ship.

  • UnauditedA bug in an in-circuit pairing breaks soundness. Use it for research and on testnets.
  • One fixed inner keyThe inner verifying key is baked in when the circuit compiles.
  • One proof at a timeEach outer proof verifies one inner proof. Batching several is planned.
  • No nested recursionAn outer polyring proof can't be an inner proof yet, and inner circuits can't use api.Commit.