Smart Contractsadvancedarticle45 min read

ZK Libraries: Arkworks and zkcrypto — Understanding Fields and Curves

As an arkworks Rust ZK developer you need to understand finite fields, BN254, and BLS12-381. This guide explains base vs scalar fields, pairing-friendly curves, and how to choose between arkworks and zkcrypto.

Vietcardano teamTeam

Đội ngũ biên tập VietCardanoCommunity — các chuyên gia blockchain và Cardano, cung cấp nội dung giáo dục và nghiên cứu chuyên sâu cho cộng đồng.

Published August 31, 2026Updated August 30, 2026

Lan is a developer in Ho Chi Minh City building a privacy-preserving loyalty program: members prove they have held a token for six months without revealing which wallet they own. The docs all point one way -- arkworks, the Rust ecosystem behind a large share of ZK production code. She adds ark-groth16, ark-bn254 and ark-relations to her Cargo.toml and stares at the first line: type Fr = ::ScalarField;. Fr. Fq. Base field. Scalar field. Embedding degree. G1, G2, GT. None of the tutorials explain why any of it matters -- until the day her circuit must verify a bn254 curve zk proof inside another proof, and the whole project hinges on which field her arithmetic runs in.

This article is the explanation those tutorials skip: what a finite field actually is and why every ZK library is built on one, how BN254 became the default bn254 curve zk choice because Ethereum shipped precompiled contracts for it, why Zcash and Ethereum 2.0 moved to bls12 381 zk work on BLS12-381 instead, and how to pick between arkworks and zkcrypto, the two Rust library families at the heart of all of it. By the end you will be able to read ark_ff::Fp or bls12_381::Scalar in a codebase and know exactly what question the author was answering.

arkworks and zkcrypto: two libraries, two philosophies

The Rust ZK world is split into two camps that are easy to confuse because they implement the same math. arkworks is a full-stack ecosystem: one org, dozens of crates, and a deliberate opinion about how a ZK application should be built. zkcrypto is the opposite philosophy: a collection of small, focused crates that each do one thing, the way the Zcash project originally needed them.

Both camps trace back to the same people. The ZEXE paper's authors -- Pratyush Mishra, Weikeng Chen and colleagues -- built the algebra layer that became arkworks, adapted from zkcrypto's ff, pairing and bellman crates written by Sean Bowe and the Zcash team. The two libraries are not rivals; they are two answers to the same question -- "what is the right shape for ZK code?" arkworks says: a framework. zkcrypto says: a toolbox.

The arkworks crate map is the fastest way to see the difference. The algebra layer is ark-ff (finite fields), ark-ec (curves and pairings), ark-poly (polynomials and FFTs), plus ready-made curve crates like ark-bn254 and ark-bls12-381. The circuit layer is ark-relations (the constraint-system API) and ark-r1cs-std (reusable gadgets -- booleans, hashes, comparisons). The proof layer is ark-snark (the interface) with ark-groth16, ark-marlin and ark-gm17. ark-std smooths over std/no_std, and ark-circom reads Circom's .r1cs and .zkey files so you can prove circom circuits from Rust and feed the proof to a Solidity verifier. One dependency tree, one consistent API, one ecosystem.

zkcrypto is deliberately the inverse. ff and group define the trait vocabulary -- a field element, a group element -- that almost everyone else now builds on. bls12_381 is the BLS12-381 curve, nothing more. pairing defines the Engine interface and keeps the concrete curve in a separate crate. jubjub is a signature curve. bellman builds circuits generically over whatever scalar field you give it ("the scalar field type is used as the 'word' of a circuit"), and halo2 is the proving system that succeeded it. Nothing in zkcrypto assumes you even want a proving system; pull in only bls12_381 to do pairing math for your own protocol.

There is one more difference worth knowing before you commit to a stack. The arkworks groth16 README states plainly: "This is an academic proof-of-concept prototype, and in particular has not received careful code review. This implementation is NOT ready for production use." The zkcrypto bls12_381 README carries its own disclaimer -- "This implementation has not been reviewed or audited. Use at your own risk" -- even though the older Zcash pairing library it descends from received a professional Kudelski audit in 2018 (one low-severity issue, two informational observations). The takeaway is not that either library is dangerous; it is that audit status is a property of your deployment, not of the library you choose. Both stacks are battle-tested -- Reth's Rust EVM ships arkworks' BN254 math inside its precompiles, and Zcash, Ethereum 2.0 and Filecoin run on zkcrypto-descended code.

Finite fields: the foundation every ZK library builds on

Everything in a ZK library sits on one concept: the finite field. A finite field is a set of numbers with addition, subtraction, multiplication and division that all work the way you expect, but the set is finite -- in practice, the integers from 0 to p−1 for some huge prime p, where every operation reduces modulo p. This is written Fp and called a prime field. An elliptic curve "over Fp" simply means its coordinates are elements of Fp, and its equation y² = x³ + ax + b is evaluated with modular arithmetic.

Every curve used in ZK actually drags two finite fields around, and this is the single most important idea in the article. The base field is the field whose elements are the coordinates of the points on the curve. The scalar field is the field whose elements are the scalars you multiply points by -- and its order is exactly the number of points in the curve's main subgroup. These are two different primes. In arkworks' ark-bn254 crate, the base field is called Fq (a 254-bit prime) and the scalar field is called Fr (another 254-bit prime, not the same number). In zkcrypto's bls12_381 documentation, the same two concepts are named Fp (381-bit base) and Fq (255-bit scalar). The symbols are inconsistent between the two libraries for the same pair of concepts -- one of the least documented sources of beginner confusion in this field, and you can now name it.

Why should a circuit developer care which is which? Because a proof system does all of its arithmetic in the scalar field, and only the scalar field -- let x = Fr::from(7) in an arkworks circuit works modulo the scalar-field prime. Public inputs must be valid scalar field elements, and confusing base with scalar field in that check is a documented source of verifier bugs. In other words, choosing a curve is choosing the field your circuit is allowed to do arithmetic in. You are not picking a security parameter; you are picking the "motherboard" your circuit runs on.

Both library families implement field arithmetic the same way under the hood: numbers are stored in Montgomery form so that modular multiplication stays fast, and ark-ff exposes this as a reusable backend (MontBackend) behind its Field and PrimeField traits. For pairing-based systems, the libraries also build the extension towers -- Fp2, Fp6, Fp12 -- because the target group of a pairing lives in a degree-12 extension field, not in the base field. ark-ff ships all of these as generic models; you never write the tower yourself.

Finite field and elliptic curve anatomy: Prime Field Fp → Elliptic Curve E/Fp → Curve points Group, with Base field Fq and Scalar field Fr below

Figure 1: Finite field and curve anatomy — one curve, two fields, the scalar field is where your circuit lives

BN254: the curve the Ethereum precompiles made standard

Every pairing-based SNARK needs a curve that supports a pairing -- a bilinear map e: G1 × G2 → GT where e(aP, bQ) = e(P, Q)^(ab). Bilinearity is what lets a verifier check a proof with a handful of pairing evaluations instead of inspecting the whole computation. BN254 -- also written alt_bn128, bn256 or BN128 -- is a Barreto-Naehrig curve with embedding degree 12 built for exactly this, and it became the default bn254 curve zk choice for a mundane reason: Ethereum shipped precompiled contracts for it.

EIP-196 added 0x06 (G1 point addition) and 0x07 (G1 scalar multiplication); EIP-197 added 0x08, a precompile that evaluates the optimal ate pairing check. That is everything a Groth16 verifier needs, and it runs inside the EVM at fixed gas prices -- 6,000 gas per scalar multiplication, 34,000 plus 45,000 per pairing. A typical Groth16 verification on BN254 lands around 200,000 gas. Because every L2, sidechain and Ethereum-compatible chain inherited these precompiles, the ecosystem -- circom, snarkjs, Tornado Cash-class privacy apps, rollups, and the Solidity verifiers generated for them -- standardized on BN254. If you want to verify proofs cheaply on an EVM chain today, you are almost certainly going to verify a proof over BN254.

The curve's anatomy is worth memorizing because it repeats for every pairing curve. G1 is the group of points on y² = x³ + 3 over the base field Fp -- 64 bytes per uncompressed point. G2 is the group of points on a degree-2 twist, y² = x³ + 3/(9+u) over Fp² -- 128 bytes per point. GT is a subgroup of the multiplicative group of the degree-12 extension, where pairing outputs live. A Groth16 proof is three points: A ∈ G1, B ∈ G2, C ∈ G1, which compresses to 128 bytes. The verifier computes a product of pairings against precomputed verification-key constants -- O(1) pairings, O(public inputs) G1 multiplications.

And here is the honest catch. BN254 was designed when ~128-bit security seemed achievable, but advances in Number Field Sieve attacks on the pairing target group eroded that. The arkworks source now warns: "This curve does not satisfy the 128-bit security level anymore." Independent estimates put it around 100–110 bits. That is not "broken" -- no one will grind a discrete log against it -- but it is below the margin modern systems prefer, and a full security level below what BLS12-381 was built to reach.

BLS12-381: the 128-bit-security workhorse

BLS12-381 is the curve Sean Bowe designed for Zcash's Sapling upgrade in 2017, and its specification is a masterclass in fitting a curve to a workload. It is a BLS (Barreto-Lynn-Scott) curve, again with embedding degree 12, but the parameters are chosen for three things at once: a base field p that is 381 bits (so compressed G1 points fit in 48 bytes and G2 in 96), a scalar field q that is 255 bits with a 2³² primitive root of unity (so radix-2 FFTs -- the engine of PLONK/KZG-style polynomial work -- are efficient), and a curve parameter z with tiny Hamming weight (so pairings are fast). The bls12 381 zk niche -- ZK systems that want more security margin than BN254 -- is built on exactly these numbers.

The security story is why Zcash moved off its BN curve. The bls12_381 crate's own notes walk through the math: pairing-friendly curves are necessarily less secure than conventional curves because a small embedding degree lets an attacker map the problem into the target group; optimized NFS attacks could weaken it further, but even in the pessimistic case BLS12-381 degrades only to roughly 117–120 bits -- "contrasting with 254-bit BN curves which usually have less than 100 bits of security in the same situation." That is a full security level of headroom, and it is why Sapling activated on BLS12-381 in October 2018, why Ethereum 2.0's consensus layer uses it for BLS signatures, and why Filecoin and most halo2 deployments followed.

The price of that margin is concrete. Groth16 proofs over BLS12-381 are 192 bytes instead of 128, and the G2 arithmetic -- 96-byte points in a quadratic extension field -- is heavier to compute and more expensive to serialize. No EVM chain ships a BLS12-381 precompile the way it ships BN254's, so on-chain verification needs custom Solidity (slower, more gas) or moves off-chain. Teams pick BLS12-381 when security margin and FFT-friendliness outweigh on-chain cost; they pick BN254 when the precompile ecosystem is decisive.

BN254 vs BLS12-381: how to choose

Put the two curves side by side and the decision becomes mechanical. Both have embedding degree 12. BN254 has a 254-bit base field and a 254-bit scalar field, ~100–110 bits of security, 128-byte Groth16 proofs, and first-class EVM precompile support. BLS12-381 has a 381-bit base field and a 255-bit scalar field, ~126 bits of security, 192-byte Groth16 proofs, and no native precompile -- but a 2³² root of unity that makes polynomial-heavy systems faster. BN254 is the compatibility answer: if your verifier is a Solidity contract on an EVM chain, or your circuit comes from circom (whose toolchain is BN254-native), the decision is made for you. BLS12-381 is the greenfield answer: when you control the verifier, care about long-term security margin, and are building FFT-heavy systems, it is the defensible choice.

BN254 vs BLS12-381 comparison: embedding degree, field sizes, security, proof size, EVM precompile support

Figure 2: BN254 vs BLS12-381 — BN254 for EVM compatibility, BLS12-381 for security margin and FFT speed

There is a deeper rule behind the table, one that saves a week of debugging. The scalar field of the curve is the field of the circuit -- full stop. Every constraint, every gadget, every public input lives in it. So when you pick a curve you are not choosing "the curve", you are choosing the field in which your application can compute. That is why the two scalar fields have different 2-adicity (the size of the largest power-of-two FFT they support): BLS12-381's scalar field supports 2³², BN254's supports 2²⁸. If your system is FFT-bound, that difference is measurable in prover time.

The same rule explains the hardest problems in ZK engineering. Proving a statement about BN254 elements inside a circuit -- say, verifying a BN254 signature, or doing recursive verification of one Groth16 proof inside another -- is impossible directly, because the base field of BN254 is not the scalar field where your circuit lives. This is called non-native field arithmetic, and it is expensive. arkworks ships ark-nonnative for exactly this, plus recursion-oriented curves (ark-bw6-761, ark-grumpkin) whose fields are chosen so the two sides of the mismatch line up. These crates are not trivia: they are the field-mismatch problem packaged as code. Knowing the vocabulary -- base field vs scalar field -- is what makes those crates comprehensible instead of magic.

Putting it together: what a real project pulls in

A minimal arkworks Groth16 project looks like this: ark-ff gives you the scalar field type, ark-ec the curve machinery, ark-bls12-381 (or ark-bn254) the concrete curve, ark-relations the constraint system, ark-r1cs-std the gadgets, ark-snark the interface and ark-groth16 the proving system. A minimal zkcrypto project looks different: bls12_381 gives you the field and curve elements, pairing the Engine trait and pairing call, and bellman (or halo2) the circuit layer -- with ff and group as the trait foundation everything else assumes.

The shared shapes matter more than the crate names. Both stacks give you the same three group types for each curve -- G1 (base-field points), G2 (twist points) and GT (pairing outputs) -- and both represent the same two fields. If you can read the curve parameters in one, you can read them in the other; the symbols shift (Fq/Fr in arkworks, Fp/Fq in zkcrypto), the math does not. That is the practical value of understanding the math rather than memorizing an API: the two ecosystems become interchangeable in your head.

The ecosystem is more porous than the crate names suggest. Both libraries implement the same curves, so an ark-groth16 proof over BN254 verifies against a snarkjs-generated contract, circom circuits prove from Rust via ark-circom, and arkworks' own algebra layer descends from zkcrypto's ff/pairing/bellman. And when Zcash needed a signature curve that would work naturally inside its Sapling circuits, it defined Jubjub over exactly BLS12-381's scalar field -- "the choice of GF(q) is made to be the scalar field of the BLS12-381 elliptic curve construction" -- so one field implementation pays for both the proof and the signature. Putting a curve on a convenient field is a recurring design pattern: it is how ZK systems share arithmetic.

arkworks vs zkcrypto ecosystem: arkworks full-stack (ark-ff, ark-ec, ark-relations, ark-groth16, ark-circom) vs zkcrypto minimal (ff, group, bls12_381, pairing, bellman/halo2)

Figure 3: arkworks vs zkcrypto — same fields and curves, different philosophy of trust

Which stack should a new project reach for? If you are building the proving side of an application in Rust and want one coherent API -- constraint system, gadgets, proof system, serialization, no native dependencies -- arkworks is the shorter path, and it is the path Mopro (a mobile-proving toolchain) chose specifically because arkworks "avoids native library dependencies and runs anywhere Rust runs". For a protocol or research system wanting the smallest crypto surface, zkcrypto's ff/group/bls12_381 primitives are the ecosystem standard. The honest middle answer: many production systems use both -- arkworks for proving, zkcrypto's bls12_381 for signatures and pairing math elsewhere.

What this means for Vietnamese ZK developers

Vietnam's developer community is growing fast, and Rust is increasingly visible at local meetups and university projects, but ZK is still a niche where a few hundred developers worldwide produce most of the tooling. It is exactly the kind of niche where foundation knowledge pays a compounding dividend: the field-and-curve math in this article never goes stale, while crate APIs are rewritten every year or two.

The practical learning path: start with a system that hides the math -- circom + snarkjs over BN254, or the official arkworks r1cs-tutorial -- and prove something real, even something tiny. Then come back to this article's questions. When the tutorial says Fr, ask "which field is this, and what is the base field of the same curve?" When you hit the "should we use BLS12-381?" debate with your team, ask it in these terms: "is our verifier on an EVM chain, and are we FFT-bound?" You will find that most ZK disagreements are really field-and-verifier disagreements wearing different names. Contribute to open source as you go -- and remember the audit caveat: your deployment's security review is your responsibility, not the library's.

One more thing for teams building on Vietnamese-market use cases -- privacy-preserving KYC, verifiable credentials, decentralized identity: those applications usually verify proofs on-chain, and Ethereum-compatible chains point hard at BN254 because the precompile makes verification ~200,000 gas instead of several times that. If your privacy app issues proofs that a contract verifies, plan your circuit around the bn254 curve zk field and precompile constraints from day one, and reserve bls12 381 zk for systems where you control the verifier end to end.

Frequently asked questions

Do I need to understand finite fields to use arkworks?

Not to run the tutorials -- but yes, the moment you deviate from them. Every circuit is arithmetic in one specific scalar field; you cannot choose a different field without choosing a different curve, and you cannot debug a "constraint mismatch" without knowing which field your constants live in. The field is the least visible dependency in the stack, which makes it the most expensive to ignore.

What is the difference between the base field and the scalar field?

The base field holds the coordinates of curve points; the scalar field holds the scalars you multiply points by, and its order is the number of points in the main subgroup. A proof system does all of its circuit arithmetic in the scalar field only. For BN254 these are two different 254-bit primes; for BLS12-381 a 381-bit base and a 255-bit scalar. Confusing them is a known source of verifier bugs.

Is BN254 still safe to use for zk applications?

"Safe" needs a number. Post-special-TNFS estimates put BN254 at roughly 100-110 bits of security, and the arkworks source explicitly says it no longer satisfies 128 bits. It remains the pragmatic choice because of Ethereum's precompiles and the circom ecosystem, but a conservative 128-bit threat model points to BLS12-381.

When should I choose BLS12-381 over BN254 for a new system?

When you control the verifier (no EVM precompile constraint), when your workload is polynomial/FFT-heavy (BLS12-381's scalar field has a 2³² root of unity), and when long-term security margin outweighs the larger proofs and slower G2 arithmetic. Zcash Sapling, Ethereum 2.0 consensus and Filecoin all made this trade in the same direction.

Can I use arkworks and zkcrypto together?

Yes, and production projects do. The libraries implement the same curves, so a Groth16 proof from ark-groth16 verifies against a snarkjs contract, and teams commonly pair arkworks proving with zkcrypto's bls12_381 for signature or pairing work. The vocabularies are compatible enough that arkworks' own algebra layer descends from zkcrypto's ff/pairing crates.

How does the field choice affect recursive proofs?

Recursion means proving that a proof verifies, and that computation runs inside a circuit -- so it runs in the scalar field. A BN254 proof contains BN254 base-field elements, which are not elements of BN254's scalar field. You need either non-native field arithmetic (ark-nonnative) or a second curve (BW6-761, Grumpkin, Pasta) whose fields line up with the first curve's. Understanding base vs scalar fields makes all of this obvious instead of confusing.

Conclusion

Lan's loyalty app will almost certainly ship on arkworks over bn254 curve zk, because her verifier is a Solidity contract and her circuit wants circom compatibility. Understanding fields and curves did not change that decision -- it made the decision legible. A ZK system is a tower: finite field, elliptic curve, pairing, proof system, application. The two library families in the title are simply the two most popular ways to build that tower in Rust. The arkworks philosophy is "one ecosystem for the whole tower"; the zkcrypto philosophy is "the smallest well-tested primitives, you assemble the rest". Both stand on the same two curves and the same two fields per curve. Learn the math that the libraries share, and the crate APIs become details you can look up; learn only the APIs, and every library rewrite starts the investigation from zero. The base field is where points live. The scalar field is where your circuit lives. Choose the curve, and you have chosen both.


Quick Reference

arkworks vs zkcrypto

Propertyarkworkszkcrypto
PhilosophyFull-stack ecosystem, one consistent APIMinimal, focused crates, bring your own layer
Finite fieldsark-ff (Field/PrimeField traits, Montgomery backend)ff (traits), bls12_381 (concrete curve fields)
Curves & pairingsark-ec + ark-bn254 / ark-bls12-381pairing (Engine) + bls12_381
Constraint systemark-relations + ark-r1cs-std gadgetsbellman (word = scalar field element) / halo2
Proof systemsark-groth16, ark-marlin, ark-gm17bellman bundles Groth16; halo2
Interopark-circom (Circom R1CS, ZKey, Solidity)No circom layer; Zcash-native
Audit statusgroth16 README: academic prototype, not production-readybls12_381 README: "not reviewed or audited"; older Zcash pairing lib audited by Kudelski (2018)
Best fitFull Rust proving stack, circom compat, mobile provingProtocol/research work, minimal crypto surface

BN254 vs BLS12-381

PropertyBN254 (alt_bn128)BLS12-381
FamilyBarreto-NaehrigBarreto-Lynn-Scott
Embedding degree1212
Base field254-bit (Fq in arkworks)381-bit (Fp in zkcrypto)
Scalar field254-bit (Fr in arkworks), 2-adicity 2²⁸255-bit (Fq in zkcrypto), 2-adicity 2³²
Security (post-TNFS)~100–110 bits~126 bits (worst case ~117–120)
G1 / G2 point size (compressed)32 B / 64 B48 B / 96 B
Groth16 proof size128 bytes192 bytes
EVM precompileYes: 0x06 ecAdd, 0x07 ecMul, 0x08 ecPairing (EIP-196/197)No native precompile
On-chain verification~200K gasCustom Solidity, more gas
Notable usersTornado Cash-class apps, circom/snarkjs ecosystem, rollupsZcash Sapling, Ethereum 2.0 consensus, Filecoin
Best fitEVM verification, circom compatibilityGreenfield, security margin, FFT-heavy systems

Tags:

arkworksbn254 curve zkbls12 381 zk