Package: winternitz
Winternitz One-Time-SignaturePython implementation of Winternitz one-time-signature schemesDescriptionWinternitz one-time-signature is an extension of lamport one-time-signature.
This python package can be used to execute WOTS operations, including
key generation, signature generation and signature verification.
Currently WOTS and WOTS+ are implemented.IntroductionLamport invented an algorithm in 1979 which allowed one to create one-time-signatures
using a cryptographically secure one-way function. It is the basis for the Winternitz
one-time-signature algorithm. Winternitz added the possibility to adjust the tradeoff
between time- and space-complexity.Lamport one-time-signature schemeLamport suggested to create two secret keys for each bit of a message which will
be signed. One for each value the bit can take. To derive the verification key,
each secret key is hashed once. Now you have a secret key and a verification key,
which consists ofm2-tuples of values, wheremis the number
of bits of the message. The verification key is published.
The signature consists ofmvalues. For each bit of the message you release a secret key from
the corresponding secret keys, depending on which value the bit has. All those secret
keys form the signature for the message. The verifier hashes each of your secret keys
once and compares it to one verification key for this position, depending on the value
of the bit. The signature is valid, if and only if all derived verification keys match with
your published verification key at the correct position of the 2-tuple, which is determined by the value
of the bit. This algorithm is quite fast
(comparing it to existing PQC-algorithms), but the signature sizes are huge.Winternitz extensionWinternitz extended lamports algorithm by offering the possiblity to decide
how many bits will be signed together. The amount of numbers those bits can
represent is called the Winternitz parameter (w = 2^{bits}). This method offers the huge
advantage that the user of this algorithm can choose the time and space tradeoff
(whether speed or storage capacity is more relevant). A fingerprint of the message which
will be signed is split into groups ofceil(log_2(w))bits. Each of these groups gets one secret key.
Each verification key is derived by hashing the secret key for each group2^{w-1}times. All verification
keys will be published and represent one unified verification key. When signing a message, the
fingerprint of the message is split into groups ofceil(log2(w))bits. To create the signature, the
private key for each bit group is hashedbitgroup_valuetimes, wherebitgroup_valueis the value
of the bitgroup. Additionally a (inverse sum) checksum is appended, which denies man-in-the-middle
attacks. The checksum is calculated from the signature, split into bit groups ofceil(log2(w))bits, and
signed. To verify the signature, the fingerprint of the message is first split into bit groups ofceil(log2(w)bits each. The basic idea is to take the signature of each bit group, calculate the verification key
from it and finally compare it to the published verification key. Since the signature was hashedbitgroup_valuetimes, all you have to do to calculate the verification key from the signature
is to hash the signature2^{w-1} - bitgroup_value - 1times. Besides verifing the message, the verifier
must also calculate the checksum and verify it.SetupRequires: Python >= 3.4Install package:pip install winternitzInstall test tools:pip install winternitz[TEST]Install linter (for tox tests):pip install winternitz[LINT]Install documentation tools:pip install winternitz[DOCS]Install everything:pip install winternitz[ALL]TestWithout tox (no linter checks):python setup.py testWith tox:python-mtoxGenerate documentationpython setup.py docsUsageThe packagewinternitzcontains a module calledsignatures.
Within this package you can find the classes WOTS and WOTSPLUS.
Those classes can be used out of the box to sign or verify
messagesWOTSimportwinternitz.signatures# Create signature and verify it with the same objectwots=winternitz.signatures.WOTS()message="My message in bytes format".encode("utf-8")sig=wots.sign(message)success=wots.verify(message=message,signature=sig["signature"])print("Verification success: "+str(success))# Output: Verification success: TrueIf you don’t specify any values in the constructor of WOTS, it will use
the winternitz parameter 16 and the hash functionsha512as default parameters.
The private key will be generated from entropy. After you received the public key,
either throughwots.pubkeyor inside the dict that is returned by thewots.sign(message)function call, you publish it. Verify that it was not modified.
In the best case a man-in-the-middle attack to modify your public key is impossible
by the design of the application. The last step is to publish your message and every
information in the dict that is returned bywots.sign(message), except the public
key (since it was already published). Publishing the fingerprint is optional, since it
is not essential for the signature verification. The signature dict contains the following
values:{"w":winternitzparameter(Type:int),"fingerprint":messagehash(Type:bytes),"hashalgo":hashalgorithm(Type:str),"digestsize":hashbytecount(Type:int),"pubkey":publickey(Type:List[bytes]),"signature":signature(Type:List[bytes])}With that data, another person can verify the authenticity of your message:# Another person or machine wants to verify your signature:# get required hash function by comparing the name# published with local implementaitonsifsig["hashalgo"]=="openssl_sha512":hashfunc=winternitz.signatures.openssl_sha512elifsig["hashalgo"]=="openssl_sha256":hashfunc=winternitz.signautres.openssl_sha256else:raiseNotImplementedError("Hash function not implemented")wots_other=winternitz.signatures.WOTS(w=sig["w"],hashfunction=hashfunc,digestsize=sig["digestsize"],pubkey=sig["pubkey"])success=wots_other.verify(message=message,signature=sig["signature"])print("Verification success: "+str(success))# Output: Verification success: TrueIn certain situations it might not be wanted to verify the derived public key with the
public key inside the executing WOTS object. For example, this can be this case when
the verification happens in a wrapping structure, like a XMSS tree. In such cases
the public key can be derived from a message and a signature with the functionwots.getPubkeyFromSignature(message=message, signature=signature)WOTSPLUSimportwinternitz.signatureswotsplus=winternitz.signatures.WOTSPLUS()message="My message in bytes format".encode("utf-8")sig=wotsplus.sign(message)success=wotsplus.verify(message=message,signature=sig["signature"])print("Verification success: "+str(success))# Output: Verification success: TrueIf you don’t specify any values in the constructor of WOTSPLUS, it will use the winternitz parameter
16 and the hash function defaults tosha256. It further requires a pseudo random function, which defaults
toHMAC-sha256, as well as a seed which is also generated from entropy. For further
informations about functions and their parameters, visit the module reference in
this thedocumentation.
Since WOTS+ uses a pseudo random function and a seed to derive signatures and public
keys, they have to be published as well. In addition to the signature of WOTS, the returned dict contains
the following values:{# ..."prf":pseudorandomfunction(Type:str),"seed":Seedusedinprf(Type:bytes)}Those arguments have to be specified in the constructor of WOTSPLUS in addition to those parameters
specified in WOTS.MiscThe WOTS classes come with some features that will be explained in the following sections.Fully configurableThe WOTS classes are fully parameterizable. You can specify anything that is specified
in the papers describing the algorithm, including the Winternitz parameter, the hash function,
the pseudo random function (WOTSPLUS), the seed (WOTSPLUS), the private key and the public key.
specifing both a private key and public key results in the public key beeing discarded.On-demand generation of keysIf no private key or no public key is specified, they will be set to None. The same
goes for the seed in wots+. Only when they are required, they will be generated or
derived. This means that as long as you don’t executerepr(obj),str(obj),obj1 == obj2,obj1 != obj2,obj.pubkey,obj.privkey,obj.sign(...)orobj.verify(...), where obj is a
WOTS object, the keys will stay None.Code representation of WOTS objectsYou can callrepr(obj), where obj is a WOTS object, to get a line of code which contains
all information to initialize another object so that it is equal to obj. Executingobj2 = eval(repr(obj))executes that code which is returned byrepr(obj)and ultimately stores a copy of it inobj2.Human readable string representationYou can callstr(obj)to get a string which contains a human readable representation of that object.Comparison of objectsYou can compare two objects from this classobj1 == obj2andobj1 != obj2OptimizationsThe code was carefully written to reduce execution times. It surely is not perfect and can still be optimized,
further time-critical sections could be coded as C extensions, but nevertheless in the current state it should
offer quite an efficient implementation. It defines__slots__to reduce execution times and storage requirements
within the class. Implementation of parallelization is planned, but it is only usefull when using huge winternitz
parameters, since python can only execute code in parallel if you spawn a new process and the overhead of forking
a new python interpreter is not negliable.NoteThis project has been set up using PyScaffold 3.1. For details and usage
information on PyScaffold seehttps://pyscaffold.org/.