@@ -14,6 +14,9 @@ def optimized_version_of(spec): | |||||
"""Decorator: This function is an optimized version of some specification""" | """Decorator: This function is an optimized version of some specification""" | ||||
def decorator(f): | def decorator(f): | ||||
def wrapper(self,*args,**kwargs): | def wrapper(self,*args,**kwargs): | ||||
def pr(x): | |||||
if isinstance(x,bytearray): return binascii.hexlify(x) | |||||
else: return str(x) | |||||
try: spec_ans = getattr(self,spec,spec)(*args,**kwargs),None | try: spec_ans = getattr(self,spec,spec)(*args,**kwargs),None | ||||
except Exception as e: spec_ans = None,e | except Exception as e: spec_ans = None,e | ||||
try: opt_ans = f(self,*args,**kwargs),None | try: opt_ans = f(self,*args,**kwargs),None | ||||
@@ -28,7 +31,7 @@ def optimized_version_of(spec): | |||||
# % (f.__name__,str(spec_ans[1]),str(opt_ans[0]))) | # % (f.__name__,str(spec_ans[1]),str(opt_ans[0]))) | ||||
if spec_ans[0] != opt_ans[0]: | if spec_ans[0] != opt_ans[0]: | ||||
raise SpecException("Mismatch in %s: %s != %s" | raise SpecException("Mismatch in %s: %s != %s" | ||||
% (f.__name__,str(spec_ans[0]),str(opt_ans[0]))) | |||||
% (f.__name__,pr(spec_ans[0]),pr(opt_ans[0]))) | |||||
if opt_ans[1] is not None: raise | if opt_ans[1] is not None: raise | ||||
else: return opt_ans[0] | else: return opt_ans[0] | ||||
wrapper.__name__ = f.__name__ | wrapper.__name__ = f.__name__ | ||||
@@ -144,7 +147,7 @@ class RistrettoPoint(QuotientEdwardsPoint): | |||||
if y == -1: y = 1 # Avoid divide by 0; doesn't affect impl | if y == -1: y = 1 # Avoid divide by 0; doesn't affect impl | ||||
if negative(x): x,y = -x,-y | if negative(x): x,y = -x,-y | ||||
s = xsqrt(self.a*(y-1)/(y+1),exn=Exception("Unimplemented: point is odd: " + str(self))) | |||||
s = xsqrt(self.mneg*(1-y)/(1+y),exn=Exception("Unimplemented: point is odd: " + str(self))) | |||||
return self.gfToBytes(s) | return self.gfToBytes(s) | ||||
@classmethod | @classmethod | ||||
@@ -164,28 +167,32 @@ class RistrettoPoint(QuotientEdwardsPoint): | |||||
@optimized_version_of("encodeSpec") | @optimized_version_of("encodeSpec") | ||||
def encode(self): | def encode(self): | ||||
"""Encode, optimized version""" | """Encode, optimized version""" | ||||
a,d = self.a,self.d | |||||
a,d,mneg = self.a,self.d,self.mneg | |||||
x,y,z,t = self.xyzt() | x,y,z,t = self.xyzt() | ||||
if self.cofactor==8: | if self.cofactor==8: | ||||
u1 = a*(y+z)*(y-z) | |||||
u1 = mneg*(z+y)*(z-y) | |||||
u2 = x*y # = t*z | u2 = x*y # = t*z | ||||
isr = isqrt(u1*u2^2) | isr = isqrt(u1*u2^2) | ||||
i1 = isr*u1 | |||||
i2 = isr*u2 | |||||
z_inv = i1*i2*t | |||||
i1 = isr*u1 # sqrt(mneg*(z+y)*(z-y))/(x*y) | |||||
i2 = isr*u2 # 1/sqrt(a*(y+z)*(y-z)) | |||||
z_inv = i1*i2*t # 1/z | |||||
if negative(t*z_inv): | if negative(t*z_inv): | ||||
if a==-1: x,y = y*self.i,x*self.i | |||||
else: x,y = -y,x # TODO: test | |||||
den_inv = self.magic * i1 | |||||
if a==-1: | |||||
x,y = y*self.i,x*self.i | |||||
den_inv = self.magic * i1 | |||||
else: | |||||
x,y = -y,x | |||||
den_inv = self.i * self.magic * i1 | |||||
else: | else: | ||||
den_inv = i2 | den_inv = i2 | ||||
if negative(x*z_inv): y = -y | if negative(x*z_inv): y = -y | ||||
s = (z-y) * den_inv | s = (z-y) * den_inv | ||||
else: | else: | ||||
num = a*(y+z)*(y-z) | |||||
num = mneg*(z+y)*(z-y) | |||||
isr = isqrt(num*y^2) | isr = isqrt(num*y^2) | ||||
if negative(isr^2*num*y*t): y = -y | if negative(isr^2*num*y*t): y = -y | ||||
s = isr*y*(z-y) | s = isr*y*(z-y) | ||||
@@ -406,6 +413,23 @@ class Ed25519Point(RistrettoPoint): | |||||
d = F(-121665/121666) | d = F(-121665/121666) | ||||
a = F(-1) | a = F(-1) | ||||
i = sqrt(F(-1)) | i = sqrt(F(-1)) | ||||
mneg = F(1) | |||||
qnr = i | |||||
magic = isqrt(a*d-1) | |||||
cofactor = 8 | |||||
encLen = 32 | |||||
@classmethod | |||||
def base(cls): | |||||
return cls( 15112221349535400772501151409588531511454012693041857206046113283949847762202, 46316835694926478169428394003475163141307993866256225615783033603165251855960 | |||||
) | |||||
class NegEd25519Point(RistrettoPoint): | |||||
F = GF(2^255-19) | |||||
d = F(121665/121666) | |||||
a = F(1) | |||||
i = sqrt(F(-1)) | |||||
mneg = F(-1) # TODO checkme vs 1-ad or whatever | |||||
qnr = i | qnr = i | ||||
magic = isqrt(a*d-1) | magic = isqrt(a*d-1) | ||||
cofactor = 8 | cofactor = 8 | ||||
@@ -414,14 +438,15 @@ class Ed25519Point(RistrettoPoint): | |||||
@classmethod | @classmethod | ||||
def base(cls): | def base(cls): | ||||
y = cls.F(4/5) | y = cls.F(4/5) | ||||
x = sqrt((y^2-1)/(cls.d*y^2+1)) | |||||
if lobit(x): x = -x | |||||
x = sqrt((y^2-1)/(cls.d*y^2-cls.a)) | |||||
if negative(x): x = -x | |||||
return cls(x,y) | return cls(x,y) | ||||
class IsoEd448Point(RistrettoPoint): | class IsoEd448Point(RistrettoPoint): | ||||
F = GF(2^448-2^224-1) | F = GF(2^448-2^224-1) | ||||
d = F(39082/39081) | d = F(39082/39081) | ||||
a = F(1) | a = F(1) | ||||
mneg = F(-1) | |||||
qnr = -1 | qnr = -1 | ||||
magic = isqrt(a*d-1) | magic = isqrt(a*d-1) | ||||
cofactor = 4 | cofactor = 4 | ||||
@@ -429,10 +454,10 @@ class IsoEd448Point(RistrettoPoint): | |||||
@classmethod | @classmethod | ||||
def base(cls): | def base(cls): | ||||
# = ..., -3/2 | |||||
return cls.decodeSpec(bytearray(binascii.unhexlify( | |||||
"00000000000000000000000000000000000000000000000000000000"+ | |||||
"fdffffffffffffffffffffffffffffffffffffffffffffffffffffff"))) | |||||
return cls( # RFC has it wrong | |||||
-345397493039729516374008604150537410266655260075183290216406970281645695073672344430481787759340633221708391583424041788924124567700732, | |||||
-363419362147803445274661903944002267176820680343659030140745099590306164083365386343198191849338272965044442230921818680526749009182718 | |||||
) | |||||
class TwistedEd448GoldilocksPoint(Decaf_1_1_Point): | class TwistedEd448GoldilocksPoint(Decaf_1_1_Point): | ||||
F = GF(2^448-2^224-1) | F = GF(2^448-2^224-1) | ||||
@@ -446,9 +471,7 @@ class TwistedEd448GoldilocksPoint(Decaf_1_1_Point): | |||||
@classmethod | @classmethod | ||||
def base(cls): | def base(cls): | ||||
return cls.decodeSpec(bytearray(binascii.unhexlify( | |||||
"00000000000000000000000000000000000000000000000000000000"+ | |||||
"fdffffffffffffffffffffffffffffffffffffffffffffffffffffff"))) | |||||
return cls.decodeSpec(Ed448GoldilocksPoint.base().encodeSpec()) | |||||
class Ed448GoldilocksPoint(Decaf_1_1_Point): | class Ed448GoldilocksPoint(Decaf_1_1_Point): | ||||
F = GF(2^448-2^224-1) | F = GF(2^448-2^224-1) | ||||
@@ -462,9 +485,9 @@ class Ed448GoldilocksPoint(Decaf_1_1_Point): | |||||
@classmethod | @classmethod | ||||
def base(cls): | def base(cls): | ||||
return cls.decodeSpec(bytearray(binascii.unhexlify( | |||||
"00000000000000000000000000000000000000000000000000000000"+ | |||||
"fdffffffffffffffffffffffffffffffffffffffffffffffffffffff"))) | |||||
return -2*cls( # FIXME: make not negative | |||||
224580040295924300187604334099896036246789641632564134246125461686950415467406032909029192869357953282578032075146446173674602635247710, 298819210078481492676017930443930673437544040154080242095928241372331506189835876003536878655418784733982303233503462500531545062832660 | |||||
) | |||||
class IsoEd25519Point(Decaf_1_1_Point): | class IsoEd25519Point(Decaf_1_1_Point): | ||||
# TODO: twisted iso too! | # TODO: twisted iso too! | ||||
@@ -533,6 +556,7 @@ def test(cls,n): | |||||
Q = Q1 | Q = Q1 | ||||
test(Ed25519Point,100) | test(Ed25519Point,100) | ||||
test(NegEd25519Point,100) | |||||
test(IsoEd25519Point,100) | test(IsoEd25519Point,100) | ||||
test(IsoEd448Point,100) | test(IsoEd448Point,100) | ||||
test(TwistedEd448GoldilocksPoint,100) | test(TwistedEd448GoldilocksPoint,100) | ||||
@@ -544,6 +568,7 @@ def testElligator(cls,n): | |||||
for i in xrange(n): | for i in xrange(n): | ||||
cls.elligator(randombytes(cls.encLen)) | cls.elligator(randombytes(cls.encLen)) | ||||
testElligator(Ed25519Point,100) | testElligator(Ed25519Point,100) | ||||
testElligator(NegEd25519Point,100) | |||||
testElligator(IsoEd448Point,100) | testElligator(IsoEd448Point,100) | ||||
testElligator(Ed448GoldilocksPoint,100) | testElligator(Ed448GoldilocksPoint,100) | ||||
testElligator(TwistedEd448GoldilocksPoint,100) | testElligator(TwistedEd448GoldilocksPoint,100) |
@@ -0,0 +1,135 @@ | |||||
\documentclass[11pt]{article} | |||||
\usepackage{amsmath,amsthm,amsfonts,amssymb,xspace,graphicx,url,stmaryrd,parskip} | |||||
\newtheorem{lemma}{Lemma} | |||||
\newcommand\todo[1]{\textbf{[[TODO: #1]]}\xspace} | |||||
\def\F{\ensuremath{\mathbb{F}}} | |||||
\def\G{\ensuremath{\mathbb{G}}} | |||||
\def\Z{\ensuremath{\mathbb{Z}}} | |||||
\def\O{\ensuremath{O}} | |||||
\begin{document} | |||||
\title{The Ristretto and Cortado elliptic curve groups} | |||||
\author{Mike Hamburg\thanks{Rambus Security Division}} | |||||
\maketitle | |||||
\begin{abstract} | |||||
\end{abstract} | |||||
\section{Introduction} | |||||
\section{Definitions and notation} | |||||
Let the symbol $\bot$ denote failure. | |||||
\subsection{Field elements} | |||||
Let \F\ be a finite field of prime order $p$. For an element $x\in\F$, let $\text{res}(x)$ be the integer representative of $x\in[0,p-1]$. We call an element $x\in\F$ \textit{negative} if $\text{res}(x)$ is odd. Call an element in \F\ \textit{square} if it is a quadratic residue, i.e.\ if there exists $\sqrt{x}\in\F$ such that $\sqrt{x}^2=x$. There will in general be two such square roots; let the notation $\sqrt{x}$ mean the unique non-negative square root of $x$. If $p\equiv1\pmod 4$, then \F\ contains an element $i := \sqrt{-1}$. | |||||
Let $\ell := \lceil \log_{2^8} p\rceil$. Each $x\in\F$ has a unique \textit{little-endian byte representation}, namely the sequence | |||||
$$ | |||||
\text{\F\_to\_bytes}(x) := \llbracket b_i\rrbracket_{i=0}^{\l-1} \ \text{where}\ b_i\in[0,255]\text{\ and\ }\sum_{i=0}^{\l-1} 2^{8i} \cdot b_i = \text{res}(x) | |||||
$$ | |||||
\todo{bytes to \F} | |||||
\subsection{Groups} | |||||
For an abelian group \G\ with identity \O, let $n\G$ denote the subgroup of $\G$ which are of the form $n\cdot g$ for some $g\in\G$. Let $\G_n$ denote the $n$-torsion group of \G, namely the subgroup $\{g\in\G : n\cdot g = O\}$. | |||||
\subsection{Edwards curves} | |||||
We will work with twisted Edwards elliptic curves of the form | |||||
% | |||||
$$E_{a,d} : y^2 + a\cdot x^2 = 1 + d\cdot x^2\cdot y^2$$ | |||||
% | |||||
where $x,y\in\F$. Twisted Edwards curves curves have a group law | |||||
$$(x_1,y_1) + (x_2,y_2) := | |||||
\left( | |||||
\frac{x_1 y_2 + x_2 y_1}{1+d x_1 x_2 y_1 y_2}, | |||||
\frac{y_1 y_2 - a x_1 x_2}{1-d x_1 x_2 y_1 y_2} | |||||
\right) | |||||
$$ | |||||
with identity point $\O := (0,1)$ and group inverse operation $$-(x,y) = (-x,y)$$ | |||||
The group law is called \textit{complete} if is produces the correct answer (rather than e.g.\ $0/0$) for all points on the curve. The above formulas are complete when $d$ and $ad$ are nonsquare in \F, which implies that $a$ is square. When these conditions hold, we also say that the curve itself is complete. | |||||
Let the number of points on the curve be $$\#E_{a,d} = h\cdot q$$ where $q$ is prime and $h\in\{4,8\}$. We call $h$ the \textit{cofactor}. | |||||
For $P = (x,y)\in E$, we can define the \textit{projective homogeneous form} of $P$ as $(X,Y,Z)$ with $Z\neq 0$ and $$(x,y) = (X/Z,Y/Z)$$ and the \textit{extended homogeneous form} as $(X,Y,Z,T)$ where additionally $XY=ZT$. Extended homogeneous form is popular because it supports simple and efficient complete addition formulas~\cite{hisil}. | |||||
\subsection{Montgomery curves} | |||||
When $a-d$ is square in \F, the twisted Edwards curve $E_{a,d}$ is isomorphic to the Montgomery curve | |||||
$$v^2 = u\cdot\left(u^2 + 2\cdot\frac{a+d}{a-d}\cdot u + 1\right)$$ | |||||
by the map | |||||
$$(u,v) = \left(\frac{1+y}{1-y},\ \ \frac{1+y}{1-y}\cdot\frac1x\cdot\frac{2}{\sqrt{a-d}}\right)$$ | |||||
with inverse | |||||
$$(x,y) = \left(\frac{u}{v}\cdot\frac{\sqrt{a-d}}{2},\ \ \frac{u-1}{u+1}\right)$$ | |||||
If $M = (u,v)$ is a point on the Montgomery curve, then the $u$-coordinate of $2M$ is $(u^2-1)^2 / (4v^2)$ is necessarily square. It follows that if $(x,y)$ is a point on $E_{a,d}$, and $a-d$ is square, then $(1+y)/(1-y)$ is also square. | |||||
\todo{Nega montgomery} | |||||
\section{Lemmas} | |||||
First, we characterize the 2-torsion and 4-torsion groups.\\ | |||||
\begin{lemma}\label{lemma:tors} | |||||
Let $E_{a,d}$ be a complete Edwards curve. Its 2-torsion subgroup is generated by $(0,-1)$. The 4-torsion subgroup is generated by $(1/\sqrt{a},0)$. | |||||
Adding the 2-torsion generator to $(x,y)$ produces $(-x,-y)$. Adding the 4-torsion generator $(1/\sqrt{a},0)$ produces $(y/\sqrt{a},-x\cdot\sqrt{a})$ | |||||
\end{lemma} | |||||
\begin{proof} | |||||
Inspection. | |||||
\end{proof} | |||||
\begin{lemma}\label{lemma:line} | |||||
Let $E_{a,d}$ be a complete twisted Edwards curve over \F, and $P_1 = (x_1,y_1)$ be any point on it. Then there are exactly two points $P_2 = (x_2,y_2)$ satisfying $x_1 y_2 = x_2 y_1$, namely $P_1$ itself and $(-x_1,-y_1)$. That is, there are either 0 or 2 points on any line through the origin. | |||||
\end{lemma} | |||||
\begin{proof} | |||||
Plugging into the group operation gives | |||||
$$x_1 y_2 = x_2 y_1 \Longleftrightarrow P_1-P_2 = (0,y_3)$$ | |||||
for some $y_3$. Plugging $x=0$ into the curve equation gives $y=\pm1$, the 2-torsion points. Adding back, we have $P_2 = P_1 + (0,\pm1) = (\pm x_1, \pm y_1)$ as claimed. | |||||
\end{proof} | |||||
\begin{lemma}\label{lemma:dma} | |||||
If $E_{a,d}$ is a complete Edwards curve, then $a^2-ad$ is square in \F\ (and thus $a-d$ is square in \F) if and only if the cofactor of $E_{a,d}$ is divisible by 8. | |||||
\end{lemma} | |||||
\begin{proof} | |||||
Doubling an 8-torsion generator $(x,y)$ should produce a 4-torsion generator, i.e.\ a point with $y=0$. From the doubling formula, this happens precisely when $y^2=ax^2$, or $2ax^2=1+adx^4$. This has roots in \F\ if and only if its discriminant $4a^2-4ad$ is square, so that $a^2-ad$ is square. | |||||
\end{proof} | |||||
\begin{lemma}\label{lemma:sqrt} | |||||
If $(x_2,y_2) = 2\cdot(x_1,y_1)$ is an even point in $E_{a,d}$, then $(1-ax_2^2)$ is a quadratic residue in \F. \todo{$(y_2^2-1)$}. | |||||
\end{lemma} | |||||
\begin{proof} | |||||
The doubling formula has $$x_2 = \frac{2x_1 y_1}{y_1^2+ax_1^2}$$ | |||||
so that $$1-ax_2^2 = \left(\frac{y_1^2-ax_1^2}{y_1^2+ax_1^2}\right)^2$$ | |||||
is a quadratic residue. Now for any point $(x,y)\in E_{a,d}$, we have | |||||
$$(y^2-1)\cdot(1-ax^2) | |||||
= y^2+ax^2-1-ax^2y^2 | |||||
= (d-a)x^2y^2 | |||||
$$ | |||||
which is a quadratic residue by Lemma~\ref{lemma:dma}. | |||||
\end{proof} | |||||
\section{The Espresso groups} | |||||
Let $E$ be a complete twisted Edwards curve with $a\in\{\pm1\}$ and cofactor $4$ or $8$. We describe the \textit{Espresso} group $\G(E)$ as | |||||
$$\text{Espresso}(E) := 2E / E_{h/2}$$ | |||||
This group has prime order $q$. | |||||
\subsection{Group law} | |||||
The group law on $\text{Espresso}(E)$ is the same as that on $E$. | |||||
\subsection{Equality} | |||||
Two elements $P_1 := (x_1,y_1)$ and $P_2 := (x_2,y_2)$ in $\text{Espresso}(E)$ are equal if they differ by an element of $E_{h/2}$. | |||||
If $h=4$, the points are equal if $P_1-P_2\in E_2$. By Lemma~\ref{lemma:line}, this is equivalent to $$x_1 y_2 = x_2 y_1$$ | |||||
If $h=8$, the points are equal if $P_1-P_2\in E_4$. By Lemmas~\ref{lemma:tors} and~\ref{lemma:line}, this is equivalent to $$x_1 y_2 = x_2 y_1\text{\ \ or\ \ }x_1 x_2 = -a y_1 y_2$$ | |||||
These equations are homogeneous, so they may be evaluated in projective homogeneous form with $X_i$ and $Y_i$ in place of $x_i$ and $y_i$ | |||||
\subsection{Encoding} | |||||
We now describe how to encode a point $P = (x,y)$ to bytes. The requirements of encoding are that | |||||
\begin{itemize} | |||||
\item Any point $P\in2E$ can be encoded. | |||||
\item Two points $P,Q$ have the same encoding if and only if $P-Q\in E_{h/2}$. | |||||
\end{itemize} | |||||
When $h=4$, we encode a point as $\sqrt{a(y-1)/(y+1)}$ | |||||
\end{document} |
@@ -0,0 +1,15 @@ | |||||
Have (1-ydbl)/(1+ydbl) | |||||
= (y^2-1)/(ax^2-1) | |||||
Have (y^2-1)*(ax^2-1) = (a-d) x^2 y^2 | |||||
==> (1-ydbl)/(1+ydbl) has same parity as a-d | |||||
No points at infinity => d nonsqr, ad nonsqr -> a sqr. | |||||
Point of order 8: ax^2=y^2 | |||||
2y^2 = 1+day^4 | |||||
product of roots = 1/ad = nonsquare, so one will be square (if no point at infty) | |||||
b^2-4ac = 4(1-ad) -> 1-ad square iff point of order 8 exists | |||||
If a^2 = 1, then 1-ad = a(a-d) |