Skip to content

Commit b679785

Browse files
jnewberysipa
authored andcommitted
Add comments to Python ECDSA implementation
1 parent 8c7b932 commit b679785

File tree

1 file changed

+56
-16
lines changed
  • test/functional/test_framework

1 file changed

+56
-16
lines changed

test/functional/test_framework/key.py

+56-16
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
# Copyright (c) 2019 Pieter Wuille
2-
2+
# Distributed under the MIT software license, see the accompanying
3+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
34
"""Test-only secp256k1 elliptic curve implementation
45
56
WARNING: This code is slow, uses bad randomness, does not properly protect
67
keys, and is trivially vulnerable to side channel attacks. Do not use for
7-
anything but tests.
8-
"""
9-
8+
anything but tests."""
109
import random
1110

1211
def modinv(a, n):
1312
"""Compute the modular inverse of a modulo n
1413
15-
See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers
14+
See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers.
1615
"""
1716
t1, t2 = 0, 1
1817
r1, r2 = n, a
@@ -30,8 +29,9 @@ def jacobi_symbol(n, k):
3029
"""Compute the Jacobi symbol of n modulo k
3130
3231
See http://en.wikipedia.org/wiki/Jacobi_symbol
33-
"""
34-
assert k > 0 and k & 1
32+
33+
For our application k is always prime, so this is the same as the Legendre symbol."""
34+
assert k > 0 and k & 1, "jacobi symbol is only defined for positive odd k"
3535
n %= k
3636
t = 0
3737
while n != 0:
@@ -47,11 +47,18 @@ def jacobi_symbol(n, k):
4747
return 0
4848

4949
def modsqrt(a, p):
50-
"""Compute the square root of a modulo p
50+
"""Compute the square root of a modulo p when p % 4 = 3.
5151
52-
For p = 3 mod 4, if a square root exists, it is equal to a**((p+1)/4) mod p.
52+
The Tonelli-Shanks algorithm can be used. See https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm
53+
54+
Limiting this function to only work for p % 4 = 3 means we don't need to
55+
iterate through the loop. The highest n such that p - 1 = 2^n Q with Q odd
56+
is n = 1. Therefore Q = (p-1)/2 and sqrt = a^((Q+1)/2) = a^((p+1)/4)
57+
58+
secp256k1's is defined over field of size 2**256 - 2**32 - 977, which is 3 mod 4.
5359
"""
54-
assert(p % 4 == 3) # Only p = 3 mod 4 is implemented
60+
if p % 4 != 3:
61+
raise NotImplementedError("modsqrt only implemented for p % 4 = 3")
5562
sqrt = pow(a, (p + 1)//4, p)
5663
if pow(sqrt, 2, p) == a % p:
5764
return sqrt
@@ -65,7 +72,9 @@ def __init__(self, p, a, b):
6572
self.b = b % p
6673

6774
def affine(self, p1):
68-
"""Convert a Jacobian point tuple p1 to affine form, or None if at infinity."""
75+
"""Convert a Jacobian point tuple p1 to affine form, or None if at infinity.
76+
77+
An affine point is represented as the Jacobian (x, y, 1)"""
6978
x1, y1, z1 = p1
7079
if z1 == 0:
7180
return None
@@ -101,7 +110,9 @@ def lift_x(self, x):
101110
return (x, y, 1)
102111

103112
def double(self, p1):
104-
"""Double a Jacobian tuple p1"""
113+
"""Double a Jacobian tuple p1
114+
115+
See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Doubling"""
105116
x1, y1, z1 = p1
106117
if z1 == 0:
107118
return (0, 1, 0)
@@ -119,10 +130,13 @@ def double(self, p1):
119130
return (x2, y2, z2)
120131

121132
def add_mixed(self, p1, p2):
122-
"""Add a Jacobian tuple p1 and an affine tuple p2"""
133+
"""Add a Jacobian tuple p1 and an affine tuple p2
134+
135+
See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition (with affine point)"""
123136
x1, y1, z1 = p1
124137
x2, y2, z2 = p2
125138
assert(z2 == 1)
139+
# Adding to the point at infinity is a no-op
126140
if z1 == 0:
127141
return p2
128142
z1_2 = (z1**2) % self.p
@@ -131,7 +145,9 @@ def add_mixed(self, p1, p2):
131145
s2 = (y2 * z1_3) % self.p
132146
if x1 == u2:
133147
if (y1 != s2):
148+
# p1 and p2 are inverses. Return the point at infinity.
134149
return (0, 1, 0)
150+
# p1 == p2. The formulas below fail when the two points are equal.
135151
return self.double(p1)
136152
h = u2 - x1
137153
r = s2 - y1
@@ -144,13 +160,17 @@ def add_mixed(self, p1, p2):
144160
return (x3, y3, z3)
145161

146162
def add(self, p1, p2):
147-
"""Add two Jacobian tuples p1 and p2"""
163+
"""Add two Jacobian tuples p1 and p2
164+
165+
See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition"""
148166
x1, y1, z1 = p1
149167
x2, y2, z2 = p2
168+
# Adding the point at infinity is a no-op
150169
if z1 == 0:
151170
return p2
152171
if z2 == 0:
153172
return p1
173+
# Adding an Affine to a Jacobian is more efficient since we save field multiplications and squarings when z = 1
154174
if z1 == 1:
155175
return self.add_mixed(p2, p1)
156176
if z2 == 1:
@@ -165,7 +185,9 @@ def add(self, p1, p2):
165185
s2 = (y2 * z1_3) % self.p
166186
if u1 == u2:
167187
if (s1 != s2):
188+
# p1 and p2 are inverses. Return the point at infinity.
168189
return (0, 1, 0)
190+
# p1 == p2. The formulas below fail when the two points are equal.
169191
return self.double(p1)
170192
h = u2 - u1
171193
r = s2 - s1
@@ -214,6 +236,8 @@ def set(self, data):
214236
x = int.from_bytes(data[1:33], 'big')
215237
if SECP256K1.is_x_coord(x):
216238
p = SECP256K1.lift_x(x)
239+
# if the oddness of the y co-ord isn't correct, find the other
240+
# valid y
217241
if (p[1] & 1) != (data[0] & 1):
218242
p = SECP256K1.negate(p)
219243
self.p = p
@@ -243,8 +267,14 @@ def get_bytes(self):
243267
return bytes([0x04]) + p[0].to_bytes(32, 'big') + p[1].to_bytes(32, 'big')
244268

245269
def verify_ecdsa(self, sig, msg, low_s=True):
246-
"""Verify a strictly DER-encoded ECDSA signature against this pubkey."""
270+
"""Verify a strictly DER-encoded ECDSA signature against this pubkey.
271+
272+
See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
273+
ECDSA verifier algorithm"""
247274
assert(self.valid)
275+
276+
# Extract r and s from the DER formatted signature. Return false for
277+
# any DER encoding errors.
248278
if (sig[1] + 2 != len(sig)):
249279
return False
250280
if (len(sig) < 4):
@@ -275,11 +305,15 @@ def verify_ecdsa(self, sig, msg, low_s=True):
275305
if (slen > 1 and (sig[6+rlen] == 0) and not (sig[7+rlen] & 0x80)):
276306
return False
277307
s = int.from_bytes(sig[6+rlen:6+rlen+slen], 'big')
308+
309+
# Verify that r and s are within the group order
278310
if r < 1 or s < 1 or r >= SECP256K1_ORDER or s >= SECP256K1_ORDER:
279311
return False
280312
if low_s and s >= SECP256K1_ORDER_HALF:
281313
return False
282314
z = int.from_bytes(msg, 'big')
315+
316+
# Run verifier algorithm on r, s
283317
w = modinv(s, SECP256K1_ORDER)
284318
u1 = z*w % SECP256K1_ORDER
285319
u2 = r*w % SECP256K1_ORDER
@@ -331,7 +365,10 @@ def get_pubkey(self):
331365
return ret
332366

333367
def sign_ecdsa(self, msg, low_s=True):
334-
"""Construct a DER-encoded ECDSA signature with this key."""
368+
"""Construct a DER-encoded ECDSA signature with this key.
369+
370+
See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
371+
ECDSA signer algorithm."""
335372
assert(self.valid)
336373
z = int.from_bytes(msg, 'big')
337374
# Note: no RFC6979, but a simple random nonce (some tests rely on distinct transactions for the same operation)
@@ -341,6 +378,9 @@ def sign_ecdsa(self, msg, low_s=True):
341378
s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER
342379
if low_s and s > SECP256K1_ORDER_HALF:
343380
s = SECP256K1_ORDER - s
381+
# Represent in DER format. The byte representations of r and s have
382+
# length rounded up (255 bits becomes 32 bytes and 256 bits becomes 33
383+
# bytes).
344384
rb = r.to_bytes((r.bit_length() + 8) // 8, 'big')
345385
sb = s.to_bytes((s.bit_length() + 8) // 8, 'big')
346386
return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb

0 commit comments

Comments
 (0)