Imagine a consensus protocol where 500,000 validators each attest to the same block. With ECDSA, that’s 500,000 signatures to gossip, store, and verify — roughly 32 MB of signature data per slot, half a million verifications. The chain would drown in its own cryptography.
Ethereum’s beacon chain handles exactly this scenario, and the artifact that lands on-chain is a single 96-byte signature plus a bitfield of who participated. That collapse — from hundreds of thousands of signatures down to one — is the headline feature of BLS, and it’s the thing that ECDSA cannot do at all and Schnorr can only do with a coordination protocol bolted on.
This piece walks through how BLS signatures work, the algebra behind aggregation, the one attack you have to defend against, and the use cases that are genuinely unique to the scheme. The Rust at the end compiles and runs.
Everything BLS does rests on a pairing — a map that takes two curve points and produces an element of a third group, with one magical property.
Take three groups of the same prime order r: G₁, G₂, and Gₜ. A pairing is a function
e : G₁ × G₂ → Gₜ
that is bilinear, meaning scalars can be slid across it freely:
e([a]P, [b]Q) = e(P, Q)^(a·b)
That single identity is the whole trick. It lets you “see” a multiplication that happened in the exponent of two separate points, without ever learning the scalars themselves. ECDSA and Schnorr live on ordinary elliptic curves where no such map exists; BLS lives on pairing-friendly curves where it does. In practice that curve is BLS12–381: a 381-bit prime field, embedding degree 12, around 128-bit security. Points in G₁ compress to 48 bytes, points in G₂ to 96 bytes.
BLS is almost embarrassingly small once you have the pairing. I’ll use the minimal-public-key layout (public keys in G₁, signatures in G₂), which is the one Ethereum and the blst library use. Let g₁ be a fixed generator of G₁, and let H be a hash that maps an arbitrary message onto a point in G₂.
Key generation. Pick a random scalar sk. The public key is a point:
pk = [sk] g₁ (a point in G₁, 48 bytes)
Signing. Hash the message to a curve point and multiply by the secret key:
σ = [sk] H(m) (a point in G₂, 96 bytes)
Note what’s missing: there is no nonce, no randomness, no per-signature k. For a given key and message there is exactly one valid signature. Hold onto that — it matters later.
Verification. Check one pairing equation:
e(g₁, σ) =? e(pk, H(m))
Why it holds is one line of bilinearity:
e(g₁, σ) = e(g₁, [sk] H(m)) = e(g₁, H(m))^sk
e(pk, H(m)) = e([sk] g₁, H(m)) = e(g₁, H(m))^sk ✓
The verifier never sees sk, yet the two sides only match if the signer knew it. That's the entire scheme.
Here’s where the pairing earns its keep. Because a signature is just a point and points add, you can sum signatures. There are two cases worth separating, because they have different costs and different security footguns.
This is the validator scenario. Everyone signs the same block m. Add up all the signatures:
σ_agg = σ₁ + σ₂ + … + σₙ = [sk₁ + sk₂ + … + skₙ] H(m)
and add up all the public keys into one aggregate key:
apk = pk₁ + pk₂ + … + pkₙ
Verification is then a single pairing equation, no matter how many signers were involved:
e(g₁, σ_agg) =? e(apk, H(m))
A thousand signers, a million signers — verification is still two pairings after a handful of cheap point additions. The on-chain footprint is one 96-byte signature. No other signature scheme gives you constant-size, constant-verification-cost aggregation without the signers coordinating.
Now suppose each signer signs their own message — think a batch of distinct transactions. You can still aggregate:
σ_agg = σ₁ + σ₂ + … + σₙ where σᵢ = [skᵢ] H(mᵢ)
but verification now needs one pairing per distinct message:
e(g₁, σ_agg) =? e(pk₁, H(m₁)) · e(pk₂, H(m₂)) · … · e(pkₙ, H(mₙ))
That’s n + 1 pairings. The win here is bandwidth and storage, not verification CPU — you ship and store one signature instead of n. The same-message case (one bitfield, two pairings) is the one that's almost free.
The same-message aggregation apk = Σ pkᵢ has a famous hole. Suppose honest parties publish pk₁. A malicious party registering afterward can advertise
pk₂ = [sk₂] g₁ − pk₁
Now the aggregate key is apk = pk₁ + pk₂ = [sk₂] g₁, whose secret the attacker knows entirely. They can sign on behalf of the "group" alone and forge the honest party's participation. The honest party never signed anything.
There are three standard defenses, corresponding to the three IETF ciphersuites in draft-irtf-cfrg-bls-signature:
Pick one deliberately. The naive Σ pkᵢ with no defense is exploitable.
Aggregation alone isn’t the full story. Here is the part the title promised — capabilities that are unique or dramatically simpler with BLS.
1. Non-interactive aggregation. This is the real differentiator. ECDSA has no native aggregation whatsoever. Schnorr does aggregate (MuSig2), but the signers must run an interactive protocol first — exchanging nonce commitments before anyone signs. BLS signers never talk to each other. Each signs independently, publishes, and any third party can aggregate the published signatures afterward, even years later. This is precisely why Ethereum can fold together attestations from validators who have no channel between them. With Schnorr you’d need every validator online and coordinating in a multi-round dance every slot.
2. Unique, deterministic signatures → verifiable randomness. Because there’s no nonce, σ = [sk] H(m) is the signature — there is exactly one. ECDSA and Schnorr both inject a per-signature nonce, so a signer can produce many valid signatures for the same message and choose among them. Uniqueness turns a BLS signature into a Verifiable Random Function: the drand beacon (the "League of Entropy") signs each round number with a threshold key, and the unique resulting signature, hashed, is unbiasable public randomness. You cannot build this from ECDSA or Schnorr, because the signer could grind the nonce to skew the output. Determinism is a feature here, not a footnote.
3. Dead-simple threshold signatures. BLS is linear in the key — σ_agg = [Σ skᵢ] H(m) — so Shamir secret sharing composes with it directly. A t-of-n set of partial signatures Lagrange-interpolates into the single group signature, and the result is identical no matter which t signers showed up. That canonical output is exactly what makes the VRF use work. Threshold ECDSA exists, but it's a heavyweight MPC affair (GG18/GG20 and friends) with multiple interactive rounds; threshold BLS is interpolation.
4. Incremental aggregation in gossip networks. Aggregation is just point addition, so a node can keep folding new signatures into a running aggregate as they trickle in over the network — no need to collect them all first.
BLS is not free, and a balanced article says so.
Here’s a complete, compiling example against the blst crate (the same library Ethereum consensus clients use). It exercises a single signature, same-message aggregation over 1,000 signers, distinct-message aggregation, and tamper rejection.
rust
use blst::min_pk::{AggregateSignature, PublicKey, SecretKey, Signature};
use blst::BLST_ERROR;
// Standard DST for Ethereum-style minimal-pubkey BLS.
const DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_";
fn keypair(seed: &[u8; 32]) -> (SecretKey, PublicKey) {
let sk = SecretKey::key_gen(seed, &[]).expect("key_gen");
let pk = sk.sk_to_pk();
(sk, pk)
}
fn main() {
// 1. One signer, one message.
let (sk, pk) = keypair(&[1u8; 32]);
let msg = b"settle block #42";
let sig = sk.sign(msg, DST, &[]);
assert_eq!(sig.verify(true, msg, DST, &[], &pk, true), BLST_ERROR::BLST_SUCCESS);
println!("signature: {} bytes, pubkey: {} bytes",
sig.compress().len(), pk.compress().len());
// 2. Same message, 1000 signers -> ONE signature (fast_aggregate_verify).
let n = 1000;
let mut sks = Vec::new();
let mut pks = Vec::new();
for i in 0..n {
let mut seed = [0u8; 32];
seed[..8].copy_from_slice(&(i as u64 + 7).to_le_bytes());
let (s, p) = keypair(&seed);
sks.push(s);
pks.push(p);
}
let block = b"finalize epoch 17";
let sigs: Vec<Signature> = sks.iter().map(|s| s.sign(block, DST, &[])).collect();
// Anyone can aggregate the published signatures - no coordination needed.
let sig_refs: Vec<&Signature> = sigs.iter().collect();
let agg = AggregateSignature::aggregate(&sig_refs, true).unwrap().to_signature();
let pk_refs: Vec<&PublicKey> = pks.iter().collect();
assert_eq!(agg.fast_aggregate_verify(true, block, DST, &pk_refs),
BLST_ERROR::BLST_SUCCESS);
println!("{} sigs on one message -> {} bytes", n, agg.compress().len());
// 3. Distinct messages, distinct signers (aggregate_verify).
let (sk_a, pk_a) = keypair(&[10u8; 32]);
let (sk_b, pk_b) = keypair(&[20u8; 32]);
let (sk_c, pk_c) = keypair(&[30u8; 32]);
let (m_a, m_b, m_c): (&[u8], &[u8], &[u8]) =
(b"alice->bob:5", b"bob->carol:3", b"carol->alice:1");
let s_a = sk_a.sign(m_a, DST, &[]);
let s_b = sk_b.sign(m_b, DST, &[]);
let s_c = sk_c.sign(m_c, DST, &[]);
let agg2 = AggregateSignature::aggregate(&[&s_a, &s_b, &s_c], true)
.unwrap().to_signature();
let msgs: [&[u8]; 3] = [m_a, m_b, m_c];
let pks2 = [&pk_a, &pk_b, &pk_c];
assert_eq!(agg2.aggregate_verify(true, &msgs, DST, &pks2, true),
BLST_ERROR::BLST_SUCCESS);
// 4. Tampering is caught.
let forged: [&[u8]; 3] = [m_a, b"bob->carol:3000", m_c];
assert_ne!(agg2.aggregate_verify(true, &forged, DST, &pks2, true),
BLST_ERROR::BLST_SUCCESS);
println!("all checks passed.");
}
Output:
signature: 96 bytes, pubkey: 48 bytes
1000 sigs on one message -> 96 bytes
all checks passed.
A thousand signatures, one 96-byte artifact, verified against a thousand public keys with two pairings. That’s the whole pitch.
ECDSA gives you signatures. Schnorr gives you signatures that aggregate if everyone coordinates first. BLS gives you signatures that aggregate after the fact, by anyone, with no coordination at all — and as a bonus, because they’re unique and deterministic, they double as a source of verifiable randomness and slot cleanly into threshold schemes. That combination is why it underpins Ethereum consensus, drand, Chia, and most modern threshold-cryptography stacks.
The cost is real: pairings are slow, the curves are heavy, and the rogue-key attack will bite you if you skip proof-of-possession. But for any system where you’re collecting independent attestations at scale, nothing else comes close.
Follow me on X.
One Signature to Verify Them All was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.

