International Association for Cryptologic Research

International Association
for Cryptologic Research

Transactions on Cryptographic Hardware and Embedded Systems 2025

Constant time lattice reduction in dimension 4 with application to SQIsign


README

CTLLL-SQIsign

Constant-time lattice reduction for SQIsign

This is the code and data for our paper on the same topic.

We will first explain how to use the present code, then we will detail the structure of the codebase.


Origins of the code

Many parts of this code are adapted from the NIST submission of SQIsign.

In particular the file src/lll/lll.c is directly taken from that implementation. Furthermore, almost all quaternion-related functions in src/ct_helpers/ct_helpers.c, src/test_helpers/nct_helpers.c and src/test_helpers/test_helpers.c are taken over from SQIsign, and the src/nct_intbig module is obtained from SQIsign's intbig module by just renaming the functions to add the nct_ prefix. Similarly, src/nct_lll/lll.c is an adaptation of SQIsign's lll.c file to use the nct_ibz functions. The constant-time gcd is implemented using Bernstein and Yang's constant-time xgcd from 2019.

The code in src/bkz, as well as most test code at the root of the src folder and all code outside of the src folder was newly written for this project.


Structure of this repository

This folder contains several subfolders. Most of these contain a separate README with details their content and use.

There are three folders with data and scripts from our experiments. Each of them contains a README giving more detail on its content.

The root folder contains, besides the above folders:


Prerequisites and first step

Prerequisites

First steps

Type make test. Wait a few seconds, and check if the tests pass.


Build and execute

First, create a directory called build in this folder if it does not yet exist.

There are 4 main executables which can be built and run:

Small_tests

Runs some fixed and not randomized tests on submodules

Build and run it with make small_test.

This is the only executable which can be run without using inputs.h, but it does require setting LIMBNUM and the scratch space in limbnum.h, and the constants in lll_constants.c.

Test

Runs tests ensuring that BKZ reduces all lattices in the inputs.h file.

The tests verify that the bound from Minkowski's second theorem and the bound on the shortest vector are respected. Furthermore, it is verified that the output is a basis of the same lattice then the input.

Compile and run it with make test

Requires inputs.h (with corresponding inputs.cor datafile), bkz_constants.h, lll_constants.c and limbnum.hto be generated correctly and consistently.

If DATA_FILE and LATTICE_DATA_NUMBER are defined in inputs.h, it the script will be compiled so that it uses these. The lattices in the data file are assumed to be integral ideals of the given quaternion algebra. If one of these macros is undefined, the compiler will check if LATTICE_NUMBER is defined and use the lattices from LATTICE_LIST. To be run on LLL instead or in addition to BKZ, the BKZ_TEST and LLL_TEST can be used as described above.

Benchmark

Runs benchmarks on BKZ and LLL, with a fixed number of iterations on each lattice from input.h.

Compile and run it with make bench

Requires inputs.h (with corresponding inputs.cor datafile), bkz_constants.h (with BENCHMARK_ITERATIONS), lll_constants.c and limbnum.hto be generated correctly and consistently.

If DATA_FILE and LATTICE_DATA_NUMBER are defined in inputs.h, it the script will be compiled so that it uses these, otherwise it will check if LATTICE_NUMBER is defined and use the lattices from LATTICE_LIST. To be run on LLL instead or in addition to BKZ, the BKZ_BENCH and LLL_BENCH can be used as described above.

Run

Runs BKZ once on each lattice in input.h. This should always run in the same time independently of the lattices in inputs.h, as long as the variables QUATALG_P, LATTICE_NUMBER and LIMBNUM are unchanged.

Compile and run it with make run. You can also compile it using make and run it using ./build/run.

Requires inputs.h (with corresponding inputs.cor datafile), bkz_constants.h and limbnum.h to be generated correctly and consistently.

If DATA_FILE and LATTICE_DATA_NUMBER are defined in inputs.h, it the script will be compiled so that it uses these. Reading from the file is however not guaranteed to run in file-content-independent time. Otherwise it will check if LATTICE_NUMBER is defined and use the lattices from LATTICE_LIST.

Clean

To remove all compilation files, use make clean.


Additional make targets for our measures

For the measures reported in the paper, some additional targets can be build and run.

Test, benchmark, write to file

This is only available if DATA_FILE and LATTICE_DATA_NUMBER are defined in inputs.h. It runs tests on NTESTS lattices in the file, where NTESTScan be defined in inputs.h and otherwise defaults to 7. After this, benchmarks are run on LATTICE_DATA_NUMBER lattices in the datafile, and the results for each lattice are written to a file named like the datafile prefixed with bench_. Our post-processing script post_processor.py can be run on this output and the datafile to produce a .csv file for RLFT.

Compile and run it with make bench_stats.

In case the LLL implementation from SQIsign's NIST submission and not our BKZ should be benchmarked, run make bench_stats_lll instead.

Quality test: Test with verbous output

make quality_test compiles and runs the tests for Minkowski reduction and for the length of the shortest vector. It does not only report the result, but also prints both sides of the inequalities it tests. This is used in out quality tests.

In order to get this verbous output from noral tests, it is sufficient to compile them with the additional flag -DPRINT_FLAG=1.

ctgrind

make bench_ctgrind will compile and run BKZ-2 on the first lattice in the used datafile while using ctgind (that is, valgrind with the input lattice declared as uninitialized memory). Be aware that this requires to have Valgrind installed, which is not possible on ARM Macs.

bench_nct_lll

make bench_nct_lll will compile and run benchmarks for the LLL implementation in nct_intbig_lll.


Different inputs for the tests

Use inputs from the data folders

The folders RTLF-data, quality-tests and benchmark-inputs provide the inputs we used in our tests for running respectively RTLF, our tests on BKZ quality for low iteration counts, and the benchmarks. Their use is described in the READMEs of these subfolders.

Automatic input generation using sage

We provide a sage script create_ideals.sage to generate (lattices representing) random ideals of a specific order (called O0 in all papers on SQIsign), with norm in a given range. The given basis is the HNF also used in the SQIsign round 1 NIST submission.
To get help on the inputs it requires, use the -h argument. To generate a data file instead of a lattice list in the inputs.c file, run the script with an additional argument -data. The name of the datafile can be set by using datafile=<name> as last argument, it is data_file by default.

A few notes on some of the arguments (in order here):

The toy example in the datafile p335_nl11_1_small used by the default src/inputs.h was obtained by running sage create_ideals.sage 33506587778976371636384290201141387 12890707586852862 187181532229268607942 9 11 10 6 -data datafile=p335_nl11_1_small.

Automatic input from SQIsign prints

The script parse_output.py (not requiring sage, only python3) allows to obtain input lattices from outputs printed by a run of the SQIsign round 1 reference implementation (which must be slightly modified to obtain these printouts). How to obtain a suitable printout is explained in the comments at the beginning of parse_output.py. The proposed modification to the NIST 1 SQIsign submission code will print all lattices encountered in keygen and one signing run.
An example of such a trace is given with the file sqisign_lvl1_lll_calls_trace. The default are BKZ and Lagrange tour counts of 30 and 10, these can however be adjusted in the script. Similar printouts (calling the same print functions at the beginning of the respective LLL implementation) can be used to achieve the same result on most other SQIsign C implementations (SQIsignHD, SQIsign2d-West and the round 2 NIST submission for example).

Once this output file is obtained, the script parse_output.py can be called. It requires the following inputs (in this order):

If less arguments (1, 2 or 4) are given, these are taken as the first (1, 2 or 4) of the above arguments, and defaults defined in the first 5 lines of the script are used for the others. These defaults are acceptable for the NIST round 1 implementation at level 1, even though the iteration counts are neither minimal nor above the proven bounds.

If one wants to generate and use a datafile instead of a lattice list in a C file (this is required for some of the make targets), run the script with an additional argument -data. The name of the datafile can be set by using datafile=<name> as last argument, it is data_file by default.

The script will then take care of setting the relevant constants in the src folder accordingly, so that recompiling using make will yield executables using the given list of lattices as input.

Input generation by hand or using parts of our scripts

If further customization of the inputs is needed, please check the README of the src folder.