FROST Explained: Schnorr Threshold Signatures for Bitcoin

Alexander Nemish

Introduction

Imagine a Bitcoin wallet controlled by a company with five board members. You want a rule: any three of the five can authorize a transaction, but no one or two members can steal the funds. And when the transaction hits the blockchain, it should look exactly like any other — nobody can tell it was signed by a group.

This is exactly what FROST does. FROST stands for Flexible Round-Optimized Schnorr Threshold Signatures. It lets tt out of nn participants jointly produce a Schnorr signature — without any single party ever knowing the full secret key. The output is a standard Schnorr signature, indistinguishable from one created by a single signer.

This is directly relevant to Bitcoin. Since the Taproot upgrade (2021), Bitcoin natively supports Schnorr signatures (BIP-340) for transaction authorization. FROST produces standard Schnorr signatures on the secp256k1 curve — exactly what Bitcoin expects. This makes FROST a natural fit for threshold signing of Bitcoin transactions: custody solutions, cross-chain bridges, or any setup where multiple parties must approve a spend, without revealing on-chain that a threshold scheme was used at all.

Why not just use a regular private key? Because a single key is a single point of failure. Stolen key — all funds gone. Lost key — all funds locked forever. FROST distributes trust: no single participant can sign alone, and any tt of the nn participants can cooperate to sign. The rest can be offline, unreachable, or even compromised — as long as fewer than tt are malicious, the key is safe.

To understand how FROST achieves this, we'll build up from its foundations, one step at a time:

  1. Schnorr signatures — the signature scheme FROST is built on
  2. Nonce aggregation — how multiple signers combine their randomness into one
  3. Partial signature aggregation — how individual contributions sum to a valid signature
  4. Shamir secret sharing — how to split a secret so that any tt of nn pieces can reconstruct it
  5. Lagrange interpolation — the mathematical tool that makes reconstruction work
  6. The FROST protocol — putting it all together

Throughout the article, we'll use concrete numeric examples you can verify by hand. In real cryptography, all numbers are 256-bit integers and the math happens on elliptic curves, but the logic is identical.

Schnorr Signatures

Before we can distribute a signature across multiple parties, we need to understand how a single-signer Schnorr signature works.

The setup: elliptic curve math in 30 seconds

All the math happens in a cyclic group — think of it as a set of points on an elliptic curve. The group has a generator point GG and a prime order qq (the total number of points). We have one operation: scalar multiplication — multiplying GG by an integer ss to get a new point sGs \cdot G.

Elliptic curve point addition: P + Q is found by drawing a line through P and Q, finding the third intersection, and reflecting it

The critical property: given ss and GG, computing sGs \cdot G is trivial (milliseconds on a laptop). But given sGs \cdot G and GG, recovering ss is computationally infeasible — this is the discrete logarithm problem, and it's the foundation of elliptic curve cryptography.

Think of it like mixing paint: combining yellow and blue to get green is easy, but looking at the green and figuring out the exact ratio of yellow and blue is practically impossible.

With that in mind, a signer has:

  • Secret key: a random number ss (a "scalar" — just a big integer modulo qq)
  • Public key: Y=sGY = s \cdot G (a point on the curve — easy to compute from ss, impossible to reverse)

How signing works

To sign a message mm, the signer:

  1. Picks a random nonce kk and computes the commitment R=kGR = k \cdot G. The nonce is a one-time secret random number. "Commitment" because RR publicly commits the signer to their choice of kk without revealing kk itself (discrete log again).

  2. Computes the challenge c=H(RYm)c = H(R \| Y \| m). This hashes together the commitment, the public key, and the message. The hash acts as an unpredictable "question" that the signer must answer — and crucially, it depends on RR, so the signer can't know cc before committing to their nonce. We'll see why this matters right after verification.

  3. Computes the response z=k+scz = k + s \cdot c. This is the core equation — it blends the nonce with the secret key, weighted by the challenge. It's the "answer" to the challenge that proves the signer knows ss.

  4. Outputs the signature σ=(R,z)\sigma = (R, z) — the commitment point and the response scalar.

How verification works

Anyone with the public key YY can verify the signature by checking:

zG=?R+cYz \cdot G \stackrel{?}{=} R + c \cdot Y

This reads: "multiply the generator by the response zz, and check if it equals the commitment RR plus the challenge times the public key."

Why does this equation hold? Let's expand the right side by substituting what RR and YY actually are:

R+cY=kG+c(sG)=(k+sc)G=zGR + c \cdot Y = k \cdot G + c \cdot (s \cdot G) = (k + s \cdot c) \cdot G = z \cdot G \quad \checkmark

The equation works because we defined z=k+scz = k + s \cdot c. A verifier who doesn't know kk or ss can still check this — they only need the public values RR, YY, cc, and zz.

Why RR must be in the challenge hash

Now that we've seen how verification works, we can understand why the challenge c=H(RYm)c = H(R \| Y \| m) must include RR. Without it, the scheme is completely broken — anyone can forge signatures without knowing the secret key.

Suppose the challenge were just c=H(Ym)c = H(Y \| m) — no RR. An attacker wants to forge a signature on message mm under public key YY:

  1. Compute c=H(Ym)c = H(Y \| m) — this is public knowledge, no secrets needed.
  2. Pick any random zz.
  3. Compute R=zGcYR = z \cdot G - c \cdot Y.
  4. Output (R,z)(R, z) as the "signature."

Does it verify? zG=?R+cY=(zGcY)+cY=zGz \cdot G \stackrel{?}{=} R + c \cdot Y = (z \cdot G - c \cdot Y) + c \cdot Y = z \cdot G. Yes — a complete forgery without knowing the secret key.

The attack works because the forger gets to choose zz and RR simultaneously, engineering them to satisfy the verification equation. By including RR in the hash, we force the signer to commit to RR before the challenge cc is determined. A forger would need to find RR such that c=H(RYm)c = H(R \| Y \| m) and zG=R+cYz \cdot G = R + c \cdot Y hold simultaneously — but they can't know cc until they fix RR, and they can't construct RR without knowing cc. This circular dependency is what makes forgery infeasible.

This construction is called the Fiat-Shamir transform (Fiat & Shamir, CRYPTO 1986): it converts an interactive identification protocol into a non-interactive signature scheme by replacing the verifier's random challenge with a hash of the commitment. Pointcheval & Stern (EUROCRYPT 1996) proved that this construction is existentially unforgeable in the random oracle model.

Why YY must be in the challenge hash

Including RR prevents forgery, but why is the public key YY also in the hash? Without it, signatures aren't bound to a specific key — an attacker can take someone else's valid signature and claim it was signed by a different key.

Suppose the challenge were c=H(Rm)c = H(R \| m) — no YY. Given a valid signature (R,z)(R, z) for message mm under public key YY, an attacker can find a different public key YY' for which the same signature is also valid:

  1. Start with the verification equation: zG=R+cYz \cdot G = R + c \cdot Y.
  2. Rearrange: Y=c1(zGR)Y' = c^{-1} \cdot (z \cdot G - R).
  3. The pair (R,z)(R, z) now verifies under YY' too, because R+cY=R+cc1(zGR)=zGR + c \cdot Y' = R + c \cdot c^{-1} \cdot (z \cdot G - R) = z \cdot G.

The attacker doesn't need to know the secret key for YY' — they just need to find a point that makes the equation balance. This is called a key substitution attack (or "duplicate signature key selection").

Why does this matter in practice? Consider a multi-party protocol where you need to prove that a specific participant signed a message. Without YY in the hash, an adversary could take a legitimate signature from Alice and reuse it to frame Bob — or vice versa. In threshold signing like FROST, this is especially dangerous: the protocol relies on verifying that each participant produced their own valid signature share under their own verification key YpY_p.

Including YY in the hash ties the challenge (and therefore the entire signature) to a specific public key. The same (R,z)(R, z) pair produces a different cc for a different YY, so it won't verify under any other key.

This is analyzed in the multi-user security setting by Menezes & Smart (2004). Ed25519 and BIP-340 (Bitcoin's Schnorr scheme) both include YY in the hash for this reason.

Why the nonce is the most critical detail

Look at the response equation again: z=k+scz = k + s \cdot c. Both zz (published in the signature) and cc (computed from public data) are known to everyone. The only thing stopping an attacker from solving for the secret key ss is the nonce kk:

s=zkcs = \frac{z - k}{c}

If the nonce leaks, the secret key is immediately exposed. If the same nonce kk is reused for two different messages (producing two different challenges c1,c2c_1, c_2 and responses z1,z2z_1, z_2), an attacker can set up two equations and solve for ss:

z1=k+sc1z_1 = k + s \cdot c_1 z2=k+sc2z_2 = k + s \cdot c_2

Subtract: z1z2=s(c1c2)z_1 - z_2 = s \cdot (c_1 - c_2), so s=z1z2c1c2s = \frac{z_1 - z_2}{c_1 - c_2}. Game over. This is why nonces must be truly random, used exactly once, and immediately deleted. This matters even more in FROST, where multiple parties each contribute nonces.

A concrete example with small numbers

In real systems these are 256-bit numbers, but the math is identical with small ones. Let's work modulo q=23q = 23.

  • Secret key: s=7s = 7
  • Public key: Y=7GY = 7G
  • Nonce: k=13k = 13, so commitment: R=13GR = 13G
  • Suppose the hash gives us: c=H(RYm)=5c = H(R \| Y \| m) = 5
  • Response: z=k+sc=13+75=482(mod23)z = k + s \cdot c = 13 + 7 \cdot 5 = 48 \equiv 2 \pmod{23}

Verification: Is zG=R+cYz \cdot G = R + c \cdot Y?

  • Left side: 2G2G
  • Right side: 13G+57G=13G+35G=48G2G(mod23)13G + 5 \cdot 7G = 13G + 35G = 48G \equiv 2G \pmod{23}

Both sides equal 2G2G. The signature is valid.

Aggregating nonce R

Now let's move toward multi-party signing. Suppose two signers want to jointly produce a signature. Each picks their own random nonce: signer 1 picks k1k_1 and publishes R1=k1GR_1 = k_1 \cdot G, signer 2 picks k2k_2 and publishes R2=k2GR_2 = k_2 \cdot G.

The simplest idea: just add them together.

R=R1+R2=(k1+k2)GR = R_1 + R_2 = (k_1 + k_2) \cdot G

The group's nonce is k1+k2k_1 + k_2 and the group's commitment is R1+R2R_1 + R_2. This is called additive nonce aggregation, and it almost works — except for a devastating attack.

Why naive aggregation is dangerous

Suppose signer 2 is malicious. The honest signer 1 publishes R1R_1 first. The attacker waits, sees R1R_1, and then computes:

R2=RtargetR1R_2 = R_{\text{target}} - R_1

for some RtargetR_{\text{target}} they chose in advance. Now the aggregate is R=R1+R2=RtargetR = R_1 + R_2 = R_{\text{target}} — the attacker has complete control over the group commitment.

Why is this bad? The challenge c=H(RYm)c = H(R \| Y \| m) depends on RR. If the attacker controls RR, they can precompute the challenge and craft their contribution to forge a signature. This is called a nonce manipulation attack.

The root cause: the attacker gets to see the honest party's commitment before choosing their own. They can "cancel out" the honest contribution and replace it with whatever they want.

The fix: binding factors

FROST solves this by adding a twist. Instead of each signer committing to one nonce value, they commit to two values, and these get combined using a binding factor — a hash-derived number that depends on everyone's commitments.

Each participant PpP_p publishes two commitments:

  • Dp=dpGD_p = d_p \cdot G (the "hiding" commitment)
  • Ep=epGE_p = e_p \cdot G (the "binding" commitment)

After everyone has published, the binding factor for each participant is computed as:

ρp=H(Yall commitmentsmp)\rho_p = H\big(Y \| \text{all commitments} \| m \| p\big)

The group commitment is then:

R=pS(Dp+ρpEp)R = \sum_{p \in S} \left( D_p + \rho_p \cdot E_p \right)

The key insight: ρp\rho_p is a hash of all commitments, so it can only be computed after everyone has already published (Dp,Ep)(D_p, E_p). If any signer changes their commitment, every binding factor changes, invalidating any precomputed attack. The attacker can no longer manipulate RR because they'd need to predict the hash output before it's computable.

Why two commitments instead of one?

If each signer only published one commitment RpR_p and we mixed it with a binding factor as ρpRp\rho_p \cdot R_p, the attacker could still manipulate things — they have one unknown (RpR_p) but also one equation to satisfy. Two independent commitments (Dp,Ep)(D_p, E_p) combined as Dp+ρpEpD_p + \rho_p \cdot E_p give the binding factor something to "entangle" with. The attacker would need to predict ρp\rho_p before choosing EpE_p, but ρp\rho_p depends on EpE_p — a chicken-and-egg problem that defeats the attack.

Why does FROST need per-participant binding factors?

In MuSig2 (an nn-of-nn multisignature scheme where all signers must participate), a single binding factor bb — the same for everyone — is sufficient. That's because the signing set is fixed: the adversary can't choose which honest parties to include.

FROST is a threshold scheme (tt-of-nn): only tt signers are needed. An adversary controlling t1t-1 parties can start multiple parallel signing sessions, each time pairing their corrupt parties with a different honest signer. If the binding factor were the same for all participants, the adversary could correlate honest signers' nonces across sessions and forge signatures (this is called Wagner's generalized birthday attack).

FROST defeats this by including the participant's identifier pp in the binding factor hash. Different participants get different ρp\rho_p values, so the adversary can't correlate nonce contributions across sessions.

Aggregating partial signatures

Now we know how to safely combine nonces. The next question: how do we combine each signer's contribution into a single valid Schnorr signature?

Each signer produces a signature share (also called a partial signature) — a number zpz_p computed from their own secrets and nonces. The design ensures that when you add up all the shares, you get a valid Schnorr response.

The signature share equation

Recall that a standard Schnorr response is z=k+scz = k + s \cdot c. In FROST, each participant PpP_p computes their share as:

zp=dp+ρpep+λpspcz_p = d_p + \rho_p \cdot e_p + \lambda_p \cdot s_p \cdot c

Let's break down each term:

  • dp+ρpepd_p + \rho_p \cdot e_p — this is PpP_p's contribution to the aggregate nonce. It matches their commitments Dp+ρpEpD_p + \rho_p \cdot E_p that went into computing RR.
  • λp\lambda_p — the Lagrange coefficient, a weight that depends on which set of signers is participating. We'll explain this in detail in the next sections. For now, just know that these weights ensure the secret shares add up correctly.
  • sps_p — participant PpP_p's secret share (their piece of the group secret key).
  • c=H(RYm)c = H(R \| Y \| m) — the challenge, same as in regular Schnorr.

Why the sum of shares equals a valid signature

When we add up all tt signature shares:

z=pSzp=pS(dp+ρpep)adds up to k+  cpSλpspadds up to sz = \sum_{p \in S} z_p = \underbrace{\sum_{p \in S} (d_p + \rho_p \cdot e_p)}_{\text{adds up to } k} + \; c \cdot \underbrace{\sum_{p \in S} \lambda_p \cdot s_p}_{\text{adds up to } s}

Two things happen:

  1. The nonce parts sum to kk — the discrete log of the group commitment RR. This is by construction: RR was defined as the sum of (Dp+ρpEp)(D_p + \rho_p \cdot E_p), so the corresponding scalars sum to kk.

  2. The weighted secret shares sum to ss — the group secret key. This is where Lagrange interpolation (covered below) does its magic: the λp\lambda_p weights are chosen so that λpsp=s\sum \lambda_p \cdot s_p = s.

Together: z=k+scz = k + s \cdot c, which is exactly a valid Schnorr response. The verifier checks zG=?R+cYz \cdot G \stackrel{?}{=} R + c \cdot Y and it passes — nobody needs to know that zz was computed by multiple parties.

Checking individual shares (identifiable abort)

Before adding the shares up, the Coordinator can verify each one individually:

zpG=?Dp+ρpEp+(λpc)Ypz_p \cdot G \stackrel{?}{=} D_p + \rho_p \cdot E_p + (\lambda_p \cdot c) \cdot Y_p

where Yp=spGY_p = s_p \cdot G is PpP_p's public verification share — a publicly known point that corresponds to their secret share. This check uses only public values (no secrets revealed). If a participant sends a bad share, the Coordinator can identify them and abort. This is why FROST has identifiable abort — you can always tell who cheated.

A concrete example

Let's trace through partial signature aggregation with numbers. We'll use the Shamir shares from the next section (feel free to jump ahead and come back):

  • t=2t = 2, n=3n = 3, signing set S={1,2}S = \{1, 2\}
  • Group secret: s=16s = 16, challenge: c=5c = 5
  • Secret shares: s1=22s_1 = 22, s2=28s_2 = 28
  • Lagrange coefficients for S={1,2}S = \{1, 2\}: λ1=2\lambda_1 = 2, λ2=1\lambda_2 = -1
  • Nonces: d1=4d_1 = 4, e1=3e_1 = 3, d2=6d_2 = 6, e2=2e_2 = 2
  • Binding factors: ρ1=7\rho_1 = 7, ρ2=11\rho_2 = 11

Each participant computes their share by plugging into zp=dp+ρpep+λpspcz_p = d_p + \rho_p \cdot e_p + \lambda_p \cdot s_p \cdot c:

z1=4+73+2225=4+21+220=245z_1 = 4 + 7 \cdot 3 + 2 \cdot 22 \cdot 5 = 4 + 21 + 220 = 245 z2=6+112+(1)285=6+22140=112z_2 = 6 + 11 \cdot 2 + (-1) \cdot 28 \cdot 5 = 6 + 22 - 140 = -112

The Coordinator sums them:

z=z1+z2=245+(112)=133z = z_1 + z_2 = 245 + (-112) = 133

Let's verify this equals k+sck + s \cdot c. The aggregate nonce is k=(4+73)+(6+112)=25+28=53k = (4 + 7 \cdot 3) + (6 + 11 \cdot 2) = 25 + 28 = 53. The reconstructed secret is λpsp=222+(1)28=16=s\sum \lambda_p \cdot s_p = 2 \cdot 22 + (-1) \cdot 28 = 16 = s. So:

k+sc=53+165=133k + s \cdot c = 53 + 16 \cdot 5 = 133 \quad \checkmark

It works. The sum of partial signatures is a valid Schnorr response.

Shamir Secret Sharing

We've been using "secret shares" sps_p and "Lagrange coefficients" λp\lambda_p without fully explaining where they come from. Let's fix that.

The core problem: we need to split the secret key ss among nn participants so that:

  • Any tt participants can reconstruct ss (by combining their shares)
  • Any t1t - 1 participants learn absolutely nothing about ss

This is Shamir's Secret Sharing (1979), and it's built on a beautifully simple idea from algebra.

The key insight: polynomials and points

A straight line (y=ax+by = ax + b) is uniquely determined by 2 points. A parabola (y=ax2+bx+cy = ax^2 + bx + c) is uniquely determined by 3 points. In general, a polynomial of degree dd is uniquely determined by d+1d + 1 points.

Shamir's trick: to create a (t,n)(t, n) sharing scheme, embed the secret as the constant term of a random polynomial of degree t1t - 1. Then distribute nn points on that polynomial. Any tt points determine the polynomial (and thus the secret), but t1t - 1 points leave the secret completely undetermined.

How it works

The dealer (the entity splitting the secret) picks a random polynomial of degree t1t - 1:

f(x)=s+a1x+a2x2++at1xt1f(x) = s + a_1 x + a_2 x^2 + \cdots + a_{t-1} x^{t-1}

The constant term is the secret: f(0)=sf(0) = s. The other coefficients a1,,at1a_1, \dots, a_{t-1} are random. Each participant PpP_p receives the share sp=f(p)s_p = f(p) — the polynomial evaluated at their index.

Concrete example

Let's set up a (2,3)(2, 3) scheme — any 2 of 3 participants can reconstruct, 1 participant alone learns nothing. Our secret is s=16s = 16.

Since t=2t = 2, we need a degree-1 polynomial (a line): f(x)=16+6xf(x) = 16 + 6x (the coefficient 66 is chosen randomly).

Compute the shares by evaluating ff at each participant's index:

  • P1P_1 gets s1=f(1)=16+6=22s_1 = f(1) = 16 + 6 = 22
  • P2P_2 gets s2=f(2)=16+12=28s_2 = f(2) = 16 + 12 = 28
  • P3P_3 gets s3=f(3)=16+18=34s_3 = f(3) = 16 + 18 = 34
Shamir (2,3) secret sharing: the secret s = 16 is the y-intercept, shares are points on the line f(x) = 16 + 6x

Why is this secure? The shares are points on a line. Two points determine a line, so any 2 shares let you find ff and compute f(0)=16=sf(0) = 16 = s. But a single point, say (1,22)(1, 22), tells you nothing: for any possible secret ss', there's a line through (1,22)(1, 22) with yy-intercept ss'. One share is consistent with every possible secret.

The dealer problem: who picks the polynomial?

In the basic scheme, a single dealer knows the secret, creates the polynomial, and distributes shares. But in a decentralized system (like a blockchain bridge), there shouldn't be anyone who ever knows the full secret. If the dealer is compromised during setup, the whole scheme fails.

The solution is Distributed Key Generation (DKG) — the participants collectively generate a shared secret where nobody, not even during setup, ever knows the full key.

Pedersen DKG: generating a shared secret with no trusted dealer

The idea is elegant: instead of one dealer picking one polynomial, every participant picks their own random polynomial and shares pieces of it with everyone else. The group secret emerges as the sum of everyone's individual secrets, but no one ever computes that sum.

Here's how it works. Each of the nn participants PjP_j independently:

  1. Picks a random polynomial fj(x)f_j(x) of degree t1t - 1. The constant term aj,0a_{j,0} is PjP_j's private contribution to the group secret.

  2. Publishes commitments to their polynomial's coefficients: Cj,k=aj,kGC_{j,k} = a_{j,k} \cdot G for each coefficient kk. These are elliptic curve points — they "commit" PjP_j to their polynomial without revealing the actual coefficients (the discrete log problem protects them).

  3. Privately sends fj(p)f_j(p) to each participant PpP_p. This is the value of PjP_j's polynomial evaluated at PpP_p's index.

  4. Publishes a proof that they know their secret aj,0a_{j,0} (a Schnorr proof of knowledge). This prevents a "rogue-key attack" where a malicious participant claims a carefully crafted public key to manipulate the group key.

After everyone has done this, each participant PpP_p adds up all the values they received:

sp=j=1nfj(p)s_p = \sum_{j=1}^{n} f_j(p)

This is PpP_p's combined secret share. It's a sum of contributions from every participant.

The group secret (which nobody ever computes explicitly!) is:

s=j=1nfj(0)=j=1naj,0s = \sum_{j=1}^{n} f_j(0) = \sum_{j=1}^{n} a_{j,0}

The group public key is computable from the published commitments:

Y=sG=j=1nCj,0Y = s \cdot G = \sum_{j=1}^{n} C_{j,0}

Why does this work as Shamir sharing? The combined shares sps_p are points on a "virtual" polynomial F(x)=j=1nfj(x)F(x) = \sum_{j=1}^{n} f_j(x). This polynomial has degree t1t - 1 (sum of degree-(t1)(t-1) polynomials) and F(0)=sF(0) = s. So the combined shares are exactly Shamir shares of the group secret ss — even though nobody ever chose FF deliberately.

Concrete DKG example

n=3n = 3, t=2t = 2. Each participant independently picks a degree-1 (line) polynomial:

  • P1P_1 picks: f1(x)=7+3xf_1(x) = 7 + 3x (their secret contribution is 77)
  • P2P_2 picks: f2(x)=5+2xf_2(x) = 5 + 2x (their secret contribution is 55)
  • P3P_3 picks: f3(x)=4+xf_3(x) = 4 + x (their secret contribution is 44)

Now each participant evaluates their polynomial at everyone's index and sends the result privately:

Sender evaluates at →P₁ (p=1)P₂ (p=2)P₃ (p=3)
P₁: f₁(p)7 + 3 = 107 + 6 = 137 + 9 = 16
P₂: f₂(p)5 + 2 = 75 + 4 = 95 + 6 = 11
P₃: f₃(p)4 + 1 = 54 + 2 = 64 + 3 = 7

Each participant sums the column they received:

  • P1P_1: s1=10+7+5=22s_1 = 10 + 7 + 5 = 22
  • P2P_2: s2=13+9+6=28s_2 = 13 + 9 + 6 = 28
  • P3P_3: s3=16+11+7=34s_3 = 16 + 11 + 7 = 34

The virtual polynomial is: F(x)=(7+5+4)+(3+2+1)x=16+6xF(x) = (7 + 5 + 4) + (3 + 2 + 1)x = 16 + 6x

The group secret is: s=F(0)=16s = F(0) = 16 — but nobody computed this! P1P_1 only knows 77 (their own contribution), P2P_2 only knows 55, P3P_3 only knows 44.

The group public key: Y=16G=7G+5G+4GY = 16G = 7G + 5G + 4G — anyone can compute this from the published commitments.

Feldman VSS: verifying that shares are correct

There's a trust problem: when PjP_j sends fj(p)f_j(p) to PpP_p, how does PpP_p know the value is correct? PjP_j could send garbage or strategically wrong values. We need a way to verify shares without revealing the polynomial.

This is where the published commitments Cj,k=aj,kGC_{j,k} = a_{j,k} \cdot G become essential. Participant PpP_p can check:

fj(p)G=?k=0t1pkCj,kf_j(p) \cdot G \stackrel{?}{=} \sum_{k=0}^{t-1} p^k \cdot C_{j,k}

Left side: multiply the generator by the share value (a single scalar-point multiplication).

Right side: evaluate the "committed polynomial" at pp using the public commitments. This works because:

k=0t1pkCj,k=k=0t1pkaj,kG=(k=0t1aj,kpk)G=fj(p)G\sum_{k=0}^{t-1} p^k \cdot C_{j,k} = \sum_{k=0}^{t-1} p^k \cdot a_{j,k} \cdot G = \left(\sum_{k=0}^{t-1} a_{j,k} \cdot p^k\right) \cdot G = f_j(p) \cdot G

Both sides compute fj(p)Gf_j(p) \cdot G by different paths. The left side uses the secret share value. The right side uses only public commitments. If they match, the share is consistent with the published commitments. If they don't, PjP_j cheated and can be identified.

The beauty: the verification happens entirely "in the exponent" (on elliptic curve points). Nobody learns the polynomial coefficients — they only verify consistency.

Lagrange Interpolation

We now have tt participants, each holding a share sp=F(p)s_p = F(p) — a point on a secret polynomial FF of degree t1t - 1. We need to recover F(0)=sF(0) = s (the group secret) without anyone actually reconstructing the polynomial.

Lagrange interpolation gives us a formula that computes F(0)F(0) directly from tt points, as a weighted sum of the share values.

Building the intuition

Suppose you have 2 points on a line and want to find where it crosses the yy-axis (i.e., the value at x=0x = 0). You could find the slope, write the line equation, and plug in x=0x = 0. Lagrange interpolation is a general formula that does this for polynomials of any degree — and it gives the answer as a weighted sum of the known yy-values.

The formula

Given a signing set SS of tt participants, each with share sp=F(p)s_p = F(p), the secret is:

s=F(0)=pSλpsps = F(0) = \sum_{p \in S} \lambda_p \cdot s_p

where the Lagrange coefficient for participant PpP_p is:

λp=qSqpqqp\lambda_p = \prod_{\substack{q \in S \\ q \neq p}} \frac{q}{q - p}

This formula looks intimidating, but it's just a product of fractions involving the participant indices. Let's see it in action.

Where does this formula come from?

The Lagrange interpolation formula for a polynomial g(x)g(x) passing through points (p,g(p))(p, g(p)) for pSp \in S is:

g(x)=pSg(p)qSqpxqpqLp(x)g(x) = \sum_{p \in S} g(p) \cdot \underbrace{\prod_{\substack{q \in S \\ q \neq p}} \frac{x - q}{p - q}}_{L_p(x)}

Each Lp(x)L_p(x) is called a Lagrange basis polynomial. It has a key property: Lp(p)=1L_p(p) = 1 and Lp(q)=0L_p(q) = 0 for every other qSq \in S. So each basis polynomial "selects" exactly one data point.

We only need g(0)g(0), so we plug in x=0x = 0:

g(0)=pSg(p)qSqp0qpq=pSg(p)qSqpqqpg(0) = \sum_{p \in S} g(p) \cdot \prod_{\substack{q \in S \\ q \neq p}} \frac{0 - q}{p - q} = \sum_{p \in S} g(p) \cdot \prod_{\substack{q \in S \\ q \neq p}} \frac{q}{q - p}

The last step is the sign flip: 0qpq=qpq=qqp\frac{0-q}{p-q} = \frac{-q}{p-q} = \frac{q}{q-p}.

Concrete examples

Using our shares: s1=22s_1 = 22, s2=28s_2 = 28, s3=34s_3 = 34. The secret is s=16s = 16.

With participants S={1,2}S = \{1, 2\}:

λ1=221=2,λ2=112=1\lambda_1 = \frac{2}{2 - 1} = 2, \qquad \lambda_2 = \frac{1}{1 - 2} = -1 s=222+(1)28=4428=16s = 2 \cdot 22 + (-1) \cdot 28 = 44 - 28 = 16 \quad \checkmark

With participants S={2,3}S = \{2, 3\}:

λ2=332=3,λ3=223=2\lambda_2 = \frac{3}{3 - 2} = 3, \qquad \lambda_3 = \frac{2}{2 - 3} = -2 s=328+(2)34=8468=16s = 3 \cdot 28 + (-2) \cdot 34 = 84 - 68 = 16 \quad \checkmark

With participants S={1,3}S = \{1, 3\}:

λ1=331=32,λ3=113=12\lambda_1 = \frac{3}{3 - 1} = \frac{3}{2}, \qquad \lambda_3 = \frac{1}{1 - 3} = -\frac{1}{2} s=3222+(12)34=3317=16s = \frac{3}{2} \cdot 22 + \left(-\frac{1}{2}\right) \cdot 34 = 33 - 17 = 16 \quad \checkmark

Every pair recovers the same secret. The Lagrange coefficients are different for different sets of participants, but the weighted sum always yields s=16s = 16. This is the mathematical guarantee of Shamir's scheme.

The critical property for FROST

Notice that Lagrange interpolation is a linear operation — the secret is a weighted sum of the shares:

pSλpsp=s\sum_{p \in S} \lambda_p \cdot s_p = s

This linearity is what makes threshold Schnorr signatures possible. Each participant multiplies their share sps_p by λpc\lambda_p \cdot c and contributes that to the signature. Nobody reconstructs ss — they each contribute their weighted piece, and the sum magically equals scs \cdot c.

In curve-point terms:

pSλp(spG)=sG=Y\sum_{p \in S} \lambda_p \cdot (s_p \cdot G) = s \cdot G = Y

The Lagrange recombination works both in the scalar world (secret numbers) and in the point world (public keys). This "homomorphic" property — the ability to do the computation "in the exponent" — is the mathematical backbone of FROST.

FROST Protocol

We've built all the pieces. Now let's put them together into the actual FROST protocol: a two-round scheme where tt of nn participants cooperatively produce a standard Schnorr signature.

FROST protocol flow: two rounds between participants and coordinator, producing a standard Schnorr signature

Setup: Distributed Key Generation (done once)

Before any signing can happen, the participants run the Pedersen DKG described above. Afterward, each participant PpP_p holds:

  • Their secret share sps_p (private — never revealed)
  • The group public key Y=sGY = s \cdot G (public — this is the address that will verify signatures)
  • Public verification shares Yp=spGY_p = s_p \cdot G for all participants (public — used to verify individual signature shares)

Roles

  • Participants P1,,PnP_1, \dots, P_n: hold secret shares, produce signature shares. At least tt must be online for any given signing session.
  • Coordinator (also called Signature Aggregator): orchestrates the protocol, collects commitments and shares, produces the final signature. The Coordinator is semi-trusted — they cannot learn the secret key or forge signatures (even a corrupt Coordinator can't sign without tt honest participants). However, a corrupt Coordinator can refuse to produce the signature (denial of service) or falsely blame a participant for sending a bad share.

Round 1: Commitment

Each participant PpP_p in the signing set SS (where St|S| \geq t):

  1. Generates two random nonces: dpd_p and epe_p (random scalars). These are generated as H(random_bytes(32)skp)H(\text{random\_bytes}(32) \| sk_p) — mixing randomness with the secret key as a hedge against a faulty random number generator.

  2. Computes commitments: Dp=dpGD_p = d_p \cdot G and Ep=epGE_p = e_p \cdot G.

  3. Sends (Dp,Ep)(D_p, E_p) to the Coordinator.

After Round 1, the Coordinator has everyone's commitments, but nobody knows anyone else's nonce values (dpd_p, epe_p), only the corresponding curve points.

Critical safety rule: each (dp,ep)(d_p, e_p) pair must be used in exactly one signing session. If a signing session is aborted, the nonces must be discarded and fresh ones generated for any retry. Reusing nonces across sessions can leak the secret key.

Round 2: Signature Share

The Coordinator broadcasts the message mm to sign and the full list of all commitments (Dp,Ep)(D_p, E_p) for every pSp \in S.

Each participant PpP_p then independently computes:

Step 1. Binding factors. Compute the binding factor for each participant:

ρp=H1(YH4(m)H5(all commitments)p)\rho_p = H_1\big(Y \| H_4(m) \| H_5(\text{all commitments}) \| p\big)

The subscripts H1,H4,H5H_1, H_4, H_5 denote domain-separated hash functions — they use different prefixes so that a hash computed for one purpose can't be confused with a hash for a different purpose. This is a standard defensive practice in cryptographic protocols.

Every participant computes the same ρp\rho_p values because they all have the same inputs (all commitments were broadcast).

Step 2. Group commitment. Compute the aggregate nonce point:

R=pS(Dp+ρpEp)R = \sum_{p \in S} \left(D_p + \rho_p \cdot E_p\right)

Again, every participant gets the same RR.

Step 3. Challenge. Compute the Schnorr challenge:

c=H2(RYm)c = H_2(R \| Y \| m)

Step 4. Lagrange coefficient. Compute λp\lambda_p for participant PpP_p given the signing set SS.

Step 5. Signature share. Compute and send:

zp=dp+ρpep+λpspcz_p = d_p + \rho_p \cdot e_p + \lambda_p \cdot s_p \cdot c

This is the moment where the participant uses their secret share sps_p. After computing zpz_p, they delete dpd_p and epe_p immediately.

Step 6. Send zpz_p to the Coordinator.

Aggregation

The Coordinator now has all tt signature shares zpz_p. They:

  1. (Optional but recommended) Verify each share:
zpG=?Dp+ρpEp+(λpc)Ypz_p \cdot G \stackrel{?}{=} D_p + \rho_p \cdot E_p + (\lambda_p \cdot c) \cdot Y_p

If any share fails, the Coordinator identifies the misbehaving participant and aborts.

  1. Aggregate:
z=pSzpz = \sum_{p \in S} z_p
  1. Output the signature: σ=(R,z)\sigma = (R, z)

This signature passes standard Schnorr verification: zG=?R+cYz \cdot G \stackrel{?}{=} R + c \cdot Y.

Why it's correct

Let's trace through the math one more time:

z=pSzp=pS(dp+ρpep)+cpSλpspz = \sum_{p \in S} z_p = \sum_{p \in S} (d_p + \rho_p \cdot e_p) + c \cdot \sum_{p \in S} \lambda_p \cdot s_p

The nonce terms sum to kk (the discrete log of RR):

pS(dp+ρpep)G=pS(Dp+ρpEp)=Rsum=k\sum_{p \in S} (d_p + \rho_p \cdot e_p) \cdot G = \sum_{p \in S} (D_p + \rho_p \cdot E_p) = R \quad \Rightarrow \quad \text{sum} = k

The secret share terms recover ss by Lagrange interpolation:

pSλpsp=s\sum_{p \in S} \lambda_p \cdot s_p = s

Therefore z=k+csz = k + c \cdot s, and zG=kG+csG=R+cYz \cdot G = k \cdot G + c \cdot s \cdot G = R + c \cdot Y. Valid Schnorr.

Complete worked example

Let's trace the entire FROST protocol end-to-end with concrete numbers. We'll work modulo q=97q = 97 to keep the arithmetic manageable.

DKG (already done). Virtual polynomial F(x)=16+6x(mod97)F(x) = 16 + 6x \pmod{97}.

  • Secret shares: s1=22s_1 = 22, s2=28s_2 = 28, s3=34s_3 = 34
  • Group secret: s=16s = 16 (nobody knows this)
  • Group public key: Y=16GY = 16G

Signing set: S={1,2}S = \{1, 2\} (participants P1P_1 and P2P_2 will sign)

Lagrange coefficients for S={1,2}S = \{1, 2\}:

λ1=221=2,λ2=112=196(mod97)\lambda_1 = \frac{2}{2 - 1} = 2, \qquad \lambda_2 = \frac{1}{1 - 2} = -1 \equiv 96 \pmod{97}

(In modular arithmetic, 196(mod97)-1 \equiv 96 \pmod{97} because 96+1=97096 + 1 = 97 \equiv 0.)

Round 1 — Each participant picks nonces and publishes commitments:

  • P1P_1: d1=9d_1 = 9, e1=14e_1 = 14. Publishes D1=9GD_1 = 9G, E1=14GE_1 = 14G.
  • P2P_2: d2=5d_2 = 5, e2=11e_2 = 11. Publishes D2=5GD_2 = 5G, E2=11GE_2 = 11G.

Round 2 — Compute everything and sign:

Binding factors (from hash — we'll suppose): ρ1=3\rho_1 = 3, ρ2=7\rho_2 = 7.

Group commitment:

R=(D1+3E1)+(D2+7E2)=(9+42)G+(5+77)G=51G+82G=133G36G(mod97)R = (D_1 + 3 \cdot E_1) + (D_2 + 7 \cdot E_2) = (9 + 42)G + (5 + 77)G = 51G + 82G = 133G \equiv 36G \pmod{97}

So R=36GR = 36G, meaning the aggregate nonce is k=36k = 36.

Challenge (from hash — we'll suppose): c=10c = 10.

Signature shares:

P1P_1:

z1=9+314+22210=9+42+440=491491mod97z_1 = 9 + 3 \cdot 14 + 2 \cdot 22 \cdot 10 = 9 + 42 + 440 = 491 \equiv 491 \mod 97

491=597+6491 = 5 \cdot 97 + 6, so z1=6z_1 = 6.

P2P_2:

z2=5+711+962810=5+77+26880=2696226962mod97z_2 = 5 + 7 \cdot 11 + 96 \cdot 28 \cdot 10 = 5 + 77 + 26880 = 26962 \equiv 26962 \mod 97

26962=27797+9326962 = 277 \cdot 97 + 93, so z2=93z_2 = 93.

Aggregation:

z=z1+z2=6+93=992(mod97)z = z_1 + z_2 = 6 + 93 = 99 \equiv 2 \pmod{97}

Verification — does this match a standard Schnorr signature?

k+sc=36+1610=1962(mod97)k + s \cdot c = 36 + 16 \cdot 10 = 196 \equiv 2 \pmod{97} \quad \checkmark zG=2G=?R+cY=36G+1016G=36G+160G=196G2G(mod97)z \cdot G = 2G \stackrel{?}{=} R + c \cdot Y = 36G + 10 \cdot 16G = 36G + 160G = 196G \equiv 2G \pmod{97} \quad \checkmark

The final signature is (R,z)=(36G,2)(R, z) = (36G, 2) — a perfectly valid Schnorr signature under the group public key Y=16GY = 16G. A verifier sees a normal-looking single-signer Schnorr signature. They have no way to know it was produced by two participants collaborating through FROST.

Summary of FROST properties

FROST produces a standard Schnorr signature — the output is indistinguishable from one created by a single signer. The protocol completes in 2 rounds (or just 1 if nonces are preprocessed). Any tt of nn participants can sign; the rest can be offline. Multiple signing sessions can run concurrently without compromising security.

If a participant misbehaves, the protocol provides identifiable abort — the cheater is detected, though a new session must be started without them. FROST is EUF-CMA secure under the One-More Discrete Log assumption: an adversary controlling up to t1t-1 participants cannot forge signatures.

The protocol is standardized in RFC 9591 (June 2024), with ciphersuites for Ed25519, Ed448, P-256, and secp256k1.

References

  • FROST paper: Komlo, Goldberg. "FROST: Flexible Round-Optimized Schnorr Threshold Signatures." SAC 2020. ePrint 2020/852
  • RFC 9591: "Two-Round Threshold Schnorr Signatures with FROST." June 2024. rfc-editor.org
  • BIP-340: Wuille, Nick, Towns. "Schnorr Signatures for secp256k1." github.com/bitcoin/bips
  • MuSig2: Nick, Ruffing, Seurin. "MuSig2: Simple Two-Round Schnorr Multi-Signatures." CRYPTO 2021. ePrint 2020/1261
  • Shamir's Secret Sharing: Shamir. "How to Share a Secret." Communications of the ACM, 1979. doi:10.1145/359168.359176
  • Fiat-Shamir transform: Fiat, Shamir. "How to Prove Yourself: Practical Solutions to Identification and Signature Problems." CRYPTO 1986. Springer
  • Schnorr signature security proof: Pointcheval, Stern. "Security Arguments for Digital Signatures and Blind Signatures." Journal of Cryptology, 2000 (originally EUROCRYPT 1996). Springer
  • Multi-user signature security: Menezes, Smart. "Security of Signature Schemes in a Multi-User Setting." Designs, Codes and Cryptography, 2004. Springer
  • Ed25519: Bernstein, Duif, Lange, Schwabe, Yang. "High-speed high-security signatures." 2012. ed25519.cr.yp.to
  • Feldman VSS: Feldman. "A Practical Scheme for Non-interactive Verifiable Secret Sharing." FOCS 1987. IEEE
  • Pedersen DKG: Pedersen. "Non-Interactive and Information-Theoretic Secure Verifiable Secret Sharing." CRYPTO 1991. Springer
  • Zcash FROST implementation (Rust): github.com/ZcashFoundation/frost
  • FROST Interactive Explainer: romarq.github.io/FROST-Interactive-Explainer