Co-authored-by: Marius A. Aardal <marius.andre.aardal@gmail.com> Co-authored-by: Gora Adj <gora.adj@tii.ae> Co-authored-by: Diego F. Aranha <dfaranha@cs.au.dk> Co-authored-by: Andrea Basso <sqisign@andreabasso.com> Co-authored-by: Isaac Andrés Canales Martínez <icanalesm0500@gmail.com> Co-authored-by: Jorge Chávez-Saab <jorgechavezsaab@gmail.com> Co-authored-by: Maria Corte-Real Santos <mariascrsantos98@gmail.com> Co-authored-by: Luca De Feo <github@defeo.lu> Co-authored-by: Max Duparc <max.duparc@epfl.ch> Co-authored-by: Jonathan Komada Eriksen <jonathan.eriksen97@gmail.com> Co-authored-by: Décio Luiz Gazzoni Filho <decio@decpp.net> Co-authored-by: Basil Hess <bhe@zurich.ibm.com> Co-authored-by: Antonin Leroux <antonin.leroux@polytechnique.org> Co-authored-by: Patrick Longa <plonga@microsoft.com> Co-authored-by: Luciano Maino <mainoluciano.96@gmail.com> Co-authored-by: Michael Meyer <michael@random-oracles.org> Co-authored-by: Hiroshi Onuki <onuki@mist.i.u-tokyo.ac.jp> Co-authored-by: Lorenz Panny <lorenz@yx7.cc> Co-authored-by: Giacomo Pope <giacomopope@gmail.com> Co-authored-by: Krijn Reijnders <reijnderskrijn@gmail.com> Co-authored-by: Damien Robert <damien.robert@inria.fr> Co-authored-by: Francisco Rodríguez-Henriquez <francisco.rodriguez@tii.ae> Co-authored-by: Sina Schaeffler <sschaeffle@student.ethz.ch> Co-authored-by: Benjamin Wesolowski <benjamin.wesolowski@ens-lyon.fr>
12 KiB
Developer guidelines
Please read carefully before contributing to this repo.
Code structure
The source code is in the src/ directory and it is split into the
following modules:
common: common code for AES, SHAKE, (P)RNG, memory handling. Every module that needs a hash function, seed expansion, deterministic alea for tests, should call to this module.mp: code for saturated-representation multiprecision arithmetic.gf: GF(p^2) and GF(p) arithmetic.ec: elliptic curves, isogenies and pairings. Everything that is purely finite-fieldy.precomp: constants and precomputed values.quaternion: quaternion orders and ideals.hd: code to compute (2,2)-isogenies in the theta model.id2iso: code for Iso <-> Ideal.verification: code for the verification protocol.signature: code for the key generation and signature protocols.
Contents of a module
Each module is comprised of implementation types and common code. An
implementation type refers to the portable optimized, portable reference or
any architecture-specific implementation of the module; a module must contain at
least one implementation type. Each implementation type must be in its own
directory within the directory of the module. The optimized and reference
implementation types must be placed in the opt and ref directories,
respectively; there is no rule for naming other architecture-specific
implementations. Common code refers to optional generic code that is shared
among all implementation types. Common code is placed in special directories
within the directory of a module: header files in the include directory and
source files in the <module_name>x directory, where <module_name> is the
name of the module. An example of a module is given below:
src
└── <module_name>
├── include
├── <module_name>x
├── opt
├── ref
└── <arch>
where:
<module_name>is the name of the module.optandrefare the portable optimized and reference implementation types, respectively.<arch>is an optional architecture-specific implementation type of the module (e.g.,broadwellfor code using assembly instructions specific to the Broadwell platform).includecontains header files common to all implementation types.<module_name>xcontains source files common to all implementation types (i.e.,opt,refand<arch>).
Header files in the include directory above can be included by other modules
and must contain extensive doxygen-formatted documentation describing the
functions declared there; see Documentation. Any
implementation-type directory above is allowed to be a symlink; e.g., if a
module has no separate optimized and reference implementation, then
opt can be a symlink to ref.
Similar to a module, each implementation type is comprised of implementation
variants and common code. A variant refers to either a generic
implementation, an implementation whose parameters are defined by one of the
NIST levels (i.e., 1, 3 or 5) or a variation of the latter. An implementation
type must contain at least one variant. Each variant must be in its own
directory within that of the implementation type. The generic variant must be
placed in the generic directory and variants corresponding to NIST levels 1,
3 and 5 are placed in the directories lvl1, lvl3 and lvl5, respectively;
there is no rule for naming the directory of a NIST variation, but
implementors are encouraged to choose informative namings. Common code refers to
optional variant-independent code that is shared among all variants of the same
implementation type. Common code is placed in special directories within that of
the implementation type: header files in the include directory and source
files in the lvlx directory. Expanding on the example above, we show the
details of its implementation types:
src
└── <module_name>
├── include
├── <module_name>x
├── opt
│ ├── include
│ ├── lvlx
│ ├── lvl1
│ ├── lvl1_var1
│ ├── lvl3
│ └── lvl5
├── ref
│ ├── generic
│ └── lvl1
└── <arch>
├── include
├── lvlx
├── lvl3
└── lvl5
where:
lvl1,lvl3,lvl5are implementations of NIST levels 1, 3 and 5, respectively, for the corresponding implementation type.lvl1_var1is a variation oflvl1for theoptimplementation type (e.g., using a different prime characteristic).opt/includecontains header files common to all variants in theoptimplementation type (i.e.,lvl1,lvl1_var1,lvl3andlvl5). Similarly,<arch>/includefor all variants in the<arch>implementation type (i.e.,lvl3andlvl5).opt/lvlxcontains source files common to all variants in theoptimplementation type. Similarly,<arch>/lvlxfor all variants in the<arch>implementation type.genericcontains a parameter-independent implementation of therefimplementation type.
As the name suggests, the generic variant is a generic implementation which
does not depend on the parameters defined by the NIST levels or any variation
of these. If this directory is present, all other parameter-dependent
implementations are ignored and the generic implementation is built instead.
As with modules, header files in the include directory of an implementation
type (e.g., opt/include and <arch>/include above) can be included by other
modules and must contain extensive doxygen-formatted documentation describing
the functions declared there.
Each implementation variant must be organized as follows:
- Header files that can be included by other modules are placed in the
includedirectory. These files must contain extensive doxygen-formatted documentation describing the functions declared there. - Source files of the implementation and their private internal header files are placed directly in the implementation variant directory.
- Source files of unit tests and their private internal header files are placed
in the
testdirectory. Refer to Tests for instructions on how to write these.
Common code (in lvlx) for all variants in an implementation type follows the
same organization as above, with the exception that lvlx never contains an
include directory. This role is taken by the include directory in the
implementation type. Below is an example with the detailed organization of the
common code and the lvl1 variant for the ref implementation type of a
module:
<module_name>
├──ref
│ ├── include
│ │ └── header_ref.h
│ ├──lvlx
│ │ ├── test
│ │ │ ├── test_internal_header_ref.h
│ │ │ │ ...
│ │ │ ├── test1_ref.c
│ │ │ └── test2_ref.c
│ │ ├── internal_header_ref.h
│ │ ├── source1_ref.c
│ │ └── source2_ref.c
│ ├──lvl1
│ │ ├── include
│ │ │ └── header_ref_lvl1.h
│ │ ├── test
│ │ │ ├── test_internal_header_ref_lvl1.h
│ │ │ │ ...
│ │ │ ├── test1_ref_lvl1.c
│ │ │ └── test2_ref_lvl1.c
│ │ ├── internal_header_ref_lvl1.h
│ │ ├── source1_ref_lvl1.c
│ │ └── source2_ref_lvl1.c
│ ├──lvl3
│ └──lvl5
Finally, common code for a module must be organized as follows:
- Header files that can be included by other modules are placed in the
includedirectory. As mentionde before, these files must contain extensive doxygen-formatted documentation describing the functions declared there. - Source files and their private internal header files are placed in the
<module_name>xdirectory. - Source files of unit tests and their private internal header files are placed
in the
<module_name>x/testdirectory. Again, refer to Tests for instructions on how to write these.
The example below shows the detailed organization of the common code of a module:
<module_name>
├── include
│ └── header.h
├── <module_name>x
│ ├── test
│ │ ├── test_internal_header.h
│ │ │ ...
│ │ ├── test1.c
│ │ └── test2.c
│ ├── internal_header.h
│ ├── source1.c
│ └── source2.c
├── opt
└── ref
Tests
It is important to have extensive test coverage of the whole software. Each module must have its own unit tests, as well as integration tests to ensure consistency across the modules.
Unit tests
These go in the src/<module_name>/<module_name>x/test and
src/<module_name>/<ref|opt|...>/<generic|lvlx|lvl1|...>/test/ directories.
Refer to src/gf/gfx/test/test_fp.c for an example
of how to write tests.
Integration tests
These go in the test/ directory. Refer to
test/test_sqisign.c for an example.
Known Answer Tests (KAT)
KATs help validate consistency across implementations. By ensuring
that, e.g., the optimized and reference implementation produce the
same signatures. KATs are generated by executing PQCgenKAT_sign_<level> in
the apps directory. KAT tests go in the test/ directory.
Benchmarks
Benchmarks for a module go in the same directories as for tests.
Global benchmarks go in the apps directory; e.g.,
apps/benchmark.c.
Documentation
Use Doxygen headers for documentation.
All code should be extensively documented. The public module headers MUST be thoroughly documented.
CI automatically builds a PDF of the doc every time code is pushed. To download the PDF, go to Actions, click on the workflow run you're interested in, then go to Artifacts -> docs (see figure). PDFs are retained for 2 days.
Branches and pull requests
Always work on topic branches, never push work in progress on the
main branch. Once a task / issue / work unit is completed, create a
pull-request and ask for at least one review.
Coding style
-
C version: All code must compile cleanly as C11, without emitting any warnings, using recent versions of GCC and clang.
-
Names: Externally visible functions and types should be prefixed with the name of the module they belong to.
-
Aliases: Do use
typedefwith descriptive names whenever it makes any nonzero amount of sense to do so. Avoidtypedefs for things other thanstructs (or elementary data types); they have a tendency to break for array and pointer types if programmers are not aware of the nature of the underlying type. -
Parameters: Output arguments, if any, should always come first. Input arguments should generally be marked
const. Objects of types which typically fit into registers should be passed and returned by value, larger objects by reference (i.e., as a pointer). If certain arguments often appear together, it may be an indication that they should be wrapped as astruct. -
Global variables: Global constants are acceptable if needed, especially within modules whose code already implicitly relies on the same constants anyway (primary example: 𝔽ₚ). It is often a good idea to group global constants in a meaningful
structand write the code such that the struct could easily be replaced by a runtime variable at a later point. Global state (modifiable global variables), on the other hand, is strictly forbidden. -
Formatting: This project uses
clang-formatto format the code. From the root of the project run the following command:find ./src -path ./src/precomp -prune -type f -o -iname '*.h' -o -iname '*.c' | xargs clang-format -ito automatically format all appropriate files with
clang-format.If you want, you can install a pre-commit hook to ensure that your work is correctly formatted before pushing
pre-commit installWill use the
.pre-commit-config.yamlfile.
