International Association for Cryptologic Research

International Association
for Cryptologic Research

Transactions on Cryptographic Hardware and Embedded Systems, Volume 2022

Quantum Period Finding against Symmetric Primitives in Practice


README

This artifact is a docker container running Ubuntu 20.04, which contains all of the code we used for the resource estimates in our paper. It was developed with Docker version 20.10.9.

Set-up

Navigate to offline-simon-docker. To build the docker image:

docker build . -t offline-simon-build

Then to create a new volume for the code, create a temporary container:

docker run -v offline-simon-vol:/app --name offline-simon-loader ubuntu:20.04 /bin/bash

then load the compressed data into the volume:

docker run --rm --volumes-from offline-simon-loader -v $(pwd)/backup:/backup ubuntu:20.04 bash -c "cd /app && tar xvf /backup/backup.tar --strip 1"

and clean up the temporary container:

docker container rm offline-simon-loader.

To start the container with all the code in the command line:

docker run --rm -it --name offline-simon-container -v offline-simon-vol:/app offline-simon-build

Within the container, navigate to /app, and call ./finish_installation.

Overview

There are two main components of the resource estimation:
- Q# code to compute circuit costs for the ciphers themselves and linear algebra, outputting them as .csv files (mainly in app/offline-quantum-period-finding/Simon)
- A Python script to assemble the results into the cost of the full attack and output them to the console (/app/offline-quantum-period-finding/attack_cost.py)

There is also the MicrosoftQuantumCrypto library, which contains some helper functions for the Q# estimation.

The docker container has pre-computed circuit cost outputs, so the python script will work immediately. Running ./recalculate_qsharp_estimates will delete the existing estimates and re-run the Q# code to produce new estimates for the individual block ciphers and linear algebra steps.

Source Code Organization and Outputs

/app/offline-quantum-period-finding/Simon

The C# code in Driver.cs starts a thread for each size of estimate and each cipher, and for the different sizes of rank calculation. Mainly this calls wrapper code in SimonResourceWrappers.qs. The code for the actual ciphers is in, e.g., Chaskey.qs, Prince.qs, and Elephant.qs, and the linear algebra code is in Matrix.qs.

The output is organized as follows:
- The Simon function for the three block ciphers, with no optimizations (saved to /FullCipherCosts/)
- The same block ciphers, with optimizations for different guess sizes (saved to /CipherCosts/)
- Grover iterations for key search (saved to /GroverCosts/)
- Computing the rank of the matrices needed for the offline Simon attack (saved to /RankCalculation/)

For each type of estimate, there is a T-depth calculation (where T-gates have depth 1 and all other gates have depth 0) and a full depth calculation (where all gates have depth 1), distinguished by the suffix "-all-gates". Common to both files are CNOT count, 1-qubit Clifford count (the count of H, X, S, Y gates), T count, M count (the count of measurements), Full width, max width, initial width, and extra width (all of which count the number of qubits, and the difference only appears for functions which are subroutines of other Q# functions. For our code, these will all display either the total number of qubits or 0). T depth and Full depth are the circuit depth under the two different metrics.

The last one or two columns are different:
- For FullCipherCosts, the last column size is a parameter of the scheme: For Chaskey, it's the number of rounds, for Elephant, it's the block size, and for PRINCE it's the block size.
- For CipherCosts, first arg is the same as size, while second arg is size of the input used for period finding (instead of amplitude amplification; see Figure 8 of the paper).
- For RankCalculation, first arg is the height and second arg is the width of the linear system being solved. second arg is always greater.
- For GroverCosts, first arg is same parameter of the cipher as before, while second arg is the number of repetitions of that cipher (which is not used in a Search-with-Two-Oracles approach)

/app/offline-quantum-period-finding/attack_cost.py

This python script will first output the costs of query-limited attacks, then it will print the costs of attacks with unlimited queries, and then the costs of an exhaustive key search with Grover's algorithm.

The outputs are formatted for a .tex table, in order as: Cipher name, size, number of queries, total gate count, T count, total depth, T depth, number of qubits. This is the same order as Table 2 in the paper.

/app/offline-quantum-period-finding/MicrosoftQuantumCrypto

This is a library of various cryptographic functions. Our project relies mainly on functions in Basics.qs, Arithmetic.qs, and ModularArithmetic.qs.

Customizations

Some of the main points you may want to customize:

Optimization strategies

The MicrosoftQuantumCrypto library can be built with any of three different optimization strategies:
dotnet build -c MinimizeT
dotnet build -c MinimizeDepth
dotnet build -c MinimizeWidth
These mainly affect which adders and which multi-input AND circuits are used.

The Simon resource estimator references these directly, so to use a different option, you will need to modify /app/offline-quantum-period-finding/Simon/ResourceEstimator.csproj. Specifically, lines including
..\MicrosoftQuantumCrypto\bin\MinimizeT\netcoreapp3.1\MicrosoftQuantumCrypto.dll
should have MinimizeT changed to one of the other options.

As well, Driver.cs makes a decision of which compiler strategy to use when allocating qubits: a depth-optimal or width-optimal. As of November 2020 there is a bug in the depth-optimal estimator (https://github.com/microsoft/qsharp-runtime/issues/419), so we opted to use a width-optimal strategy when optimizing for T count. This is is part of the GetTraceSimulator function.

The python script attack_cost.py will also need to have Q_SHARP_SUBFOLDER changed to "LowDepth" or "LowWidth" if you change to one of these other options.

Keccak

Because Pi, Rho, and Theta use a PLU decomposition, this must be created for every block size used. We only generated code for a block size of 200 bits. To change this, modify the matrixPLU.sage file. It has a hard-coded w which represents the length of a state. Modify this (e.g., w=64 for 1600-bit blocks), and run sage matrixPLU.sage and it will save new Q# code to PiRhoThetaRaw. This will require SageMath, and was originally run with version 8.1. As needed, replace the code in PiRhoTheta.qs.

Cost metrics

The script attack_cost.py chooses an optimal number of queries based on the minimum gate cost. This can be changed to optimize the depth*width cost: the global variableCOST_MODEL can be switched from G_COST to DW_COST. COST_METRIC can also be changed from ALL_GATES to T_GATES; however, during the main loops of calculation, it switches between cost metrics to compute both the T-cost and the cost of all gates. Thus, the lines from 403-418 and 447-455 would need to change in several places to switch to optimize for T-cost.

Rank Calculations

The attack_cost.py script will check the linear algebra calculations in /app/offline-quantum-period-finding/Simon/RankCalculation/ for the exact size of linear system it is trying to compute (where the sizes are in the first arg and second arg columns). If it finds the right size, it uses those numbers; if not, it estimates based on an asymptotic formula. If you want to run estimates that include different sizes of linear system, we recommend adding those sizes to the realWidths and realHeights arrays in the Driver.cs file, rather than relying on the asymptotic formulas.

The asymptotic formulas were estimated using calculations for matrices of random heights and widths. The EstimateRankCosts function in Driver.cs computed these. Currently that function is commented out, because it takes a long time to compute, but this is the function to use to verify our asymptotic formulas.

Alternate Cipher Sizes

Within the function EstimateCipherCosts of Driver.cs, the {cipherName}GuessSizes arrays contain the size of the input message that is part of the period-finding subtroutine, rather than the amplitude amplification. There are two such sizes for each parameter of the cipher (e.g., 2 sizes for each length of Elephant). Since this gives the number of chosen plaintext queries the adversary must make, the first represents a query limit fixed by the cipher's designers, and the second represents the optimal number of queries that we found with the python script.

The python script will try to find a cipher cost specifically computed for the number of queries that it tests, and if not, it will just take the last cipher cost estimate in the csv file it checks. Hence, if you modify the script so that it chooses a different optimal number of queries, you should probably add the new number to the array of {cipherName}GuessSizes and compute a new estimate for it. The difference in cost comes from how the cipher-specific optimizations can be used (e.g., not computing all of the output bits).