Pairing-Based Cryptography: BLS Signatures, KZG, and zk-SNARKs
Pairing-based cryptography looks strange the first time you see it.
Most elliptic-curve cryptography gives you one main group structure: points can be added, and a secret scalar can repeatedly add a public generator to itself.
secret scalar * public generator = public point
That is how a private key becomes a public key:
sk * G = public_key
The public key hides sk. Anyone can see the point, but recovering sk from the point should be infeasible.
G is not random per user. It is not a password. It is not secret.
G is a fixed public generator point chosen by the curve or protocol. Everyone uses the same G. The private key is the scalar sk; the public key is the point you get after multiplying that scalar by the public generator.
Some real examples:
| System | Curve or group | What G means | Public-key shape |
|---|---|---|---|
| Bitcoin | secp256k1 | The standard secp256k1 base point from SEC 2. | public_key = sk * G |
| Ethereum accounts | secp256k1 | The same secp256k1 base point used by Bitcoin. Ethereum then derives the account address from the public key. | public_key = sk * G |
| Ethereum consensus BLS | BLS12-381 | A standard generator in G1 is used for validator public keys. Signatures live in G2 in Ethereum's BLS scheme. | public_key = sk * G1 |
| Ethereum KZG / EIP-4844 | BLS12-381 G1 and G2 | The trusted setup publishes many generator-derived powers, such as G1, tau*G1, tau^2*G1, and G2, tau*G2. | commitment = f(tau) * G1 |
| Solana / Ed25519-style keys | Edwards25519 | The standard base point is usually called B, but it plays the same role as G. | public_key = sk * B |
For Bitcoin and Ethereum account keys, the secp256k1 generator is a fixed point with large coordinates:
Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
Gy = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
Every Bitcoin or Ethereum account private key is basically a different scalar multiplied by that same public G.
Alice public key = alice_sk * G
Bob public key = bob_sk * G
Same G, different secret scalar, different public point.
Pairings add another operation. Instead of only making or combining points inside one elliptic-curve group, a pairing takes two points from two source groups and sends them into a target group:
e(point_from_G1, point_from_G2) = target_group_element
Think of it as a special comparison machine:
G1 point: a * P
G2 point: b * Q
pairing:
e(aP, bQ) -> element in GT
That is only one pairing. To compare something, the verifier needs another pairing result too.
So introduce a second pair of source-group points:
first pairing:
G1 point: a * P
G2 point: b * Q
e(aP, bQ)
second pairing:
G1 point: c * P
G2 point: d * Q
e(cP, dQ)
That is where cP and dQ come from. They are not missing from the first equation. They belong to the second pairing, the thing we compare against.
By "comparison machine", I mean this:
The verifier does not ask:
is aP the same point as cP?
is bQ the same point as dQ?
The verifier asks:
does the hidden product a*b match the hidden product c*d?
The source points can look different while the hidden products are the same.
Left pairing uses aP, bQ. Right pairing uses cP, dQ.
Change the four source-group scalars. The verifier compares the GT output from e(aP, bQ) with the GT output from e(cP, dQ).
12P9Q482e(12P, 9Q) = 64^7 mod 607 = 482hidden product = 12 * 9 mod 101 = 73P36Q482e(3P, 36Q) = 64^7 mod 607 = 482hidden product = 3 * 36 mod 101 = 7e(12P, 9Q) = e(3P, 36Q)The source points can look different, but the hidden products match.
Use the toy pairing from this article:
e(aP, bQ) = 64^(a*b mod 101) mod 607
Example that passes:
left side:
e(12P, 9Q)
hidden product = 12 * 9 mod 101 = 7
GT output = 64^7 mod 607 = 482
right side:
e(3P, 36Q)
hidden product = 3 * 36 mod 101 = 7
GT output = 64^7 mod 607 = 482
The verifier sees:
482 == 482
So the comparison passes.
Example that fails:
left side:
e(12P, 9Q)
hidden product = 7
GT output = 482
right side:
e(4P, 20Q)
hidden product = 4 * 20 mod 101 = 80
GT output = 64^80 mod 607 = 151
The verifier sees:
482 != 151
So the comparison fails.
That is the whole point of the machine: it turns a relationship between hidden scalars into a visible equality check in GT.
In BLS signatures, the same comparison looks like:
e(signature, Q) == e(H(message), public_key)
What it is really checking:
signature hides: sk * H(message)
public key hides: sk
left hidden product = sk * H(message)
right hidden product = H(message) * sk
Same product, so the target-group outputs match.
In KZG commitments, the comparison looks like:
e(C - yG1, G2) == e(proof, (tau - z)G2)
What it is really checking:
left hidden value = f(tau) - y
right hidden value = q(tau) * (tau - z)
If the claimed opening is correct, those values are equal, so the target-group outputs match.
G1 and G2 are source groups. They contain elliptic-curve points. GT is the target group. It is not another public key. It is where the pairing result lives.
Public key is still a curve point. GT is the pairing output.
Move the sliders. The public key remains a source-group point like 19Q. The target-group element is a computed pairing output like 482.
Public key
pk = sk * Q = 19QThis is a point in a source group. In real systems it is serialized as compressed or uncompressed curve-point bytes.
Message point
H(m) = 44PPairing protocols often hash a message or encode a polynomial term as a point in the other source group.
Pairing result
e(H(m), pk) = 64^28 mod 607 = 369This is the target-group element. It is not a public key. It is a value the verifier compares against another pairing output.
19Qe(44P, 19Q)44 * 19 mod 101 = 28369The useful fact is that the GT result depends on the product a * b:
e(aP, bQ) = e(P, Q)^(a*b)
So if two different-looking pairings hide the same scalar product, they land on the same target-group element:
e(aP, bQ) == e(cP, dQ)
when:
a*b == c*d
That target-group element lets a verifier compare hidden scalar products.
That single idea powers BLS signatures, KZG polynomial commitments, many zk-SNARK verifiers, threshold signatures, identity-based encryption, and a long list of more specialized protocols.
Open the interactive pairing playground
The Two Operations
Keep these two ideas separate.
First, ordinary elliptic-curve scalar multiplication hides a number inside a point:
secret: sk
public generator: Q
public key: pk = sk * Q
This gives you a public point. It does not give you a general way to compare products hidden across two different points.
Now suppose a message hash is also represented as a point:
message point: H(m) = h * P
public key: pk = sk * Q
The hidden product you care about is:
h * sk
A pairing can compare that product in two different forms:
e(sk * H(m), Q) == e(H(m), sk * Q)
Substitute H(m) = h * P:
left = e(sk * h * P, Q)
right = e(h * P, sk * Q)
Both sides contain the same hidden scalar product:
h * sk
The verifier learns that the products match. The verifier does not learn sk.
That is what “compare hidden scalar relationships” means.
What G1 x G2 -> GT Means
Use a tiny toy pairing:
source group order = 101
target modulus = 607
target generator = 64
Define:
e(aP, bQ) = 64^(a*b mod 101) mod 607
Now choose:
a = 12
b = 9
The source points are:
G1 point = 12P
G2 point = 9Q
The hidden product is:
12 * 9 mod 101 = 108 mod 101 = 7
The target-group element is:
e(12P, 9Q) = 64^7 mod 607 = 482
Now compare a different pair:
c = 3
d = 36
These are different source points:
3P is not 12P
36Q is not 9Q
But the hidden product is the same:
3 * 36 mod 101 = 108 mod 101 = 7
So the pairing result is the same:
e(12P, 9Q) = 482
e(3P, 36Q) = 482
That is the comparison trick.
The verifier is not saying:
12P == 3P
9Q == 36Q
Those are false.
The verifier is saying:
the hidden product inside (12P, 9Q)
equals
the hidden product inside (3P, 36Q)
Real systems do this with elliptic-curve groups and a real pairing such as the BLS12-381 pairing. The toy numbers only make the algebra visible.
What The Target Group Is
The target group GT is the group where pairing outputs live.
It is not G1. It is not G2. It is not another public key group.
In most explanations, G1 and G2 are written additively because they contain elliptic-curve points:
aP
bQ
P + P + P
GT is usually written multiplicatively because pairing outputs behave like powers:
e(P, Q)
e(P, Q)^7
e(P, Q)^74
In the toy model:
GT generator = 64
GT modulus = 607
So this pairing output:
e(12P, 9Q)
becomes:
64^(12*9 mod 101) mod 607
= 64^7 mod 607
= 482
The number 482 is the target-group element. It is useful because it can be compared with another target-group element.
Basic use:
e(12P, 9Q) == e(3P, 36Q)
because both hide the same product:
12*9 mod 101 = 3*36 mod 101 = 7
More advanced use:
e(A, B) * e(C, D) == e(E, F)
Now the verifier is multiplying target-group elements. This is why zk-SNARK verifiers often look like products of pairings. The verifier is doing algebra in GT, not trying to recover the original secrets.
A pairing output is a GT element
This toy target group is generated by 64 modulo 607. A pairing output is a value like 482, which represents a hidden exponent product modulo 101.
12P9Q482e(12P, 9Q)12 * 9 mod 101 = 764^7 mod 607 = 482GT stores the pairing result, not a curve public keyThe Short Version
A pairing is a map:
e: G1 x G2 -> GT
Read that as:
take one element from G1
take one element from G2
produce one element in GT
The useful property is bilinearity.
If P is a generator in G1, Q is a generator in G2, and a and b are scalars:
e(aP, bQ) = e(P, Q)^(a*b)
The pairing does not reveal a or b. It lets a verifier check whether the scalar relationship is consistent.
That is the core mental model:
ordinary EC crypto:
hide a scalar inside a point
pairing crypto:
compare scalar products hidden inside points
A Tiny Pairing Example
Use a toy pairing where:
group order = 101
target modulus = 607
target generator = 64
Define:
e(aP, bQ) = 64^(a*b mod 101) mod 607
Let:
a = 13
b = 29
Then:
a*b mod 101 = 13*29 mod 101
= 377 mod 101
= 74
So:
e(13P, 29Q) = 64^74 mod 607
The same result appears if you move one scalar outside:
e(13P, 29Q) = e(P, 29Q)^13
e(13P, 29Q) = e(13P, Q)^29
e(13P, 29Q) = e(P, Q)^(13*29)
That is not secure cryptography. It is a small exact model for the algebra.
Real pairings use pairing-friendly elliptic curves such as BLS12-381. Real implementations also need subgroup checks, point validation, serialization rules, domain separation, constant-time arithmetic, and hash-to-curve logic.
Why This Is Different From ECDSA Or Schnorr
In regular discrete-log signatures, the verifier checks equations inside one group.
Pairing-based signatures can put the signature in one group and the public key in another, then compare them in GT.
That is why BLS signatures are compact and aggregatable.
For a simplified BLS signature:
secret key: sk
public key: pk = sk * Q
message hash: H(m) in G1
signature: sig = sk * H(m)
The verifier checks:
e(sig, Q) == e(H(m), pk)
Substitute the definitions:
left = e(sk * H(m), Q)
right = e(H(m), sk * Q)
By bilinearity:
left = e(H(m), Q)^sk
right = e(H(m), Q)^sk
The verifier never sees sk, but both sides land on the same target-group element only when the signature and public key match the same hidden scalar.
Real Example: Ethereum Consensus BLS
Ethereum consensus uses BLS signatures for validator messages. The consensus specs define functions such as:
Sign(privkey, message)
Verify(pubkey, message, signature)
Aggregate(signatures)
FastAggregateVerify(pubkeys, message, signature)
AggregateVerify(pubkeys, messages, signature)
Ethereum uses BLS12-381 with validator public keys in G1 and signatures in G2. The simplified equation above used the opposite placement because both variants are common in explanations. The idea is the same:
public key hides sk
signature hides sk * message_hash
pairing check proves both used the same sk
The practical win is aggregation.
If many validators sign the same kind of consensus message, the network can carry and verify an aggregate signature instead of treating every signature as an isolated object.
The pairing is what makes that aggregation check possible.
Real Example: KZG Commitments
KZG is a polynomial commitment scheme.
It lets a prover commit to a polynomial and later prove:
f(z) = y
without sending the full polynomial.
The setup publishes powers of a hidden value tau:
G1, tau*G1, tau^2*G1, ...
G2, tau*G2, ...
The commitment to:
f(x) = c0 + c1*x + c2*x^2 + ...
is:
C = f(tau) * G1
To prove f(z) = y, the prover forms the quotient:
q(x) = (f(x) - y) / (x - z)
If the claimed y is correct, then x - z divides f(x) - y.
The proof is:
proof = q(tau) * G1
The verifier checks:
e(C - y*G1, G2) == e(proof, tau*G2 - z*G2)
Substitute the hidden evaluations:
left = e((f(tau) - y)G1, G2)
right = e(q(tau)G1, (tau - z)G2)
By bilinearity:
left = e(G1, G2)^(f(tau) - y)
right = e(G1, G2)^(q(tau) * (tau - z))
And because:
q(tau) * (tau - z) = f(tau) - y
the pairing check passes.
That is the algebra behind Ethereum's EIP-4844 blob proof verification path.
Real Example: zk-SNARK Verifiers
Many succinct proof systems use pairings to compress a large computation into a small verifier equation.
In a Groth16-style verifier, the verifier does not re-run the whole computation. It checks a small number of elliptic-curve proof elements against verification-key elements using pairings.
The final form is often a product of pairing outputs:
e(A, B) * e(C, D) * e(E, F) == target
The exact terms depend on the proof system and circuit. The pattern is the same:
encode polynomial relationships into group elements
use pairings to check those relationships succinctly
What Pairings Buy You
Pairings give protocols a way to publicly check equations about hidden exponents.
That enables:
- Short signatures.
- Signature aggregation.
- Threshold and multisignature constructions.
- Polynomial commitments.
- Constant-size KZG openings.
- Succinct proof verification.
- Identity-based encryption and other advanced primitives.
The cost is that pairing crypto is more delicate than ordinary elliptic-curve signing.
You need to care about:
- Choosing a pairing-friendly curve with enough security margin.
- Correct subgroup membership checks.
- Cofactor clearing.
- Hash-to-curve domain separation.
- Serialization and point validation.
- Trusted setup assumptions for schemes such as KZG and many SNARKs.
- Expensive pairing operations compared with ordinary scalar multiplication.
The Practical Mental Model
When you see a pairing equation, translate it like this:
e(left G1 element, left G2 element)
==
e(right G1 element, right G2 element)
means:
the hidden scalar product on the left
equals
the hidden scalar product on the right
That is why the same tool appears in BLS signatures, KZG commitments, and zk-SNARKs.
They are different protocols, but they all need the same capability:
verify algebra over hidden scalars
without revealing the scalars