This is a recipe for adding the CROSS signature algorithm to post-quantum libraries (PQClean, liboqs, oqs-provider, oqs-demos, OQS-bind) starting from the NIST submission package.
- Get CROSS ready for PQClean
- Directory structure
- SHAKE
- Detached signatures
- Fixed size integers
- No variable-length arrays
- Remove assertions
- Unused parameters
- Missing prototypes
- Parameter sets
- Placeholders and code generation
- Namespacing
- Makefiles
- META.yml
- KATs and test vectors
- Parameter file
- Dead code removal
- No external includes in
api.h
- Astyle
- PQClean
- liboqs
- oqs-provider
- oqs-demos
- OQS-bind
- Create the
clean
directory and populate it with all the.c
and.h
files from the Reference Implementation, add also theLICENSE
file. - Create the
avx2
directory and populate it with all the.c
and.h
files from the Optimized Implementation.
Migrate the internal implementation of SHAKE used by CROSS to the one in PQClean/common
.
- Delete files:
fips202.c
fips202.h
keccakf1600.c
keccakf1600.h
- Delete the definition of
randombytes
fromcsprng_hash.h
. This function is called during key generation and signing to obtain salts and seeds. To make results like KATs reproducible it is substituted by the one defined in PQClean. - Delete the declaration of
platform_csprng_state
fromcsprng_hash.c
as it was only used by therandombytes
function. - Free the memory allocated by SHAKE (the implementation in PQClean uses dynamic memory allocation while the internal one in CROSS uses fixed sizes).
- In
sha3.h
define functioncsprng_release
simply callingxof_shake_release
which in turn wraps SHAKE'sinc_ctx_release
. - For every call to
initialize_csprng
wait forcsprng_randombytes
to be called (possibly more than once) and then addcsprng_release
to free up the memory.
- In
PQClean requires two additional functions crypto_sign_signature
and crypto_sign_verify
which compute signing and verification using detached signatures (the signature is returned separately from the message instead of appended to it). Declare them in api.h
and define them in sign.c
by simply wrapping the existing CROSS functions.
PQClean requires fixed sized integer types.
Replace | With |
---|---|
unsigned int |
uint32_t |
unsigned long |
uint32_t |
unsigned long long |
uint64_t |
The size of a message (mlen
and smlen
) becomes of type size_t
instead of uint64_t
.
PQClean requires the absence of variable length arrays: use a #define
placed before the function (instead of a normal variable definition) for the size of the array. This happens with:
- function
CROSS_verify
ofCROSS.c
. - function
compute_round_seeds
ofseedtree.c
. - all the functions of
csprng_hash.h
that use a buffer for the CSPRNG.
In CROSS.c
during signing and verification assert
is used multiple times to abort and display an error when something went wrong. This is useful for debugging but some tests in PQClean and liboqs require program termination, for example test_wrong_pk
tries to verify a signature using the wrong public key and checks that crypto_sign_open
returns -1
. The assertions must therefore be deleted.
PQClean is compiled with flags -Werror=unused-parameter
and Werror=unused-value
. The occurrences of unused parameters in CROSS can be eliminated by either changing the function definition and declaration or simply adding a line that uses that parameter, this happens with:
- Parameter
leaves
of functionmerkle_tree_proof_compute
inmerkle_tree.h
. - Parameter
inlen
of functiongeneric_unpack_fz
inpack_unpack.c
- Parameter
inlen
of functiongeneric_unpack_fq
in filepack_unpack.c
- Parameter
val
of functionxof_shake_init
insha3.h
PQClean is compiled with flag -Werror=missing-prototypes
, add the missing declarations in merkle_tree.h
.
A parameter set is an instance of the algorithm defined by all its possible parameters. CROSS has two variants (RSDP and RSPDG), three security levels (128, 192, 256 bits) and three "targets" (signature size, balanced, speed). In total there are 18 possible parameter sets. Each parameter set contains two implementations: the reference (clean) one and the avx2 optimized one. The script make_csv.py
uses Python's itertools
to generate all the possible combinations of parameters, which will be used as placeholders in a parameter set.
PQClean requires that every parameter set is placed in a separate directory. The script generate.py
will use CROSS's source files as templates, copying them in an appropriately named directory for each parameter set. After copying it will use text substitution to replace all the necessary parameters in the source files. For example, every time a source file needs to access the length of the public key the placeholder __length-public-key__
is used instead. The script will then substitute the placeholder with an actual value like 77 while generating the parameter sets.
PQClean requires "all exported symbols" to be prefixed with a string corresponding to the parameter set and implementation, for example function crypto_sign
becomes PQCLEAN_CROSSRSDP128BALANCED_CLEAN_crypto_sign
. Use the placeholder __namespace__
which will then be substituted when generating the parameter sets. PQClean has a handy test (test_symbol_namespace.py
) to check that you didn't forget to namespace something.
For PQClean's compilation two makefiles (make and NMAKE) are necessary in each parameter set's directory. Add CROSS's source files and compilation flags to Makefile
and Makefile.Microsoft_nmake
.
Every parameter set in PQClean also needs a file listing its parameters, the hashes for KATs and test vectors, and the implementations (clean and avx2). Placeholders are used again since META.yml
is also copied as a template by generate.py
. For liboqs an extra flag is added in required_flags
for the avx2 implementation.
TODO
Create a new file set.h
with placeholders for the parameters in a set, the definitions in here were previously done externally like in Benchmarking/CMakeLists.txt
. Include set.h
in parameters.h
and in the makefiles.
set.h
also undefines macros that are unused for a specific parameter set, this helps when removing dead code in generate.py
.
TODO: set.h also has defines for avx2
CROSS' source code makes heavy use of #ifdef
and similar preprocessor macros, for example to assign different values to constants in parameters.h
based on the security level. This behaviour is prohibited in PQClean, therefore when generating parameter sets generate.py
will remove lines of code corresponding to the dead branches of an #if
. This is achieved by calling the unifdef
utility, wich should be installed on the system. On a Debian-based system it's as simple as:
sudo apt install unifdef
PQClean has a handy test (test_preprocessor.py
) to check that all the preprocessor macros have been removed.
PQClean requires that the api file does not include any external file. Define parameters such as the length of the public key as placeholders, which will be substituted by actual values by generate.py
.
The last step of the code generation script generate.py
is running astyle usign the configuration file from the target library (e.g. generate/astyle/liboqs_astyle.ini
).
The goal of PQClean is to collect implementations of post-quantum key encapsulation mechanisms and signature schemes. The rules for contributing a new scheme are clearly stated in its README.md
and CONTRIBUTING.md
files. These rules are also enforced by an extensive test framework built using Python's pytest
and documented in the Wiki. After applying to CROSS all the changes just discussed the parameter sets can be generated using generate.py
and then tested in PQClean.
Run generate.py
to create a directory for each parameter set. The clean and avx2 implementations will be copied in it along with the META file. Then the placeholders will be substituted by the actual parameters taken from the csv file. The 18 directories can then be copied directly into PQClean's crypto-sign
.
Install all required Python packages from requirements.txt
then simply move inside the test folder and run the Python scripts. Here are some of the most essential tests:
test_nistkat.py
checks the consistency between the KATs produced by PQClean and the ones declared in theMETA.yml
files.test_functest.py
checks that the signatures work as expected, e.g. verifying a signed message with the wrong public key returns-1
meaning failure.test_symbol_namespace.py
checks that the all exported symbols are properly namespaced.
The aim of liboqs is to collect quantum-safe cryptographic algorithms (same as PQClean) and make them available and easy to use through a common API and benchmarking framework. The coding conventions are similar to PQClean, although less strict.
There are two methods for including a new scheme as documented by the Wiki: either add it directly or use the handy "copy-from-upstream" functionality (chosen here). After inclusion liboqs can be compiled and CROSS benchmarked against other signature schemes.
The idea is to edit a configuration file in liboqs, adding a git repository as an "upstream" from which the source code for the new scheme will be automatically pulled.
- Edit
copy_from_upstream.yml
:- Add CROSS's repo under
upstreams
with the last commit SHA. - Add a new entry for CROSS under
sigs
specifying every parameter set. The signature should be appended to the message usingsigned_msg_order: msg_then_sig
.
- Add CROSS's repo under
- Run
copy_from_upstream.py
, which will use the YAML file to download CROSS intosrc/sig
. - Update liboqs' documentation:
- Create
docs/algs/sig/cross.yml
to add information about the scheme in general (e.g. the website), and about each specific parameter set (e.g.length-secret-key
). - Run
update_docs_from_yaml.py
andupdate_cbom.py
to update the documentation files
- Create
TODO: the documentation is updated by copy_from_upstream.py
TODO: document valgrind suppression files
TODO: document Zephyr configuration
First install all dependencies listed in liboqs' README.md
. Then export the variable indicating the source folder and build with:
export LIBOQS_DIR=/path/to/liboqs
rm -rf build; mkdir build && cd build; cmake -GNinja ..; ninja
Move into liboqs/build/tests
and run the C executables. For example speed_sig -f
will measure the time and CPU cycles it takes to generate the keypair, sign, and verify.
Other tests are available in the liboqs/tests
directory, for example test_kat.py
will check the consistency of KATs.
openssl and liboqs TODO
replace liboqs cloning with fork TODO
- Edit
generate.yml
- Run
generate.py
- Run
generate_oid_nid_table.py
- Build
TODO
TODO
TODO
TODO
TODO
TODO:
- document the addition of keccak-x4 to CROSS (since it deviates from version 1.2 of the NIST submission package)
https://github.com/rtjk/CROSS-keccak-times4
- document the temporary fix for the CSPRNG buffer size in csprng_hash.h (since it deviates from version 1.2 of the NIST submission package)
- document the changes to architecture_detect.h that force optimizations when compiling for AVX2
- document: with nmake avx2 needs flag /std:c11
- document: removed x86intrin.h because it's not available on Windows
- document: renamed type sig_t to CROSS_sig_t to solve a name conflict in iOS
- document: cosmetic changes to make PQClean's linter happy