Skip to content

Commit edb8820

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 edb8820

File tree

2 files changed

+119
-1
lines changed

2 files changed

+119
-1
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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 forward_map(u):
14+
"""Forward mapping function
15+
16+
Parameters:
17+
u (of type fe) : any field element
18+
Returns:
19+
fe, fe : affine X and Y coordinates of a point on the secp256k1 curve
20+
"""
21+
s = u**2
22+
x1 = C2 - C1*s / (fe(1)+B+s)
23+
g1 = x1**3 + B
24+
if g1.is_square():
25+
x, g = x1, g1
26+
else:
27+
x2 = -x1 - fe(1)
28+
g2 = x2**3 + B
29+
if g2.is_square():
30+
x, g = x2, g2
31+
else:
32+
x3 = fe(1) - (fe(1)+B+s)**2 / (fe(3)*s)
33+
g3 = x3**3 + B
34+
x, g = x3, g3
35+
y = g.sqrt()
36+
if y.is_odd() == u.is_odd():
37+
return x, y
38+
else:
39+
return x, -y
40+
41+
def reverse_map(x, y, i):
42+
"""Reverse mapping function
43+
44+
Parameters:
45+
fe, fe : X and Y coordinates of a point on the secp256k1 curve
46+
i : integer in range [0,3]
47+
Returns:
48+
u (of type fe) : such that forward_map(u) = (x,y), or None.
49+
50+
- There can be up to 4 such inverses, and i selects which formula to use.
51+
- Each i can independently from other i values return a value or None.
52+
- All non-None values returned across all 4 i values are guaranteed to be distinct.
53+
- Together they will cover all inverses of (x,y) under forward_map.
54+
"""
55+
if i == 0 or i == 1:
56+
z = fe(2)*x + fe(1)
57+
t1 = C1 - z
58+
t2 = C1 + z
59+
if not (t1*t2).is_square():
60+
return None
61+
if i == 0:
62+
if t2 == fe(0):
63+
return None
64+
if t1 == fe(0) and y.is_odd():
65+
return None
66+
u = ((fe(1)+B)*t1/t2).sqrt()
67+
else:
68+
x1 = -x-fe(1)
69+
if (x1**3 + B).is_square():
70+
return None
71+
u = ((fe(1)+B)*t2/t1).sqrt()
72+
else:
73+
z = fe(2) - fe(4)*B - fe(6)*x
74+
if not (z**2 - fe(16)*(B+fe(1))**2).is_square():
75+
return None
76+
if i == 2:
77+
s = (z + (z**2 - fe(16)*(B+fe(1))**2).sqrt()) / fe(4)
78+
else:
79+
if z**2 == fe(16)*(B+fe(1))**2:
80+
return None
81+
s = (z - (z**2 - fe(16)*(B+fe(1))**2).sqrt()) / fe(4)
82+
if not s.is_square():
83+
return None
84+
x1 = C2 - C1*s / (fe(1)+B+s)
85+
if (x1**3 + B).is_square():
86+
return None
87+
u = s.sqrt()
88+
if y.is_odd() == u.is_odd():
89+
return u
90+
else:
91+
return -u

test/functional/test_framework/key.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,34 @@ 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+
87+
@staticmethod
88+
def from_bytes(b): return fe(int.from_bytes(b, 'big'))
89+
def to_bytes(self): return self.val.to_bytes(32, 'big')
90+
6391
class EllipticCurve:
6492
def __init__(self, p, a, b):
6593
"""Initialize elliptic curve y^2 = x^3 + a*x + b over GF(p)."""
@@ -216,7 +244,6 @@ def mul(self, ps):
216244
r = self.add(r, p)
217245
return r
218246

219-
SECP256K1_FIELD_SIZE = 2**256 - 2**32 - 977
220247
SECP256K1 = EllipticCurve(SECP256K1_FIELD_SIZE, 0, 7)
221248
SECP256K1_G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, 1)
222249
SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141

0 commit comments

Comments
 (0)