forked from davidmcgrew/hash-sigs
/
lmots.py
159 lines (138 loc) · 6.91 KB
/
lmots.py
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
from Crypto import Random
from lmots_type import LmotsType
from need_to_sort import D_MESG, D_ITER, D_PBLC, D_PRG
from utils import u16str, u8str, digest, create_digest
from lmots_pubkey import LmotsPublicKey
from lmots_pvtkey import LmotsPrivateKey
from merkle import Merkle
from lmots_sig import LmotsSignature
from lmots_serializer import LmotsSerializer
class Lmots:
"""
Leighton-Micali One Time Signature (LMOTS) Algorithm implementation.
"""
def __init__(self, lmots_type=LmotsType.LMOTS_SHA256_M32_W8, entropy_source=None):
"""
Initializer.
:param lmots_type: LMOTS algorithm type
:param entropy_source: entropy source
"""
if not isinstance(lmots_type, LmotsType):
raise ValueError("lmots_type must be of type LmotsType")
self.lmots_type = lmots_type
if entropy_source is None:
self._entropy_source = Random.new()
else:
self._entropy_source = entropy_source
def generate_key_pair(self, s=None, seed=None):
"""
Generates a LMOTS key pair.
:param s: entropy s value; if None then random bytes read from entropy source
:param seed: seed value; if None then random bytes read from entropy source
:return: key pair set (public key, private key)
"""
if s is None:
s = self._entropy_source.read(self.lmots_type.n)
pvt_key = self.generate_private_key(s, seed)
pub_key = self.generate_public_key(s, pvt_key)
return pub_key, pvt_key
def generate_private_key(self, s, seed=None):
"""
Generate a LMOTS private key. In most cases, a key-pair is generated by calling the generate_key_pair
method. Since LMOTS public keys can be derived from the private key later, it is valid to simply
create the private key only and compute the public key at a different time. In that use-case the
generate_private_key method can be called directly.
Algorithm 0 as defined in Hash-Based Signatures draft RFC.
:param s: entropy s value
:param seed: seed value; if None then random bytes read from entropy source
:return: LMOTS private key object
"""
raw_key = list()
if seed is None:
for i in xrange(0, self.lmots_type.p):
raw_key.append(self._entropy_source.read(self.lmots_type.n))
else:
for i in xrange(0, self.lmots_type.p):
raw_key.append(digest(self.lmots_type.hash_alg, s + seed + u16str(i + 1) + D_PRG))
return LmotsPrivateKey(lmots_type=self.lmots_type, raw_key=raw_key, s=s, seed=seed, signatures_remaining=1)
def generate_public_key(self, s, pvt_key):
"""
Generate LMOTS public key from a private key. In most cases, a key-pair is generated by calling the
generate_key_pair method. Alternatively the public key can be derived from the private key at any time.
Algorithm 1 as defined in Hash-Based Signatures draft RFC.
:param s: entropy s value
:param pvt_key: LMOTS private key object
:return: LMOTS public key object
"""
if not isinstance(pvt_key, LmotsPrivateKey):
raise ValueError("pvt_key must be of type LmotsPrivateKey")
outer_hash = create_digest(self.lmots_type.hash_alg)
outer_hash.update(s)
for i, pvt_key in enumerate(pvt_key.raw_key):
tmp = pvt_key
for j in xrange(0, 2 ** self.lmots_type.w - 1):
tmp = digest(self.lmots_type.hash_alg, s + tmp + u16str(i) + u8str(j) + D_ITER)
outer_hash.update(tmp)
outer_hash.update(D_PBLC)
return LmotsPublicKey(s=s, k=outer_hash.digest(), lmots_type=self.lmots_type)
def sign(self, message, pvt_key):
"""
Generate a cryptographic signature for the supplied message using a LMOTS private key.
Algorithm 3: Generating a Signature from a Private Key and a Message as defined in Hash-Based Signatures draft
RFC.
:param message: message bytes
:param pvt_key: LMOTS private key object
:return: LMOTS signature object
"""
if not isinstance(pvt_key, LmotsPrivateKey):
raise ValueError("pvt_key must be of type LmotsPrivateKey")
if pvt_key.signatures_remaining != 1:
raise ValueError("private key has no signature operations remaining")
c = self._entropy_source.read(self.lmots_type.n)
hash_q = digest(self.lmots_type.hash_alg, pvt_key.s + c + message + D_MESG)
v = hash_q + Merkle.checksum(hash_q, self.lmots_type.w, self.lmots_type.ls)
y = list()
for i, x in enumerate(pvt_key.raw_key):
tmp = x
for j in xrange(0, Merkle.coef(v, i, self.lmots_type.w)):
tmp = digest(self.lmots_type.hash_alg, pvt_key.s + tmp + u16str(i) + u8str(j) + D_ITER)
y.append(tmp)
pvt_key.signatures_remaining = 0
lmots_sig = LmotsSignature(c, y, pvt_key.lmots_type)
return LmotsSerializer.serialize_signature(lmots_sig)
def verify(self, message, signature, pub_key):
"""
Verify a LMOTS signature using the message and s and k values.
Algorithm 4: Verifying a Signature and Message Using a Public Key as defined in Hash-Based Signatures draft RFC.
:param message: original message bytes
:param signature: signature to verify
:param pub_key: LMOTS public key object
:return: true if valid; otherwise false
"""
if not isinstance(pub_key, LmotsPublicKey):
raise ValueError("pub_key must be of type LmotsPublicKey")
ext_pub_key = self.extract_public_key(signature, pub_key.s, message)
is_valid = pub_key.k == ext_pub_key.k
return is_valid
def extract_public_key(self, signature, s, message):
"""
Extracts a LMOTS public key object from a LMOTS signature and the s value.
:param signature: LMOTS signature
:param s: entropy s value
:param message: original message
:return: LMOTS public key object
"""
lmots_sig = LmotsSerializer.deserialize_signature(signature)
if lmots_sig.lmots_type != self.lmots_type:
raise ValueError("signature type code does not match expected value")
hash_q = digest(self.lmots_type.hash_alg, s + lmots_sig.c + message + D_MESG)
v = hash_q + Merkle.checksum(hash_q, self.lmots_type.w, self.lmots_type.ls)
outer_hash = create_digest(self.lmots_type.hash_alg)
outer_hash.update(s)
for i, y in enumerate(lmots_sig.y):
tmp = y
for j in xrange(Merkle.coef(v, i, self.lmots_type.w), 2 ** self.lmots_type.w - 1):
tmp = digest(self.lmots_type.hash_alg, s + tmp + u16str(i) + u8str(j) + D_ITER)
outer_hash.update(tmp)
outer_hash.update(D_PBLC)
return LmotsPublicKey(s=s, k=outer_hash.digest(), lmots_type=self.lmots_type)