1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
from .math import Math
from .point import Point
from .curve import secp256k1, getByOid
from .utils.pem import getPemContent, createPem
from .utils.der import hexFromInt, parse, DerFieldType, encodeConstructed, encodePrimitive
from .utils.binary import hexFromByteString, byteStringFromHex, intFromHex, base64FromByteString, byteStringFromBase64
class PublicKey:
def __init__(self, point, curve):
self.point = point
self.curve = curve
def toString(self, encoded=False):
baseLength = 2 * self.curve.length()
xHex = hexFromInt(self.point.x).zfill(baseLength)
yHex = hexFromInt(self.point.y).zfill(baseLength)
string = xHex + yHex
if encoded:
return "0004" + string
return string
def toCompressed(self):
baseLength = 2 * self.curve.length()
parityTag = _evenTag if self.point.y % 2 == 0 else _oddTag
xHex = hexFromInt(self.point.x).zfill(baseLength)
return parityTag + xHex
def toDer(self):
hexadecimal = encodeConstructed(
encodeConstructed(
encodePrimitive(DerFieldType.object, _ecdsaPublicKeyOid),
encodePrimitive(DerFieldType.object, self.curve.oid),
),
encodePrimitive(DerFieldType.bitString, self.toString(encoded=True)),
)
return byteStringFromHex(hexadecimal)
def toPem(self):
der = self.toDer()
return createPem(content=base64FromByteString(der), template=_pemTemplate)
@classmethod
def fromPem(cls, string):
publicKeyPem = getPemContent(pem=string, template=_pemTemplate)
return cls.fromDer(byteStringFromBase64(publicKeyPem))
@classmethod
def fromDer(cls, string):
hexadecimal = hexFromByteString(string)
curveData, pointString = parse(hexadecimal)[0]
publicKeyOid, curveOid = curveData
if publicKeyOid != _ecdsaPublicKeyOid:
raise Exception("The Public Key Object Identifier (OID) should be {ecdsaPublicKeyOid}, but {actualOid} was found instead".format(
ecdsaPublicKeyOid=_ecdsaPublicKeyOid,
actualOid=publicKeyOid,
))
curve = getByOid(curveOid)
return cls.fromString(string=pointString, curve=curve)
@classmethod
def fromString(cls, string, curve=secp256k1, validatePoint=True):
baseLength = 2 * curve.length()
if len(string) > 2 * baseLength and string[:4] == "0004":
string = string[4:]
xs = string[:baseLength]
ys = string[baseLength:]
p = Point(
x=intFromHex(xs),
y=intFromHex(ys),
)
publicKey = PublicKey(point=p, curve=curve)
if not validatePoint:
return publicKey
if p.isAtInfinity():
raise Exception("Public Key point is at infinity")
if not curve.contains(p):
raise Exception("Point ({x},{y}) is not valid for curve {name}".format(x=p.x, y=p.y, name=curve.name))
if not Math.multiply(p=p, n=curve.N, N=curve.N, A=curve.A, P=curve.P).isAtInfinity():
raise Exception("Point ({x},{y}) * {name}.N is not at infinity".format(x=p.x, y=p.y, name=curve.name))
return publicKey
@classmethod
def fromCompressed(cls, string, curve=secp256k1):
parityTag, xHex = string[:2], string[2:]
if parityTag not in [_evenTag, _oddTag]:
raise Exception("Compressed string should start with 02 or 03")
x = intFromHex(xHex)
y = curve.y(x, isEven=parityTag == _evenTag)
return cls(point=Point(x, y), curve=curve)
_evenTag = "02"
_oddTag = "03"
_ecdsaPublicKeyOid = (1, 2, 840, 10045, 2, 1)
_pemTemplate = """
-----BEGIN PUBLIC KEY-----
{content}
-----END PUBLIC KEY-----
"""
|