Skip to content

Commit 1dabf70

Browse files
Refactor DER and binary handling structures for better readability and easier translations
1 parent e215ded commit 1dabf70

20 files changed

+450
-547
lines changed

ellipticcurve/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
from ellipticcurve.utils.compatibility import *
1+
from ellipticcurve.utils.compatibility import *
2+
from ellipticcurve.privateKey import PrivateKey
3+
from ellipticcurve.publicKey import PublicKey
4+
from ellipticcurve.signature import Signature
5+
from ellipticcurve.utils.file import File
6+
from ellipticcurve.ecdsa import Ecdsa

ellipticcurve/curve.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def __init__(self, A, B, P, N, Gx, Gy, name, oid, nistName=None):
1717
self.G = Point(Gx, Gy)
1818
self.name = name
1919
self.nistName = nistName
20-
self.oid = oid
20+
self.oid = oid # ASN.1 Object Identifier
2121

2222
def contains(self, p):
2323
"""
@@ -40,7 +40,7 @@ def length(self):
4040
N=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141,
4141
Gx=0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
4242
Gy=0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8,
43-
oid=(1, 3, 132, 0, 10)
43+
oid=[1, 3, 132, 0, 10]
4444
)
4545

4646
prime256v1 = CurveFp(
@@ -52,13 +52,25 @@ def length(self):
5252
N=0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551,
5353
Gx=0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,
5454
Gy=0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5,
55-
oid=(1, 2, 840, 10045, 3, 1, 7),
55+
oid=[1, 2, 840, 10045, 3, 1, 7],
5656
)
57+
5758
p256 = prime256v1
5859

5960
supportedCurves = [
6061
secp256k1,
6162
prime256v1,
6263
]
6364

64-
curvesByOid = {curve.oid: curve for curve in supportedCurves}
65+
_curvesByOid = {tuple(curve.oid): curve for curve in supportedCurves}
66+
67+
68+
def getCurveByOid(oid):
69+
if oid not in _curvesByOid:
70+
raise Exception(
71+
"Unknown curve with oid %s; The following are registered: %s" % (
72+
".".join(oid),
73+
", ".join([curve.name for curve in supportedCurves])
74+
)
75+
)
76+
return _curvesByOid[oid]

ellipticcurve/ecdsa.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
from hashlib import sha256
22
from .signature import Signature
33
from .math import Math
4-
from .utils.binary import BinaryAscii
54
from .utils.integer import RandomInteger
5+
from .utils.binary import numberFromByteString
66
from .utils.compatibility import *
77

88

99
class Ecdsa:
1010

1111
@classmethod
1212
def sign(cls, message, privateKey, hashfunc=sha256):
13-
hashMessage = hashfunc(toBytes(message)).digest()
14-
numberMessage = BinaryAscii.numberFromString(hashMessage)
13+
byteMessage = hashfunc(toBytes(message)).digest()
14+
numberMessage = numberFromByteString(byteMessage)
1515
curve = privateKey.curve
1616

1717
r, s, randSignPoint = 0, 0, None
@@ -28,14 +28,14 @@ def sign(cls, message, privateKey, hashfunc=sha256):
2828

2929
@classmethod
3030
def verify(cls, message, signature, publicKey, hashfunc=sha256):
31-
hashMessage = hashfunc(toBytes(message)).digest()
32-
numberMessage = BinaryAscii.numberFromString(hashMessage)
31+
byteMessage = hashfunc(toBytes(message)).digest()
32+
numberMessage = numberFromByteString(byteMessage)
3333
curve = publicKey.curve
34-
sigR = signature.r
35-
sigS = signature.s
36-
inv = Math.inv(sigS, curve.N)
37-
u1 = Math.multiply(curve.G, n=(numberMessage * inv) % curve.N, A=curve.A, P=curve.P, N=curve.N)
38-
u2 = Math.multiply(publicKey.point, n=(sigR * inv) % curve.N, A=curve.A, P=curve.P, N=curve.N)
39-
add = Math.add(u1, u2, P=curve.P, A=curve.A)
34+
r = signature.r
35+
s = signature.s
36+
inv = Math.inv(s, curve.N)
37+
u1 = Math.multiply(curve.G, n=(numberMessage * inv) % curve.N, N=curve.N, A=curve.A, P=curve.P)
38+
u2 = Math.multiply(publicKey.point, n=(r * inv) % curve.N, N=curve.N, A=curve.A, P=curve.P)
39+
add = Math.add(u1, u2, A=curve.A, P=curve.P)
4040
modX = add.x % curve.N
41-
return sigR == modX
41+
return r == modX

ellipticcurve/math.py

Lines changed: 20 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,7 @@ def multiply(cls, p, n, N, A, P):
1616
:return: Point that represents the sum of First and Second Point
1717
"""
1818
return cls._fromJacobian(
19-
cls._jacobianMultiply(
20-
cls._toJacobian(p),
21-
n,
22-
N,
23-
A,
24-
P,
25-
),
26-
P,
19+
cls._jacobianMultiply(cls._toJacobian(p), n, N, A, P), P
2720
)
2821

2922
@classmethod
@@ -38,13 +31,7 @@ def add(cls, p, q, A, P):
3831
:return: Point that represents the sum of First and Second Point
3932
"""
4033
return cls._fromJacobian(
41-
cls._jacobianAdd(
42-
cls._toJacobian(p),
43-
cls._toJacobian(q),
44-
A,
45-
P,
46-
),
47-
P,
34+
cls._jacobianAdd(cls._toJacobian(p), cls._toJacobian(q), A, P), P,
4835
)
4936

5037
@classmethod
@@ -59,12 +46,19 @@ def inv(cls, x, n):
5946
if x == 0:
6047
return 0
6148

62-
lm, hm = 1, 0
63-
low, high = x % n, n
49+
lm = 1
50+
hm = 0
51+
low = x % n
52+
high = n
53+
6454
while low > 1:
6555
r = high // low
66-
nm, new = hm - lm * r, high - low * r
67-
lm, low, hm, high = nm, new, lm, low
56+
nm = hm - lm * r
57+
nw = high - low * r
58+
high = low
59+
hm = lm
60+
low = nw
61+
lm = nm
6862

6963
return lm % n
7064

@@ -88,11 +82,10 @@ def _fromJacobian(cls, p, P):
8882
:return: Point in default coordinates
8983
"""
9084
z = cls.inv(p.z, P)
85+
x = (p.x * z ** 2) % P
86+
y = (p.y * z ** 3) % P
9187

92-
return Point(
93-
(p.x * z ** 2) % P,
94-
(p.y * z ** 3) % P,
95-
)
88+
return Point(x, y, 0)
9689

9790
@classmethod
9891
def _jacobianDouble(cls, p, A, P):
@@ -113,6 +106,7 @@ def _jacobianDouble(cls, p, A, P):
113106
nx = (M**2 - 2 * S) % P
114107
ny = (M * (S - nx) - 8 * ysq ** 2) % P
115108
nz = (2 * p.y * p.z) % P
109+
116110
return Point(nx, ny, nz)
117111

118112
@classmethod
@@ -126,9 +120,9 @@ def _jacobianAdd(cls, p, q, A, P):
126120
:param A: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
127121
:return: Point that represents the sum of First and Second Point
128122
"""
129-
130123
if not p.y:
131124
return q
125+
132126
if not q.y:
133127
return p
134128

@@ -176,31 +170,9 @@ def _jacobianMultiply(cls, p, n, N, A, P):
176170

177171
if (n % 2) == 0:
178172
return cls._jacobianDouble(
179-
cls._jacobianMultiply(
180-
p,
181-
n // 2,
182-
N,
183-
A,
184-
P
185-
),
186-
A,
187-
P,
173+
cls._jacobianMultiply(p, n // 2, N, A, P), A, P
188174
)
189175

190-
# (n % 2) == 1:
191176
return cls._jacobianAdd(
192-
cls._jacobianDouble(
193-
cls._jacobianMultiply(
194-
p,
195-
n // 2,
196-
N,
197-
A,
198-
P,
199-
),
200-
A,
201-
P,
202-
),
203-
p,
204-
A,
205-
P,
177+
cls._jacobianDouble(cls._jacobianMultiply(p, n // 2, N, A, P), A, P), p, A, P
206178
)

ellipticcurve/privateKey.py

Lines changed: 36 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1+
from .math import Math
12
from .utils.integer import RandomInteger
2-
from .utils.compatibility import *
3-
from .utils.binary import BinaryAscii
4-
from .utils.der import fromPem, removeSequence, removeInteger, removeObject, removeOctetString, removeConstructed, toPem, encodeSequence, encodeInteger, encodeBitString, encodeOid, encodeOctetString, encodeConstructed
3+
from .utils.pem import getPemContent, createPem
4+
from .utils.binary import hexFromByteString, byteStringFromHex, intFromHex, base64FromByteString, byteStringFromBase64
5+
from .utils.der import hexFromInt, parse, encodeConstructed, DerFieldType, encodePrimitive
6+
from .curve import secp256k1, getCurveByOid
57
from .publicKey import PublicKey
6-
from .curve import secp256k1, curvesByOid, supportedCurves
7-
from .math import Math
8-
9-
hexAt = "\x00"
108

119

1210
class PrivateKey:
@@ -27,69 +25,48 @@ def publicKey(self):
2725
return PublicKey(point=publicPoint, curve=curve)
2826

2927
def toString(self):
30-
return BinaryAscii.stringFromNumber(number=self.secret, length=self.curve.length())
28+
return hexFromInt(self.secret)
3129

3230
def toDer(self):
33-
encodedPublicKey = self.publicKey().toString(encoded=True)
34-
35-
return encodeSequence(
36-
encodeInteger(1),
37-
encodeOctetString(self.toString()),
38-
encodeConstructed(0, encodeOid(*self.curve.oid)),
39-
encodeConstructed(1, encodeBitString(encodedPublicKey)),
31+
publicKeyString = self.publicKey().toString(encoded=True)
32+
hexadecimal = encodeConstructed(
33+
encodePrimitive(DerFieldType.integer, 1),
34+
encodePrimitive(DerFieldType.octetString, hexFromInt(self.secret)),
35+
encodePrimitive(DerFieldType.oidContainer, encodePrimitive(DerFieldType.object, self.curve.oid)),
36+
encodePrimitive(DerFieldType.publicKeyPointContainer, encodePrimitive(DerFieldType.bitString, publicKeyString))
4037
)
38+
return byteStringFromHex(hexadecimal)
4139

4240
def toPem(self):
43-
return toPem(der=toBytes(self.toDer()), name="EC PRIVATE KEY")
41+
der = self.toDer()
42+
return createPem(content=base64FromByteString(der), template=_pemTemplate)
4443

4544
@classmethod
4645
def fromPem(cls, string):
47-
privateKeyPem = string[string.index("-----BEGIN EC PRIVATE KEY-----"):]
48-
return cls.fromDer(fromPem(privateKeyPem))
46+
privateKeyPem = getPemContent(pem=string, template=_pemTemplate)
47+
return cls.fromDer(byteStringFromBase64(privateKeyPem))
4948

5049
@classmethod
5150
def fromDer(cls, string):
52-
t, empty = removeSequence(string)
53-
if len(empty) != 0:
54-
raise Exception(
55-
"trailing junk after DER private key: " +
56-
BinaryAscii.hexFromBinary(empty)
57-
)
58-
59-
one, t = removeInteger(t)
60-
if one != 1:
61-
raise Exception(
62-
"expected '1' at start of DER private key, got %d" % one
63-
)
64-
65-
privateKeyStr, t = removeOctetString(t)
66-
tag, curveOidStr, t = removeConstructed(t)
67-
if tag != 0:
68-
raise Exception("expected tag 0 in DER private key, got %d" % tag)
69-
70-
oidCurve, empty = removeObject(curveOidStr)
71-
72-
if len(empty) != 0:
73-
raise Exception(
74-
"trailing junk after DER private key curve_oid: %s" %
75-
BinaryAscii.hexFromBinary(empty)
76-
)
77-
78-
if oidCurve not in curvesByOid:
79-
raise Exception(
80-
"unknown curve with oid %s; The following are registered: %s" % (
81-
oidCurve,
82-
", ".join([curve.name for curve in supportedCurves])
83-
)
84-
)
85-
86-
curve = curvesByOid[oidCurve]
87-
88-
if len(privateKeyStr) < curve.length():
89-
privateKeyStr = hexAt * (curve.lenght() - len(privateKeyStr)) + privateKeyStr
90-
91-
return cls.fromString(privateKeyStr, curve)
51+
hexadecimal = hexFromByteString(string)
52+
privateKeyFlag, secretHex, curveData, publicKeyString = parse(hexadecimal)[0]
53+
if privateKeyFlag != 1:
54+
raise Exception("Private keys should start with a '1' flag, but a '{flag}' was found instead".format(
55+
flag=privateKeyFlag
56+
))
57+
curve = getCurveByOid(curveData[0])
58+
privateKey = cls.fromString(string=secretHex, curve=curve)
59+
if privateKey.publicKey().toString(encoded=True) != publicKeyString[0]:
60+
raise Exception("The public key described inside the private key file doesn't match the actual public key of the pair")
61+
return privateKey
9262

9363
@classmethod
9464
def fromString(cls, string, curve=secp256k1):
95-
return PrivateKey(secret=BinaryAscii.numberFromString(string), curve=curve)
65+
return PrivateKey(secret=intFromHex(string), curve=curve)
66+
67+
68+
_pemTemplate = """
69+
-----BEGIN EC PRIVATE KEY-----
70+
{content}
71+
-----END EC PRIVATE KEY-----
72+
"""

0 commit comments

Comments
 (0)