cpp-hsslms
This is an implementation of Leighton-Micali Hash-Based Signatures in C++ according to RFC 8554. It is a stateful hash-based signature scheme, see also NIST.
The implementation is meant as a reference and for educational purposes. A similar implementation in Python can be found here.
The implementation provides 4 classes:
- LM-OTS One-Time Signatures, i.e.
LM_OTS_Priv
,LM_OTS_Pub
. These are one-time signatures; each private key MUST be used at most one time to sign a message. - Leighton-Micali Signatures (LMS), i.e.
LMS_Priv
,LMS_Pub
. This system holds a fixed number of one-time signatures, i.e. LM-OTS. - Hierarchical Signatures (HSS), i.e.
HSS_Priv
,HSS_Pub
. This system uses a sequence of LMS. - Persistent Hierarchical Signatures, i.e.
PersHSS_Priv
. A child ofHSS_Priv
where the private key is stored in an encrypted file.
The only dependency is OpenSSL for key derivation PKCS5_PBKDF2_HMAC, random number generation RAND_priv_bytes, hashing SHA-256 and encryption with autentication AES-GCM. If the class PersHSS_Priv
is not needed the only dependency to be fulfilled is for random number generation and hashing.
The projects compiles to an executable hbslms
with a command line interface.
>> hbslms -h
Hierarchical Signature System of Leighton-Micali Hash-Based Signatures according to RFC 8554
Returns 0 if a command is executed without error and if a verification succeeded.
optional arguments:
-h show this help message and exit
possible commands:
-a {key-gen,sign,verify,test,performance}
command -a key-gen:
generates a key pair arguments:
-l [1234][56789]+ one LMOTS-typecode and at least one LMS-typecode
-o filename filename of private key, ".pub" is appended to the filename of the pubklic key
-p password password to encrypt the private key
-c number number of cpu cores (1-#logical cores) for computation
command -a pubkey-gen:
generates the public key from a private key arguments:
-k filename filename of private key
-p password password to encrypt the private key
-o filename filename of the public key
command -a sign:
generates a signature arguments:
-k filename filename of private key
-m filename filename of message to be read and signed
-o filename filename of signature to be stored
-p password password to encrypt the private key
-c number number of cpu cores (1-#logical cores) for computation
command -a verify:
verifies a signature arguments:
-k filename filename of public key
-m filename filename of message to be read
-s filename filename of signature to be read
command -a test:
performs a number of tests.
command -a performance:
does some performance measurements arguments:
-c number number of cpu cores (1-#logical cores) for computation
Compilation
cmake --build "target directory/" --target all
Example Usage of the Command Line Interface
If the return code of the executable hsslms
is not 0 an error occurred.
Key Generation
The following command computes a key pair with with LMOTS algorithm type LMOTS_SHA256_N32_W8 and LMS algorithms types LMS_SHA256_M32_H10, LMS_SHA256_M32_H5. The private key is stored to testkey
the public key to testkey.pub
. The private key ist protected by the password password
. The computation uses 2 threads.
hbslms -l 465 -k testkey -p password -c 2
Signature Generation
The following command computes a signature with the private key testkey
for the message message.bin
. The signature ist stored to signature.bin
.
hbslms -k testkey -p password -m message.bin -o signature.bin -c 2
Signature Verification
The signature generated by the last command can be verified by:
hbslms -k testkey.pub -m message.bin -s signature.bin
Example Usage in C++
LM-OTS
#include <cstring>
#include "lmots.h"
int main(int argc, char *argv[]) {
std::array<uint8_t, 16> I {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
LM_OTS_Priv sk = LM_OTS_Priv(LMOTS_SHA256_N32_W1, I, 99);
std::string signature = sk.sign("abc");
LM_OTS_Pub vk = sk.gen_pub();
try {
vk.verify("abc", signature);
std::cout << "Vaild (-:" << std::endl;
}
catch (INVALID &e) {
std::cout << "Invaild )-: " << e.what() << std::endl;
}
}
LMS
#include <cstring>
#include "lms.h"
int main(int argc, char *argv[]) {
LMS_Priv sk_lms = LMS_Priv(LMS_SHA256_M32_H5, LMOTS_SHA256_N32_W8, NUM_THREADS);
signature = sk_lms.sign("abc");
LMS_Pub vk_lms = sk_lms.gen_pub();
try {
vk_lms.verify("abc", signature);
std::cout << "Vaild (-:" << std::endl;
}
catch (INVALID &e) {
std::cout << "Invaild )-: " << e.what() << std::endl;
}
}
HSS
#include <cstring>
#include "hss.h"
int main(int argc, char *argv[]) {
HSS_Priv sk_hss = HSS_Priv(std::vector<LMS_ALGORITHM_TYPE>{LMS_SHA256_M32_H15, LMS_SHA256_M32_H15}, LMOTS_SHA256_N32_W8, NUM_THREADS);
signature = sk_hss.sign("abc");
HSS_Pub vk_hss = sk_hss.gen_pub();
try {
vk_hss.verify("abc", signature);
std::cout << "Vaild (-:" << std::endl;
}
catch (INVALID &e) {
std::cout << "Invaild )-: " << e.what() << std::endl;
}
}
Performance Measurements
The measurements are done on a Ryzen 5800X, where multiprocessing features are used with 6 cores.
Key Generation
Key-Type | Time[s] | #Signatures | Size of Signature | ||||||
---|---|---|---|---|---|---|---|---|---|
w | 1 | 2 | 4 | 8 | 1 | 2 | 4 | 8 | |
H5 | 0.001 | 0.002 | 0.001 | 0.003 | 32 | 8688 | 4464 | 2352 | 1296 |
H10 | 0.01 | 0.01 | 0.02 | 0.1 | 1024 | 8848 | 4624 | 2512 | 1456 |
H15 | 0.2 | 0.2 | 0.3 | 2.5 | 32768 | 9008 | 4784 | 2672 | 1616 |
H20 | 6.3 | 5.7 | 10.9 | 82 | 1048576 | 9168 | 4944 | 2832 | 1776 |
H25 | 2593 | 33554432 | 1936 | ||||||
H10/H10 | 0.02 | 0.02 | 0.03 | 0.2 | 1048576 | 17748 | 9300 | 5076 | 2964 |
H10/H15 | 0.2 | 0.2 | 0.3 | 2.7 | 33554432 | 17908 | 9460 | 5236 | 3124 |
H15/H15 | 0.4 | 0.4 | 0.7 | 5.1 | 1073741824 | 18068 | 9620 | 5396 | 3284 |
Performance of Signature Generation:
Key-Type | Time[ms] | |||
---|---|---|---|---|
w | 1 | 2 | 4 | 8 |
H15 | 0.01 | 0.02 | 0.03 | 0.2 |
Performance of Signature Verification:
Key-Type | Time[ms] | |||
---|---|---|---|---|
w | 1 | 2 | 4 | 8 |
H15 | 0.02 | 0.0 | 0.03 | 0.2 |