 
          A privacy-enhancing protocol and browser extension.
The protocol that is implemented in Privacy Pass is built upon elliptic-curve cryptography, specifically using the NIST P-256 curve. We can think of our protocol as a variant of a ‘blind signature scheme’.
The concept of a blind signature has been around since David Chaum introduced RSA blinding in 1985. Our system is conceptually similar to Chaum’s original idea: it lets someone with a private key digitally sign a message without knowing what it is, but rather than an RSA private key, an Elliptic Curve private key is used. The construction we use was developed independently, but bears resemblance to recent EC-OPRF and EC-VRF proposals. The Privacy Pass team developed this scheme with the help of experts in cryptography such as Dan Boneh.
To make the design decisions behind the development of the Privacy Pass protocol clear, we detail a set of scenarios each with flaws. In each scenario we address a flaw of the previous construction and show how to avoid it. By Scenario 7 we have something very close to our scheme. In these scenarios there are two actors, the client and the server.
Additionally, see the full description for more details.
          The client takes a point on an elliptic curve
          T and sends it to the server.
          The server applies a secret transformation (multiplication by a secret
          number s) and sends it back.
          Call this step “Issue”, as the server issues a signed point to the
          client.
        
T -> 
	<-  sT
          Later, the client sends T and
          sT to the server to prove it
          has previously issued sT.
        
T, sT -> 
          Since only the server knows s,
          it can confirm that it had issued
          sT. We call this step “Redeem”.
        
          In this situation, the server knows
          T because it has seen it
          already. This lets the server connect the two requests, something
          we’re trying to avoid. This is where we introduce the blinding factor.
        
          Rather than sending T, the
          client generates its own secret number
          b. The client multiplies the
          point T by
          b before sending it to the
          server. The server does the same thing as in scenario 1 (multiplies
          the point it receives by s).
        
bT ->
  	<- s(bT)
          The client knows b and
          s(bT) is equal to
          b(sT) because multiplication is
          commutative. The client can compute
          sT from
          b(sT) by dividing by
          b. To redeem, the client sends
          T,
          sT.
        
T, sT ->
          Since only the server knows s,
          it can confirm that sT is
          T multiplied by
          s and will verify the
          redemption.
        
          It’s possible to create an arbitrary number of pairs of points that
          will be verified. The client can create these points by multiplying
          both T and
          sT by an arbitrary number
          a. If the client attempts to
          redeem aT and
          a(sT), the server will accept
          it. This effectively gives the client unlimited redemptions.
        
          Instead of picking an arbitrary point
          T, the client can pick a number
          t. The point
          T can be derived by hashing
          t to a point on the curve using
          a one-way hash. The hash guarantees that it’s hard to find another
          number that hashes to aT for an
          arbitrary a.
        
T = Hash(t) 
bT ->
	  <- sbT
t, sT ->
          Since only the server knows s, it can compute
          T = Hash(t) and confirm that
          sT is
          T multiplied by
          s and will verify the
          redemption.
        
          If the values t and
          sT are sent across an unsecured
          network, an adversary could take them and use them for their own
          redemption.
        
          Sending sT is what lets
          attackers hijack a redemption. Since the server can calculate
          sT from
          t on it’s own, the client
          doesn’t actually need to send it. All the client needs to do is prove
          that it knows sT. A trick for
          doing this is to use t and
          sT to derive a HMAC key and use
          it to sign a message that relates to the redemption. Without seeing
          sT, the attacker will not be
          able to take this redemption and use it for a different message
          because it won’t be able to compute the HMAC key.
        
          Instead of sending t and
          sT the client can send
          t and
          HMAC(sT, M) for a message
          M. When the server receives
          this, it calculates
          T = Hash(t), then uses its
          secret value to compute sT.
          With t and
          sT it can generate the HMAC key
          and check the signature. If the signature matches, that means the
          client knew sT.
        
T = Hash(t) 
bT ->
	  <- sbT
t, M, HMAC(sT, M) ->
          Since only the server knows s, it can compute
          T = Hash(t) and compute
          sT as
          T multiplied by
          s and verify the HMAC to
          validate that the client knew
          sT.
        
          The server can use a different s for each client, say
          s_1 for client 1 and
          s_2 for client 2. Then the
          server can identify the client by comparing
          s_1*Hash(t) and
          s_2*Hash(t) against the
          sT submitted by the client and
          seeing which one matches.
        
This is where we introduce a zero-knowledge proof. We’ll go into more detail about how these work in a later blog post. The specific proof we’re using is called a discrete logarithm equivalence proof (DLEQ).
Those lucky enough to take the SAT before 2005 may remember the analogy section. You can think of a DLEQ proof in terms of an SAT analogy. It proves that two pairs of items are related to each other in a similar way.
For example: puppies are to dogs as kittens are to cats. A kitten is a young cat and a puppy is a young dog. You can represent this with the following notation: puppy:dog == kitten:cat
          A DLEQ proves that two elliptic curve points are related by the same
          multiplicative factor without revealing that factor. Say you have a
          number s and two points
          P and
          Q. Someone with knowledge of s
          can construct a proof
          DLEQ(P:sP == Q:sQ). A third
          party with access to P,
          sP,
          Q,
          sQ can use
          DLEQ(P:sP == Q:sQ) to verify
          that the same value s was used without knowing what s is.
        
          The server picks a generator point
          G and publishes
          sG somewhere where every client
          knows it.
        
T = Hash(t) 
bT ->
	  <- sbT, DLEQ(bT:sbT == G:sG)
          The client can then check to see that the server used the same
          s, since everyone knows
          sG.
        
t, M, HMAC(sT, M) ->
          Just like in Scenario 4, since only the server knows s, it can compute
          T = Hash(t) and compute
          sT as
          T multiplied by
          s and verify the HMAC to
          validate that the client knew
          sT.
        
This system seems to have all the properties we want, but it would be nice to be able to get multiple points.
          The client picks multiple values
          t1, t2, … , tn and multiple
          blinding factors
          b1, b2, … , bn. For simplicity,
          let’s make n=3, but it could be an arbitrary number.
        
T1 = Hash(t1) 
T2 = Hash(t2)
T3 = Hash(t3)
b1T1 ->
b2T2 ->
b3T3 ->
		<- sb1T1, DLEQ(b1T1:sb1T1 == G: sG)
		<- sb2T2, DLEQ(b2T2:sb2T2 == G: sG)
		<- sb3T3, DLEQ(b3T3:sb3T3 == G: sG)
Each DLEQ can be verified independently like in Scenario 4, the client is safe from tagging.
t1, M, HMAC(sT1, M) ->
This lets the client do multiple redemptions.
DLEQ proofs are not particularly compact. Luckily, they can be optimized with something called an efficient batch DLEQ proof. It’s essentially a single proof that covers all the returned values. This can be done by computing a proof over a random linear combination of the points:
          Because the same s is used for
          every T, you can use the
          commutative property of multiplication again to help you.
        
Note the following:
sb1T1+sb2T2+sb3T3 = s(b1T1+b2T2+b3T3)
          So the server can compute a single DLEQ that proves that the same s
          was used for each T:
          DLEQ(b1T1+b2T2+b3T3:s(b1T1+b2T2+b3T3) == G: sG)
          This is the same size as a single DLEQ proof.
        
          In fact, as mentioned above, we take a random linear combination of
          these points without compromising the malleability requirement. In
          particular, we seed a Pseudorandom Number Generator (PRNG) using the
          output z of a hash computation
          over the common information in the signing phase (e.g. blinded/signed
          points). We then parse the output of
          PRNG(z) to be
          c1,c2,c3. We can then compute:
        
DLEQ(c1b1T1+c2b2T2+c3b3T3:s(c1b1T1 + c2b2T2 + c3b3T3) == G: sG)
Without using the random linear combinations the proof is insecure.
This scenario is similar to the last one except that the server sends a batch DLEQ proof rather than one for each point.
T1 = Hash(t1) 
T2 = Hash(t2)
T3 = Hash(t3)
b1T1 ->
b2T2 ->
b3T3 ->
		c1,c2,c3 = H(G,sG,b1T1,b2T2,b3T3,s(b1T1),s(b2T2),s(b3T3))
		<- sb1T1
		<- sb2T2
		<- sb3T3
		<- DLEQ(c1b1T1+c2b2T2+c3b3T3:s(c1b1T1+c2b2T2+c3b3T3) == G: sG)
          This DLEQ proof can be validated by recomputing
          z = c1,c2,c3 and then
          c1b1T1+c2b2T2+c3b3T3 and
          sc1b1T1+sc2b2T2+sc3b3T3.
        
t1, M, HMAC(sT1, M) ->
This is basically our scheme.
We have published a detailed specification of our scheme if you are interested in learning more. We also address some more possible attack avenues with working mitigations that are in use currently with respect to the Cloudflare implementation.