BLS12-381
zisklib functions for BLS12-381 field arithmetic (Fp, Fp2, Fr,
Fp6, Fp12), G1 and G2 curve operations, Miller loop, final
exponentiation, optimal Ate pairing, KZG verification, and
hash-to-curve helpers.
Overview
BLS12-381 is a ~128-bit BLS pairing-friendly curve, powering Ethereum
2.0 signatures, Filecoin proofs, KZG / EIP-4844 commitments, and modern
zkSNARKs. It's y² = x³ + 4 over Fp (381 bits), embedding degree 12,
with the tower
Fp ⊂ Fp2 = Fp[u]/(u² + 1) ⊂ Fp6 = Fp2[v]/(v³ − (u+1)) ⊂ Fp12 = Fp6[w]/(w² − v).
G1 lives over Fp, G2 on a sextic twist over Fp2, and GT in Fp12; the
scalar field Fr is 255 bits. Pairings are computed in two stages: a
Miller loop yields an Fp12 element, then a final exponentiation maps it
into the cyclotomic subgroup of Fp12, where exponentiation by the BLS
parameter x is cheap. KZG proof verification (one pairing check) and
a simplified-SWU map-to-curve for BLS signatures sit on top.
The page is organized bottom-up:
| Section | Provides | Depends on |
|---|---|---|
| Base field (Fp) | Arithmetic for every coordinate computation. | — |
| Extension field (Fp2) | Quadratic extension Fp[u]/(u² + 1); host of G2 coordinates. | Fp |
| Scalar field (Fr) | Arithmetic for scalar multiplication and exponents. | — |
| Utilities | Byte/limb conversions bridging external encodings. | — |
| G1 curve | Point operations on y² = x³ + 4 over Fp. | Fp |
| G2 / twist | Point operations on the sextic twist over Fp2. | Fp2 |
| Fp6 tower | Cubic extension Fp2[v]/(v³ − (u+1)). | Fp2 |
| Fp12 tower | Quadratic extension Fp6[w]/(w² − v); host of GT. | Fp6 |
| Miller loop | Maps a (G1, G2) pair to an Fp12 element — the first half of pairing. | G1, G2/twist, Fp12 |
| Final exponentiation | Maps an Fp12 element into the cyclotomic subgroup. | Fp12 |
| Cyclotomic subgroup | Compressed squaring and exponentiation by x in the small-order Fp12 subgroup. | Fp12 |
| Pairing | High-level pairing_bls12_381, pairing_batch_bls12_381, pairing_check_bls12_381. | G1, G2/twist, Fp12 |
| KZG | KZG polynomial commitment proof verification (one pairing check; EIP-4844). | G1, G2/twist, Pairing |
| Hash-to-curve | Simplified-SWU map from Fp / Fp2 elements onto G1 / G2. | Fp, Fp2, G1, G2/twist |
Base field (Fp)
The BLS12-381 base field Fp, where p is a 381-bit prime and the
curve equation y² = x³ + 4 is defined. Elements are represented as
[u64; 6]: six little-endian limbs encoding a 384-bit value reduced
modulo p. Every higher layer of the tower (Fp2, Fp6, Fp12) and both
curve groups ultimately reduce to operations here.
Returns the sign of an Fp element: the least significant bit of its canonical representative, as defined in RFC 9380.
pub fn sgn0_fp_bls12_381(x: &[u64; 6]) -> u64
Parameters
| Name | Type | Description |
|---|---|---|
x | &[u64; 6] | BLS12-381 Fp element as 6 LE limbs. |
Returns
| Type | Description |
|---|---|
u64 | 0 if the element is even, 1 if odd (LSB of the canonical representative). |
Example
let sign = zisklib::sgn0_fp_bls12_381(&x_limbs);
Extension field (Fp2)
The quadratic extension Fp2 = Fp[u] / (u² + 1). Elements are pairs
(re, im) of Fp values, stored as [u64; 12] (the first six limbs
are re, the next six are im). G2 coordinates are Fp2 elements,
and Fp6 and Fp12 are towers built on top of Fp2, so this section
feeds both the G2 twist and the pairing target.
Sign of a BLS12-381 Fp2 element, as defined in RFC 9380 for hash-to-curve.
pub fn sgn0_fp2_bls12_381(a: &[u64; 12]) -> u64
Parameters
| Name | Type | Description |
|---|---|---|
a | &[u64; 12] | BLS12-381 Fp2 element as 12 LE limbs (real ++ imaginary). |
Returns
| Type | Description |
|---|---|
u64 | The sign bit as defined in RFC 9380: based on the LSB of the first non-zero component. |
Example
let sign = zisklib::sgn0_fp2_bls12_381(&a);
Scalar field (Fr)
The scalar field Fr, which is the prime-order subgroup order shared
by G1, G2, and GT. Scalars are 256-bit values represented as
[u64; 4] in little-endian limb order, and they are the exponents
used in scalar multiplication on the curve and in pairing-based
protocols. KZG and BLS signature scalars all live here.
Reduces a 256-bit integer modulo the BLS12-381 scalar field
order r. Returns the input unchanged when it is already in
range.
pub fn reduce_fr_bls12_381(x: &[u64; 4]) -> [u64; 4]
Parameters
| Name | Type | Description |
|---|---|---|
x | &[u64; 4] | 256-bit integer as 4 LE limbs. |
Returns
| Type | Description |
|---|---|
[u64; 4] | x mod r in the BLS12-381 scalar field. |
Example
let reduced = zisklib::reduce_fr_bls12_381(&scalar);
Utilities
Encoding and conversion helpers that bridge external byte representations with the internal limb layouts used by the rest of the module. These cover scalar-byte conversions and other glue needed when feeding inputs into the field, curve, and pairing routines below.
Converts a big-endian 32-byte scalar to 4 little-endian u64
limbs. No range check is performed; use
reduce_fr_bls12_381 if you need the result modulo r.
pub fn scalar_bytes_be_to_u64_le_bls12_381(
bytes: &[u8; 32],
) -> [u64; 4]
Parameters
| Name | Type | Description |
|---|---|---|
bytes | &[u8; 32] | Big-endian 32-byte scalar representation. |
Returns
| Type | Description |
|---|---|
[u64; 4] | The same value as 4 little-endian u64 limbs. |
Example
let limbs = zisklib::scalar_bytes_be_to_u64_le_bls12_381(&be_bytes);
G1 curve
The prime-order subgroup G1 of the curve y² = x³ + 4 over Fp.
Points are stored as [u64; 12] (the affine coordinates x ++ y,
six limbs each), with the identity encoded as the all-zero buffer.
Two flavors of group law are provided: the add_* and dbl_*
routines assume non-zero, non-equal inputs and are faster, while the
add_complete_* variants handle every case (identity, equal inputs,
opposite inputs) at the cost of extra checks. G1 is the first
argument of the pairing and the group used by KZG commitments.
Decompresses a 48-byte ZCash-format compressed G1 point. The top
3 bits of the first byte are flags: bit 7 is the compression
flag (must be 1), bit 6 is the infinity flag, bit 5 is the sign
of y. Returns Err if the input is malformed or does not lie
on the curve.
pub fn decompress_bls12_381(
input: &[u8; 48],
) -> Result<[u64; 12], &'static str>
Parameters
| Name | Type | Description |
|---|---|---|
input | &[u8; 48] | Compressed G1 point in the standard ZCash serialization (flags in the top 3 bits of byte 0). |
Returns
| Type | Description |
|---|---|
Result<[u64; 12], &'static str> | Ok([u64; 12]) with the decompressed affine point (6 LE limbs for x ++ 6 LE limbs for y), or Err(&'static str) describing why decompression failed. |
Example
let point = zisklib::decompress_bls12_381(&compressed_bytes)
.expect("invalid compressed G1 point");
G2 / twist
The prime-order subgroup G2, defined on a sextic twist of the curve
over Fp2. Points are stored as [u64; 24] (a pair of Fp2
coordinates), with the identity encoded as the all-zero buffer. As
in G1, the add_* and dbl_* routines assume non-zero, non-equal
inputs while the add_complete_* variants handle every edge case
including the identity. G2 is the second argument of the pairing
and the group BLS signatures live in.
Decompresses a 96-byte ZCash-format compressed G2 point on the
BLS12-381 twist E': y² = x³ + 4·(1+u). The top 3 bits of the
first byte are flags identical in meaning to the G1 form.
Returns the affine point together with a boolean indicating
whether the encoded point is the identity.
pub fn decompress_twist_bls12_381(
input: &[u8; 96],
) -> Result<([u64; 24], bool), &'static str>
Parameters
| Name | Type | Description |
|---|---|---|
input | &[u8; 96] | Compressed G2 point: 48 bytes for x_i (with flags in byte 0) followed by 48 bytes for x_r. |
Returns
| Type | Description |
|---|---|
Result<([u64; 24], bool), &'static str> | Ok((point, is_identity)) with the decompressed affine point as 24 LE limbs (Fp2 x ++ Fp2 y) and a flag that is true when the input encodes the point at infinity, or Err(&'static str) describing why decompression failed. |
Example
let (point, is_inf) = zisklib::decompress_twist_bls12_381(&bytes)
.expect("invalid compressed G2 point");
Fp6 tower
The sextic extension Fp6 = Fp2[v] / (v³ − (u + 1)), built directly
on top of Fp2. Elements are represented as [u64; 36] — three Fp2
coefficients in the basis 1, v, v². Fp6 is the intermediate layer
between Fp2 and Fp12: every Fp12 multiplication ultimately decomposes
into Fp6 operations, which is why this section sits alongside the
pairing internals rather than next to the other field sections.
Addition in the BLS12-381 sextic extension Fp6 = Fp2[v]/(v³ − (1+u)).
Elements are stored as three Fp2 coefficients: (c0, c1, c2)
laid out as [u64; 36].
pub fn add_fp6_bls12_381(a: &[u64; 36], b: &[u64; 36]) -> [u64; 36]
Parameters
| Name | Type | Description |
|---|---|---|
a | &[u64; 36] | First Fp6 element as three Fp2 limbs (c0 ++ c1 ++ c2). |
b | &[u64; 36] | Second Fp6 element as three Fp2 limbs. |
Returns
| Type | Description |
|---|---|
[u64; 36] | a + b in BLS12-381 Fp6. |
Fp12 tower
The dodecic extension Fp12 = Fp6[w] / (w² − v), the pairing target
field. Elements are represented as [u64; 72] — two Fp6
coefficients in the basis 1, w. The Miller loop accumulates an
Fp12 element, final exponentiation maps it into the GT subgroup, and
the cyclotomic-subgroup routines provide cheaper variants for the
particular shape of elements that show up after that mapping.
Multiplication in the BLS12-381 dodecic extension
Fp12 = Fp6[w]/(w² − v). Elements are stored as two Fp6
coefficients (c0, c1) laid out as [u64; 72]. Uses Karatsuba
in Fp6.
pub fn mul_fp12_bls12_381(a: &[u64; 72], b: &[u64; 72]) -> [u64; 72]
Parameters
| Name | Type | Description |
|---|---|---|
a | &[u64; 72] | First Fp12 operand. |
b | &[u64; 72] | Second Fp12 operand. |
Returns
| Type | Description |
|---|---|
[u64; 72] | a * b in BLS12-381 Fp12. |
Miller loop
The Miller loop, the first half of the optimal Ate pairing. It walks the binary expansion of the BLS parameter, evaluating line functions through points of G2 at a G1 point and accumulating the product into an Fp12 element. The result is not yet pairing-canonical — it must be passed through the final exponentiation below before it lives in GT.
Computes the Miller loop for a non-zero G1 point p and a
non-zero G2 point q, following the optimal Ate exponent for
BLS12-381. Line coefficients (λ, μ) are hinted and verified
on the fly.
pub fn miller_loop_bls12_381(
p: &[u64; 12],
q: &[u64; 24],
) -> [u64; 72]
Parameters
| Name | Type | Description |
|---|---|---|
p | &[u64; 12] | G1 point as 12 LE limbs. Must be non-zero. |
q | &[u64; 24] | G2 point as 24 LE limbs. Must be non-zero. |
Returns
| Type | Description |
|---|---|
[u64; 72] | Miller loop output as an Fp12 element — the inverse of f_(abs X, q)(p) (the final conjugation accounts for the negative sign of X). |
Not optimized for the infinity case. Use pairing_bls12_381
for end-to-end pairings or pre-filter identity inputs.
Final exponentiation
The second half of the optimal Ate pairing: raises an Fp12 element
to the power (p¹² − 1) / r, mapping the Miller-loop output into the
order-r GT subgroup of Fp12. It is split into an easy part
((p⁶ − 1)(p² + 1)) and a hard part ((p⁴ − p² + 1)/r) evaluated
via cyclotomic exponentiations defined in the next section. The
implementation is not optimized for the degenerate case where the
input is already 1.
Raises an Fp12 element to the power (p¹² − 1)/r, computing the
final exponentiation of the BLS12-381 Ate pairing. Splits into
an easy part ((p⁶ − 1)(p² + 1)) followed by the hard part
(p⁴ − p² + 1)/r evaluated via cyclotomic exponentiations.
pub fn final_exp_bls12_381(f: &[u64; 72]) -> [u64; 72]
Parameters
| Name | Type | Description |
|---|---|---|
f | &[u64; 72] | Fp12 element, typically the output of a Miller loop. |
Returns
| Type | Description |
|---|---|
[u64; 72] | f^((p¹² − 1)/r) as an Fp12 element in the GT subgroup. |
Not optimized for the case f == 1.
Cyclotomic subgroup
The cyclotomic subgroup of Fp12 — the small-order subgroup where
pairing outputs live after final exponentiation. Elements of GT sit
inside this subgroup, and its special structure allows squarings and
exponentiations that are significantly cheaper than generic Fp12
operations. A compressed representation as [u64; 48] (four Fp2
entries) is also exposed, and these routines are what the final
exponentiation and pairing-equation evaluations rely on internally.
Compresses an Fp12 element in the cyclotomic subgroup
GΦ₆(p²) from six Fp2 coefficients down to four: the encoding
maps a = (a0 + a4·v + a3·v²) + (a2 + a1·v + a5·v²)·w to
[a2, a3, a4, a5].
pub fn compress_cyclo_bls12_381(a: &[u64; 72]) -> [u64; 48]
Parameters
| Name | Type | Description |
|---|---|---|
a | &[u64; 72] | Fp12 element assumed to lie in the cyclotomic subgroup GΦ₆(p²). |
Returns
| Type | Description |
|---|---|
[u64; 48] | The compressed representation [a2, a3, a4, a5] as four packed Fp2 elements. |
If the input does not lie in GΦ₆(p²) then the
compress / decompress round-trip is not well-defined and the
output of decompress_cyclo_bls12_381 will not match a.
Pairing
The high-level optimal Ate pairing e: G1 × G2 → GT, packaging the
Miller loop and the final exponentiation into a single call. The
output is a GT element stored as [u64; 72] (an Fp12 in the
cyclotomic subgroup). This is the entry point KZG verification and
BLS signature verification ultimately call into.
Optimal Ate pairing e(p, q) on BLS12-381. Returns the GT
element as [u64; 72] (Fp12). Returns the GT identity 1 when
either input is the point at infinity.
pub fn pairing_bls12_381(
p: &[u64; 12],
q: &[u64; 24],
) -> [u64; 72]
Parameters
| Name | Type | Description |
|---|---|---|
p | &[u64; 12] | G1 point as 12 LE limbs. |
q | &[u64; 24] | G2 point as 24 LE limbs. |
Returns
| Type | Description |
|---|---|
[u64; 72] | The pairing result e(p, q) as an Fp12 GT element. |
Example
let gt = zisklib::pairing_bls12_381(&g1_point, &g2_point);
KZG
KZG polynomial-commitment proof verification, as used by EIP-4844
for Ethereum blob data. Verification reduces to checking a single
pairing equation of the form
e(commitment − [y]G, G) = e(proof, [z]G − [x]G), where the
right-hand-side [z]G and [x]G come from the trusted-setup
parameters. This section consumes the G1, G2, and Pairing pieces
above and exposes a single high-level verification entry point.
Verifies a KZG opening proof against the EIP-4844 trusted setup
hard-coded in zisklib. Checks that the polynomial committed
by commitment_bytes evaluates to y_bytes at z_bytes using
the witness proof_bytes. All inputs are big-endian byte
arrays; z_bytes and y_bytes must be canonical scalars
(strictly less than r).
pub fn verify_kzg_proof(
z_bytes: &[u8; 32],
y_bytes: &[u8; 32],
commitment_bytes: &[u8; 48],
proof_bytes: &[u8; 48],
) -> bool
Parameters
| Name | Type | Description |
|---|---|---|
z_bytes | &[u8; 32] | Evaluation point as a big-endian 32-byte scalar. Must satisfy z < r. |
y_bytes | &[u8; 32] | Claimed evaluation value as a big-endian 32-byte scalar. Must satisfy y < r. |
commitment_bytes | &[u8; 48] | KZG commitment C as a compressed G1 point (big-endian). |
proof_bytes | &[u8; 48] | Opening proof π as a compressed G1 point (big-endian). |
Returns
| Type | Description |
|---|---|
bool | true if the KZG proof is valid, false otherwise (including if any input fails to decode or z / y are out of range). |
Example
let valid = zisklib::verify_kzg_proof(
&z_bytes,
&y_bytes,
&commitment,
&proof,
);
assert!(valid);
Hash-to-curve
Hash-to-curve / map-to-curve primitives that produce a point on the
curve from a field element, using the simplified SWU map for both G1
and G2. These are the building blocks of RFC 9380 hash-to-curve
suites — in particular, BLS signatures rely on hashing messages into
G2 — and they combine the sgn0 and field-arithmetic helpers from
the earlier sections with the curve groups defined above.
Maps an Fp element to a G1 point in the prime-order subgroup
using the simplified SWU method on the 11-isogenous curve,
followed by the isogeny back to E and cofactor clearing
(RFC 9380).
pub fn map_to_curve_g1_bls12_381(
u: &[u64; 6],
) -> Result<[u64; 12], u8>
Parameters
| Name | Type | Description |
|---|---|---|
u | &[u64; 6] | Fp field element as 6 LE limbs. Must be canonical (u < p). |
Returns
| Type | Description |
|---|---|
Result<[u64; 12], u8> | Ok([u64; 12]) with a G1 point in the prime-order subgroup, or Err(1) if u ≥ p. |
Example
let point = zisklib::map_to_curve_g1_bls12_381(&u_limbs)
.expect("u not in Fp");