Skip to content

Commit 2d9e717

Browse files
committed
Merge #852: Add sage script for generating scalar_split_lambda constants
329a2e0 sage: Add script for generating scalar_split_lambda constants (Tim Ruffing) f554dfc sage: Reorganize files (Tim Ruffing) Pull request description: ACKs for top commit: sipa: ACK 329a2e0 Tree-SHA512: d41fe5eba332f48af0b800778aa076925c4e8e95ec21c4371a500ddd6088b6d52961bdb93f7ce2b127e18095667dbb966a0d14191177f0d0e78dfaf55271d5e2
2 parents dc6e5c3 + 329a2e0 commit 2d9e717

4 files changed

+151
-6
lines changed

sage/gen_exhaustive_groups.sage

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
# Define field size and field
2-
P = 2^256 - 2^32 - 977
3-
F = GF(P)
4-
BETA = F(0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee)
5-
6-
assert(BETA != F(1) and BETA^3 == F(1))
1+
load("secp256k1_params.sage")
72

83
orders_done = set()
94
results = {}

sage/gen_split_lambda_constants.sage

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
""" Generates the constants used in secp256k1_scalar_split_lambda.
2+
3+
See the comments for secp256k1_scalar_split_lambda in src/scalar_impl.h for detailed explanations.
4+
"""
5+
6+
load("secp256k1_params.sage")
7+
8+
def inf_norm(v):
9+
"""Returns the infinity norm of a vector."""
10+
return max(map(abs, v))
11+
12+
def gauss_reduction(i1, i2):
13+
v1, v2 = i1.copy(), i2.copy()
14+
while True:
15+
if inf_norm(v2) < inf_norm(v1):
16+
v1, v2 = v2, v1
17+
# This is essentially
18+
# m = round((v1[0]*v2[0] + v1[1]*v2[1]) / (inf_norm(v1)**2))
19+
# (rounding to the nearest integer) without relying on floating point arithmetic.
20+
m = ((v1[0]*v2[0] + v1[1]*v2[1]) + (inf_norm(v1)**2) // 2) // (inf_norm(v1)**2)
21+
if m == 0:
22+
return v1, v2
23+
v2[0] -= m*v1[0]
24+
v2[1] -= m*v1[1]
25+
26+
def find_split_constants_gauss():
27+
"""Find constants for secp256k1_scalar_split_lamdba using gauss reduction."""
28+
(v11, v12), (v21, v22) = gauss_reduction([0, N], [1, int(LAMBDA)])
29+
30+
# We use related vectors in secp256k1_scalar_split_lambda.
31+
A1, B1 = -v21, -v11
32+
A2, B2 = v22, -v21
33+
34+
return A1, B1, A2, B2
35+
36+
def find_split_constants_explicit_tof():
37+
"""Find constants for secp256k1_scalar_split_lamdba using the trace of Frobenius.
38+
39+
See Benjamin Smith: "Easy scalar decompositions for efficient scalar multiplication on
40+
elliptic curves and genus 2 Jacobians" (https://eprint.iacr.org/2013/672), Example 2
41+
"""
42+
assert P % 3 == 1 # The paper says P % 3 == 2 but that appears to be a mistake, see [10].
43+
assert C.j_invariant() == 0
44+
45+
t = C.trace_of_frobenius()
46+
47+
c = Integer(sqrt((4*P - t**2)/3))
48+
A1 = Integer((t - c)/2 - 1)
49+
B1 = c
50+
51+
A2 = Integer((t + c)/2 - 1)
52+
B2 = Integer(1 - (t - c)/2)
53+
54+
# We use a negated b values in secp256k1_scalar_split_lambda.
55+
B1, B2 = -B1, -B2
56+
57+
return A1, B1, A2, B2
58+
59+
A1, B1, A2, B2 = find_split_constants_explicit_tof()
60+
61+
# For extra fun, use an independent method to recompute the constants.
62+
assert (A1, B1, A2, B2) == find_split_constants_gauss()
63+
64+
# PHI : Z[l] -> Z_n where phi(a + b*l) == a + b*lambda mod n.
65+
def PHI(a,b):
66+
return Z(a + LAMBDA*b)
67+
68+
# Check that (A1, B1) and (A2, B2) are in the kernel of PHI.
69+
assert PHI(A1, B1) == Z(0)
70+
assert PHI(A2, B2) == Z(0)
71+
72+
# Check that the parallelogram generated by (A1, A2) and (B1, B2)
73+
# is a fundamental domain by containing exactly N points.
74+
# Since the LHS is the determinant and N != 0, this also checks that
75+
# (A1, A2) and (B1, B2) are linearly independent. By the previous
76+
# assertions, (A1, A2) and (B1, B2) are a basis of the kernel.
77+
assert A1*B2 - B1*A2 == N
78+
79+
# Check that their components are short enough.
80+
assert (A1 + A2)/2 < sqrt(N)
81+
assert B1 < sqrt(N)
82+
assert B2 < sqrt(N)
83+
84+
G1 = round((2**384)*B2/N)
85+
G2 = round((2**384)*(-B1)/N)
86+
87+
def rnddiv2(v):
88+
if v & 1:
89+
v += 1
90+
return v >> 1
91+
92+
def scalar_lambda_split(k):
93+
"""Equivalent to secp256k1_scalar_lambda_split()."""
94+
c1 = rnddiv2((k * G1) >> 383)
95+
c2 = rnddiv2((k * G2) >> 383)
96+
c1 = (c1 * -B1) % N
97+
c2 = (c2 * -B2) % N
98+
r2 = (c1 + c2) % N
99+
r1 = (k + r2 * -LAMBDA) % N
100+
return (r1, r2)
101+
102+
# The result of scalar_lambda_split can depend on the representation of k (mod n).
103+
SPECIAL = (2**383) // G2 + 1
104+
assert scalar_lambda_split(SPECIAL) != scalar_lambda_split(SPECIAL + N)
105+
106+
print(' A1 =', hex(A1))
107+
print(' -B1 =', hex(-B1))
108+
print(' A2 =', hex(A2))
109+
print(' -B2 =', hex(-B2))
110+
print(' =', hex(Z(-B2)))
111+
print(' -LAMBDA =', hex(-LAMBDA))
112+
113+
print(' G1 =', hex(G1))
114+
print(' G2 =', hex(G2))
File renamed without changes.

sage/secp256k1_params.sage

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Prime order of finite field underlying secp256k1 (2^256 - 2^32 - 977)"""
2+
P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
3+
4+
"""Finite field underlying secp256k1"""
5+
F = FiniteField(P)
6+
7+
"""Elliptic curve secp256k1: y^2 = x^3 + 7"""
8+
C = EllipticCurve([F(0), F(7)])
9+
10+
"""Base point of secp256k1"""
11+
G = C.lift_x(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798)
12+
13+
"""Prime order of secp256k1"""
14+
N = C.order()
15+
16+
"""Finite field of scalars of secp256k1"""
17+
Z = FiniteField(N)
18+
19+
""" Beta value of secp256k1 non-trivial endomorphism: lambda * (x, y) = (beta * x, y)"""
20+
BETA = F(2)^((P-1)/3)
21+
22+
""" Lambda value of secp256k1 non-trivial endomorphism: lambda * (x, y) = (beta * x, y)"""
23+
LAMBDA = Z(3)^((N-1)/3)
24+
25+
assert is_prime(P)
26+
assert is_prime(N)
27+
28+
assert BETA != F(1)
29+
assert BETA^3 == F(1)
30+
assert BETA^2 + BETA + 1 == 0
31+
32+
assert LAMBDA != Z(1)
33+
assert LAMBDA^3 == Z(1)
34+
assert LAMBDA^2 + LAMBDA + 1 == 0
35+
36+
assert Integer(LAMBDA)*G == C(BETA*G[0], G[1])

0 commit comments

Comments
 (0)