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/polyringv0.1, unaudited. Use it for research and on testnets.
one batched check
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.
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.
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.
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.
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.
Less to prove
- Constraints791,400 → 491,043
- −38%
- Committed wires589,535 → 398,959
- −32%
More to check
- Verification gas≈359k → ≈460k per
verifyProofcall - +28%
- Proof size384 → 512 bytes
- +33%
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.
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
}
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.