[curves] SPAKE2 and SPAKE2 Elligator Edition

Mike Hamburg mike at shiftleft.org
Thu Feb 12 01:53:15 PST 2015


Hello curves,

It's been suggested that I explain Elligator and SPAKE2-EE in more 
detail.  Get ready for a wall of text.

TL;DR: SPAKE2-EE is probably slightly better overall than SPAKE2. It's 
slightly faster, has better security assumptions, and makes one or two 
cases that can arise in SPAKE2 slightly easier.  It's also more 
complicated to spec and implement, since you need Elligator. 
SPAKE2-vanilla needs more complicated optimizations to achieve its top 
speed, but at least you don't have to spec them.


Elligator 2:

Elligator 2 is a map from the base field F to any elliptic curve E (over 
a large characteristic field) with a point of order 2. There's also an 
Elligator 1 but it's more complicated and has more restrictions, so 
let's stick with Elligator 2.  If the curve doesn't have a point of 
order 2 (eg, the NIST curves), then the Shallue-Woestijne-Ulas (SWU) 
algorithm has very similar characteristics, and is only slightly more 
complicated.

Any curve with a point of order 2 is isomorphic to one of the form y^2 = 
f(x) = x (x^2 + Ax + B), i.e. Weierstrass form with the point of order 2 
at (0,0).  From there, the idea is to come up with two values x1 and x2 
where one of them has a y on the curve and the other does not.  To do 
this, it suffices that:

x1^2 + Ax1 + B = x2^2 + Ax2 + B, and
x1 / x2 is not square in F

The first condition is equivalent to x1 + x2 = -A.  The second condition 
is equivalent to x1 = ur^2 x2, where r is arbitrary and u is a fixed 
nonsquare value, eg -1 if p==3 mod 4.  (If r=0 then actually x1/x2 = 0 
will be square, but that's good enough because (0,0) is on the curve.)  
Solving these two equations gives x2 = -A/(1+ur^2), and x1 = ur^2 * 
that.  Notice that if you plug 1/ur into the equation for x1 you get x2 
and vice-versa.

So Elligator is conceptually pretty simple:

* Map your input value to a field element r.
* Compute whether f(-A/(1+ur^2)) is square.  If so, compute its square root.
* If not, compute the square root of f(-Aur^2/(1+ur^2)) = ur^2 
f(-A/(1+ur^2)).

There are two important optimizations here.  The first is that you can 
compute f(-A/(1+ur^2)) projectively, so you don't have to divide.  If it 
ends up as C/D, then you can compute s = sqrt(CD) and then have y = s/D, 
again projectively; or if you use the isqrt trick you can compute the 
actual value of y.  This latter technique is better, because you can 
easily enforce the spec that y is "positive" if you chose x1 and 
"negative" if you chose x2, for your favorite definition of those terms 
(even, Legendre symbol, whatever).

The other important optimization is to compute both square roots in one 
step.  For example, if p == 3 mod 4, then u=-1 and x^((p+1)/4) = 
sqrt(+-x).  So compute f(...)^((p+1)/4).  If it comes out as 
sqrt(f(...)), good.  If not, it's sqrt(-f(...)) but you want 
sqrt(-r^2f(...)).  So multiply it by r and you're good.  In 5-mod-8 
world, sqrt(-1) takes the place of -1 here.

But you didn't want to hash to a Weierstrass curve.  You wanted to hash 
to an Edwards curve.  No problem.  Just hash to the isomorphic 
Montgomery curve, and apply the isomorphism, all in projective 
coordinates.  Or, in cases like SPAKE2, you'll need to multiply by 4 so 
that you get something in the main subgroup.  You can hash to Edwards 
and multiply by 4, or you can hash to the isogenous Montgomery curve 
instead of the isomorphic one, and apply the isogeny.  Your pick.

All in all, this means you can compute Elligator 2 in projective 
coordinates with maybe one isqrt, a dozen muls, 35 lines of code, a 
couple cond-selects (or multiplies by values you know in advance to be 
+-1), and some careful testing.

Elligator 2 is implemented in C as one of the many available point ops in:

http://sourceforge.net/p/ed448goldilocks/code/ci/decaf/tree/src/ec_point.c

This is an unnecessarily nasty implementation, because it maps to affine 
(using the inverse square root trick) instead of to projective.  Not 
worth it.  The Decaf version is simpler.

In the case of Decaf, the main spec is in terms of a Jacobi quartic, 
rather than a Weierstrass or Edwards curve, but all the actual math 
takes place in twisted Edwards.  The Jacobi quartic is isomorphic to L_d 
: y^2 = x(x-1)(x-d).  So you can do Elligator to that curve -- remember 
that Elligator 2 doesn't require a Montgomery curve -- and then use the 
2-isogeny from there to the twisted Edwards curve where it does all its 
math.  Same deal as with the Montgomery/Edwards combination, slightly 
different formulas.

This is implemented in C as decaf_point_from_hash_nonuniform in

http://sourceforge.net/p/ed448goldilocks/code/ci/decaf/tree/src/decaf.c

There's also a function decaf_point_from_hash_uniform which calls 
Elligator twice and adds the results.  This is indifferentiable from a 
random oracle, but twice as slow.  It's not necessary in most protocols, 
because they really only need an argument that looks like "for all you 
know, the hashed point on the curve could secretly have an embedded CDH 
challenge" and that doesn't require uniformity, only almost-uniformity 
and almost-invertibility.

For an RFC-style spec, you'd probably want to turn one of these C 
functions into a few lines of Python and specify that exact computation.



OK, now on to PAKE.

Elligator is 1:1 on [0,(p-1)/2], and it's easy to invert the map for 
those points in E where it has an inverse.  This matters for EKE, 
though, and we're talking about SPAKE2.  Remember that SPAKE2 works like so:

Let g, M and N be random generators of G, with no known relationship in 
the group.  Let "password" be a suitably stretched salted hash of the 
actual password.  Either the client has to retrieve the salt, or it's 
equal to her username and the server's name.

Alice -> Bob: A := g^x M^password, ID_Alice
Bob -> Alice: B := g^y N^password, ID_Bob

This can be optimized by a double-exp, possibly with fixed bases if you 
precomputed a table for M and one for N.  Also, the server can just 
store M,N^password in the database.

Both sides can now compute g^xy = (A/M^password)^y = (B/N^password)^x.  
They can implement the division as written, or they can use a 
double-exp, i.e. A^y M^(-password*y).

Then both sides compute validators and session key vA,vB,sk = 
H(A,B,ID_Alice,ID_Bob,g^xy,password).  The session messages are included 
to prevent tampering.  The password is included as an artifact of the 
security proof, and is probably technically unnecessary.  For augmented 
versions of SPAKE2, a hash of password is also a good enough artifact.  
Anyway, the two sides exchange validators and then start using the 
session key.

The two DH flows and the two validation flows can each happen 
simultaneously, or more likely Bob's two flows will be merged but 
Alice's will remain separate.


SPAKE2-EE

SPAKE2-EE ("Elligator Edition") is a simple variant of this protocol, in 
which M^password is replaced by h*Elligator(H("M",password)) where h is 
the cofactor, and likewise for N.  It's very important to clear the 
cofactor here, or to use a g which generates the entire group and not 
just the main subgroup; otherwise the cofactor components of the 
password hash will leak. Decaf's Elligator variant clears the cofactor 
for you; Goldilocks' does not.

Putting Elligator in clearly doesn't hurt the security of the protocol 
in the ROM, because for all the attacker knows 
h*Elligator(H("M",password)) is actually equal to, or at least morally 
equivalent to, M^password.  The rigorous proof proceeds almost exactly 
the same as SPAKE2 proof, but with an extra invocation of the random 
oracle model.

As far as I see it, SPAKE2-EE's advantage is that it's slightly tidier 
than SPAKE2 proper, because it removes the static-DH assumption.  In 
SPAKE2, you have to convince people that you generated M and N with no 
relationship to g, because an adversary knowing the discrete log of M 
base g can mount a dictionary attack against everyone.  (I believe that 
the same is only true for N if the flow ordering allows Alice to 
authenticate first.)  It shouldn't be hard to generate such an M,N, but 
it's even better if you don't have to.

SPAKE2-EE has a small performance advantage over SPAKE2 proper, at least 
when Elligator is implemented as described above.  That implementation 
takes under 1M/bit since the expensive part is the square root, but even 
if you precompute tables for M,N, it will cost at least 1.8M/bit to 
compute M^password and again 1.8M/bit for N.

The cost is that you have to spec and implement Elligator, which is 
annoying but not terrible.  At the same time, there won't be any 
temptation to implement double-fixed-base-comb-exp routines to improve 
performance.



More-symmetric and less-symmetric PAKE:

It's not very important that M != N.  I'm pretty sure the proof Trevor 
cited of this is wrong, but I'm also pretty sure that a proof exists 
under slightly worse assumptions than the original.

Alternatively, N^password can be dropped, so long as Bob sends his 
validator first.  (If Alice sends hers first without N^password, then a 
fake Bob clearly has a dictionary attack because his first 
password-related computation is to check the validator.)  This doesn't 
harm the proof IIRC, it just adds the ordering restriction.

These are both provable under some CDH variant (eg, GapDH if you like 
shorter proofs with unrealistic assumptions, otherwise straight CDH with 
tightness loss) in the random oracle model.

SPAKE2-EE has the same properties as SPAKE2 with respect to these two 
options.  It also has another option, which is that Alice and Bob use 
either temporary identities (IP address, etc) or even just random 
nonces, and replace M with h*Elligator(H(nonce_M,password)) and likewise 
for N.



Augmentation:

SPAKE2 can be augmented, where the server's credentials (i.e. Bob's 
credentials) are not sufficient to log in.  This is accomplished by 
storing g^password2, where (password,password2) is the output of the key 
strengthening function.  Alice can compute g^(y*password2) from Bob's 
flow because she knows password2 and g^y.  Bob can compute it because he 
knows y and g^password2.  So they can throw g^(y*password2) into the 
hash function when they compute the session key.  Dan and Victor's book 
calls this PAKE2+ and contains a security proof, but then again that 
book isn't published.

I suspect that many PAKEs can be augmented in a similar way, but they 
usually aren't specified with augmentation.

SPAKE2 can even be doubly augmented, where Alice does the same thing, 
but I'm not sure if there's any application to that.

Anyway, because SPAKE2EE only affects M^password and N^password, it 
doesn't interfere with this augmentation scheme.

Questions?  Comments?  Corrections?

Cheers,
-- Mike


More information about the Curves mailing list