Skip to content

Commit c33fcf5

Browse files
committed
Add class for field elements and ellsq mapping functions
- source: https://github.com/sipa/writeups/tree/main/elligator-square-for-bn - f maps maps every field element to a curve point - r is a partial reverse function which can map a curve point to a field element
1 parent bdf9292 commit c33fcf5

File tree

2 files changed

+97
-1
lines changed

2 files changed

+97
-1
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Source: https://github.com/sipa/writeups/tree/main/elligator-square-for-bn
2+
"""Test-only Elligator Squared implementation
3+
4+
WARNING: This code is slow and uses bad randomness.
5+
Do not use for anything but tests."""
6+
7+
from .key import fe
8+
9+
C1 = fe(-3).sqrt()
10+
C2 = (C1 - fe(1)) / fe(2)
11+
B = fe(7)
12+
13+
def f(u):
14+
"""Forward mapping function"""
15+
s = u**2
16+
x1 = C2 - C1*s / (fe(1)+B+s)
17+
g1 = x1**3 + B
18+
if g1.is_square():
19+
x, g = x1, g1
20+
else:
21+
x2 = -x1 - fe(1)
22+
g2 = x2**3 + B
23+
if g2.is_square():
24+
x, g = x2, g2
25+
else:
26+
x3 = fe(1) - (fe(1)+B+s)**2 / (fe(3)*s)
27+
g3 = x3**3 + B
28+
x, g = x3, g3
29+
y = g.sqrt()
30+
if y.is_odd() == u.is_odd():
31+
return (x, y)
32+
else:
33+
return (x, -y)
34+
35+
def r(x,y,i):
36+
"""Reverse mapping function"""
37+
if i == 0 or i == 1:
38+
z = fe(2)*x + fe(1)
39+
t1 = C1 - z
40+
t2 = C1 + z
41+
if not (t1*t2).is_square():
42+
return None
43+
if i == 0:
44+
if t2 == fe(0):
45+
return None
46+
if t1 == fe(0) and y.is_odd():
47+
return None
48+
u = ((fe(1)+B)*t1/t2).sqrt()
49+
else:
50+
x1 = -x-fe(1)
51+
if (x1**3 + B).is_square():
52+
return None
53+
u = ((fe(1)+B)*t2/t1).sqrt()
54+
else:
55+
z = fe(2) - fe(4)*B - fe(6)*x
56+
if not (z**2 - fe(16)*(B+fe(1))**2).is_square():
57+
return None
58+
if i == 2:
59+
s = (z + (z**2 - fe(16)*(B+fe(1))**2).sqrt()) / fe(4)
60+
else:
61+
if z**2 == fe(16)*(B+fe(1))**2:
62+
return None
63+
s = (z - (z**2 - fe(16)*(B+fe(1))**2).sqrt()) / fe(4)
64+
if not s.is_square():
65+
return None
66+
x1 = C2 - C1*s / (fe(1)+B+s)
67+
if (x1**3 + B).is_square():
68+
return None
69+
u = s.sqrt()
70+
if y.is_odd() == u.is_odd():
71+
return u
72+
else:
73+
return -u

test/functional/test_framework/key.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,30 @@ def modsqrt(a, p):
6060
return sqrt
6161
return None
6262

63+
SECP256K1_FIELD_SIZE = 2**256 - 2**32 - 977
64+
65+
class fe:
66+
"""Prime field over 2^256 - 2^32 - 977"""
67+
def __init__(self, x):
68+
self.val = x % SECP256K1_FIELD_SIZE
69+
70+
def __add__ (self, o): return fe(self.val + o.val)
71+
def __eq__ (self, o): return self.val == o.val
72+
def __hash__ (self ): return id(self)
73+
def __mul__ (self, o): return fe(self.val * o.val)
74+
def __neg__ (self ): return fe(-self.val)
75+
def __pow__ (self, s): return fe(pow(self.val, s, SECP256K1_FIELD_SIZE))
76+
def __sub__ (self, o): return fe(self.val - o.val)
77+
def __truediv__ (self, o): return fe(self.val * o.invert().val)
78+
79+
def invert (self ):
80+
return fe(modinv(self.val, SECP256K1_FIELD_SIZE))
81+
def is_odd(self): return (self.val & 1) != 0
82+
def is_square(self):
83+
return jacobi_symbol(self.val, SECP256K1_FIELD_SIZE) >= 0
84+
def sqrt(self):
85+
return fe(modsqrt(self.val, SECP256K1_FIELD_SIZE))
86+
6387
class EllipticCurve:
6488
def __init__(self, p, a, b):
6589
"""Initialize elliptic curve y^2 = x^3 + a*x + b over GF(p)."""
@@ -216,7 +240,6 @@ def mul(self, ps):
216240
r = self.add(r, p)
217241
return r
218242

219-
SECP256K1_FIELD_SIZE = 2**256 - 2**32 - 977
220243
SECP256K1 = EllipticCurve(SECP256K1_FIELD_SIZE, 0, 7)
221244
SECP256K1_G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, 1)
222245
SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141

0 commit comments

Comments
 (0)