On considère la courbe elliptique $\mathcal{C}$ d'équation de Weierstrass $y^2 = x^3+ax+b$ dans $\mathbb{Z}/p\mathbb{Z}$, $p$ premier.
On considère l'ensemble $\mathcal{E}$ des points de la courbe : $$ \mathcal{E} = \left\{ [x, y] \in \mathbb{Z}/p\mathbb{Z}\times\mathbb{Z}/p\mathbb{Z} ~~|~~ y^2 = x^3+ax+b\right\} \cup \{0\}$$ où $0$ est le point à l'infini de $\mathcal{C}$.
On définit une addition sur $\mathcal{E}$ qui fait de $\mathcal{E}$ un groupe abélien, d'élément neutre $0$.
D'après le petit théorème de Fermat $n^{p-1}\equiv 1 \mod{p}$ si $p$ est premier.
Donc $n^{p-2}\equiv n^{-1} \mod{p}$. On suppose naturellement $n\neq 0$
La fonction Python de calcul de la puissance modulaire pow
est très rapide.
def inverse_mod_p(n, p):
"""Retourne l'inverse de n modulo p premier.
"""
return pow(n,p-2,p)
On encapsule les paramètres de la courbe elliptique dans un objet. On définit trois méthodes :
has_point
qui teste si un point est sur la courbe,opp
qui renvoie l'opposé d'un point (c'est le symétrique par rapport à l'axe des abscisses),addP
qui renvoie le point correspondant à la somme de deux points.class EllipticCurve(object):
"""Objet représentant la courbe elliptique d'équation y**2 = x**3 + a*x + b
sur le corps Z/pZ (p premier).
"""
def __init__(self, a, b, p):
delta = (4*a**3 + 27*b**2)%p # discriminant
if delta == 0:
raise Exception ("Discriminant nul : courbe elliptique singulière.")
self.a = a%p
self.b = b%p
self.p = p
def __str__(self):
return 'y^2 = x^3 + {}x + {} modulo {}'.format(self.a, self.b, self.p)
def has_point(self, p1):
if p1 == 0:
return True
return (p1[1] ** 2) % self.p == (p1[0] ** 3 + self.a * p1[0] + self.b) % self.p
def opp(self, p1):
if p1 == 0:
return 0
return [p1[0], -p1[1]%self.p]
def addP(self, p1, p2):
if p1 != 0 and self.has_point(p1) == False:
raise Exception ("Le point {} n'est pas sur la courbe.".format(p1))
if p2 != 0 and self.has_point(p2) == False:
raise Exception ("Le point {} n'est pas sur la courbe.".format(p2))
if p1 == 0:
return p2 # 0 + p2 = p2
if p2 == 0:
return p1 # p1 + 0 = p1
if (p1[0] - p2[0])%self.p == 0 and (p1[1] + p2[1])%self.p == 0 : # si p1 == -p2 mod p
return 0
if (p1[0] - p2[0])%self.p == 0 and (p1[1] - p2[1])%self.p == 0: # si p1 == p2 mod p
m = (3*p1[0]*p1[0] + self.a)*inverse_mod_p(2*p1[1], self.p) # pente tangente
else:
m = (p2[1] - p1[1])*inverse_mod_p(p2[0] - p1[0], self.p) # pente sécante
x = (m*m - p1[0] - p2[0])%self.p
return [x, (m*(p1[0] - x) - p1[1])%self.p ]
ec = EllipticCurve(100, 100, 1009)
print(ec)
p = [12, 1]
ec.has_point(p)
Donc le point p
est sur la courbe.
On demande de calculer 17p
(17 est la clé privée de Bob).
p2 = ec.addP(p, p) # p2 = 2p
p2
p4 = ec.addP(p2, p2) # p4 = 4p
p4
p8 = ec.addP(p4, p4) # p8 = 8p
p8
p16 =ec.addP(p8, p8) # p16 = 16p
p16
p17 = ec.addP(p16, p) # p17 = 17p
p17
17p
est la clé publique que Bob envoie à Alice.
Si le module est petit, le nombre de points de la courbe est petit aussi et on peut dresser la table d'addition des points de la courbe elliptique. On n'affiche pas les marges, car la marge gauche est identique à la première colonne et la marge supérieure est identique à la première ligne.
ec = EllipticCurve(3, 8, 13)
print(ec)
Ensemble des points de la courbe :
e = []
for a in range(13):
for b in range(13):
if ec.has_point([a, b]):
if [a, b] not in e:
e.append([a, b])
e = [0] + e
print(e)
Table d'addition (sans les marges) :
for x in e:
for y in e:
z = ec.addP(x, y)
if z == 0:
print('{:>8}'.format('0 '), end=' ' )
else:
print('{:>8}'.format(str(z)), end=' ' )
print()
Autre courbe elliptique :
ec = EllipticCurve(-1, 0, 13)
print(ec)
Ensemble des points :
e = []
for a in range(13):
for b in range(13):
if ec.has_point([a, b]):
if [a, b] not in e:
e.append([a, b])
e = [0] + e
print(e)
Table d'addition (sans les marges) :
for x in e:
for y in e:
z = ec.addP(x, y)
if z == 0:
print('{:>8}'.format('0 '), end=' ' )
else:
print('{:>8}'.format(str(z)), end=' ' )
print()
On remarque les trois points [0, 0], [1,0] et [12, 0] sont "à tangentes verticales" (si on peut dire !)
Le 5/12/2021 - Contact : Rossignol@bribes.org