Skip to content

Gidney 2025 #55

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Aug 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified assets/qtt-output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/rsa-threat-over-time.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
82 changes: 53 additions & 29 deletions notebooks/development/demo_plots.ipynb

Large diffs are not rendered by default.

1,541 changes: 1,541 additions & 0 deletions notebooks/development/gidney_basic.ipynb

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions src/quantumthreattracker/algorithms/algorithm_lister.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
CryptParams,
QuantumAlgorithm,
)
from .rsa.gidney_basic import GidneyBasic
from .rsa.gidney_ekera import GidneyEkera


Expand Down Expand Up @@ -38,9 +39,12 @@ def list_algorithms(
If the protocol is not recognized.
"""
if crypt_params.protocol == "RSA":
algorithms = [GidneyEkera(crypt_params)]
algorithms = [GidneyBasic(crypt_params), GidneyEkera(crypt_params)]
elif crypt_params.protocol == "DH-SP":
algorithms = [DLogSafePrimeEH(crypt_params), DLogSafePrimeShor(crypt_params)]
algorithms = [
DLogSafePrimeEH(crypt_params),
DLogSafePrimeShor(crypt_params),
]
elif crypt_params.protocol == "DH-SCH":
algorithms = [DLogSchnorrEH(crypt_params), DLogSchnorrShor(crypt_params)]
elif crypt_params.protocol == "ECDH":
Expand Down
19 changes: 11 additions & 8 deletions src/quantumthreattracker/algorithms/rsa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

from .baseline_shor import BaselineShor, BaselineShorParams
from .chevignard import Chevignard, ChevignardParams
from .gidney_basic import GidneyBasic, GidneyBasicParams
from .gidney_ekera import GidneyEkera, GidneyEkeraParams
from .gidney_ekera_basic import GidneyEkeraBasic, GidneyEkeraBasicParams

__all__ = [
'BaselineShor',
'BaselineShorParams',
'Chevignard',
'ChevignardParams',
'GidneyEkera',
'GidneyEkeraBasic',
'GidneyEkeraBasicParams',
'GidneyEkeraParams',
"BaselineShor",
"BaselineShorParams",
"Chevignard",
"ChevignardParams",
"GidneyBasic",
"GidneyBasicParams",
"GidneyEkera",
"GidneyEkeraBasic",
"GidneyEkeraBasicParams",
"GidneyEkeraParams",
]
121 changes: 121 additions & 0 deletions src/quantumthreattracker/algorithms/rsa/gidney_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"""Class for a parameterised implementation of Gidney (2025)'s implementation.

[1] https://arxiv.org/abs/2505.15917
"""

from dataclasses import dataclass
from typing import Optional

from qualtran.resource_counting import GateCounts
from qualtran.surface_code import AlgorithmSummary

from quantumthreattracker.algorithms.quantum_algorithm import (
AlgParams,
CryptParams,
QuantumAlgorithm,
)


@dataclass
class GidneyBasicParams(AlgParams):
"""(No tunable parameters for this basic implementation; placeholder.)."""

pass


class GidneyBasic(QuantumAlgorithm):
"""Class for a basic implementation of Gidney."""

def __init__(
self,
crypt_params: CryptParams,
alg_params: Optional[GidneyBasicParams] = None,
):
"""Initialize the quantum algorithm.

Parameters
----------
crypt_params : CryptParams
Cryptographic parameters.
alg_params : Optional[GidneyEkeraBasicParams], optional
Copy link
Preview

Copilot AI Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring references 'GidneyEkeraBasicParams' but should reference 'GidneyBasicParams' to match the actual parameter type.

Suggested change
alg_params : Optional[GidneyEkeraBasicParams], optional
alg_params : Optional[GidneyBasicParams], optional

Copilot uses AI. Check for mistakes.

Algorithmic parameters. For the basic implementation, these have no effect
but are included for API consistency.
"""
super().__init__(crypt_params, alg_params)

def get_algorithm_summary(
self, alg_params: Optional[AlgParams] = None
) -> AlgorithmSummary:
"""Compute logical resource estimates for the circuit.

Parameters
----------
alg_params : Optional[AlgParams], optional
Algorithm parameters (not used in this basic implementation)

Returns
-------
AlgorithmSummary
Logical resource estimates.

Raises
------
NameError
If the protocol is not "RSA".
ValueError
If the key_size is not supported.
"""
if self._crypt_params.protocol != "RSA":
raise NameError(
'The protocol for this class must be "RSA". '
+ f'"{self._crypt_params.protocol}" was given.'
)

key_size = self._crypt_params.key_size

# Table mapping key_size (n) to Toffolis and Qubits
resource_table = {
1024: {"qubit_count": 742, "toffoli_count": 1.1e9},
1536: {"qubit_count": 1074, "toffoli_count": 3.1e9},
2048: {"qubit_count": 1399, "toffoli_count": 6.5e9},
3072: {"qubit_count": 2043, "toffoli_count": 1.9e10},
4096: {"qubit_count": 2692, "toffoli_count": 4.0e10},
6144: {"qubit_count": 3978, "toffoli_count": 1.2e11},
8192: {"qubit_count": 5261, "toffoli_count": 2.7e11},
}

sorted_keys = sorted(resource_table.keys())
key_size_rounded_up = None
for k in sorted_keys:
if key_size <= k:
key_size_rounded_up = k
break

if key_size_rounded_up is None:
raise ValueError(
f"Unsupported key_size: {key_size} exceeds maximum supported ({sorted_keys[-1]})"
)

key_size = key_size_rounded_up

qubits = int(resource_table[key_size]["qubit_count"])
toffolis = int(resource_table[key_size]["toffoli_count"])

return AlgorithmSummary(
n_algo_qubits=int(qubits),
n_logical_gates=GateCounts(toffoli=toffolis),
)

@staticmethod
def generate_search_space() -> list[GidneyBasicParams]:
"""Generate a search space for algorithm parameters.

Since GidneyBasic doesn't have configurable parameters, this returns
a list with a single set of default parameters.

Returns
-------
list[GidneyBasicParams]
Single-element list containing default parameters.
"""
return [GidneyBasicParams()]
39 changes: 25 additions & 14 deletions src/quantumthreattracker/lifespan_estimator/lifespan_estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from datetime import datetime
from pathlib import Path

import matplotlib.dates as mdates
import matplotlib.pyplot as plt
from matplotlib.axes import Axes
from qsharp.estimator import EstimatorError
Expand Down Expand Up @@ -225,30 +226,44 @@ def plot_threats(self, protocol: str | None = None) -> Axes:
timestamps = []
runtimes = []

ax = plt.subplot(111)
ax.set_yscale("log")
ax.set_xlabel("Timestamp")
ax.set_ylabel("Algorithm runtime (hours)")
ax.xaxis.set_major_locator(mdates.YearLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y"))

if protocol is not None:
report = self.get_report(detail_level=1)
report = [entry for entry in report if entry["protocol"] == protocol]
threats = report[0]["threats"]

# Remove threats that are dominated by other threats
for threat in threats:
for alt_threat in threats:
# Pareto frontier: keep only threats not dominated by another
# (sooner & shorter runtime)
filtered_threats = []
for i, t1 in enumerate(threats):
dominated = False
for j, t2 in enumerate(threats):
if j == i:
continue
if (
threat["timestamp"] >= alt_threat["timestamp"]
and threat["runtime"] >= alt_threat["runtime"]
t2["timestamp"] <= t1["timestamp"]
and t2["runtime"] <= t1["runtime"]
) and (
t2["timestamp"] < t1["timestamp"]
or t2["runtime"] < t1["runtime"]
):
threats.remove(threat)
dominated = True
break
Copy link
Preview

Copilot AI Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The break statement exits the inner loop prematurely when a dominating threat is found, but doesn't prevent checking against remaining threats. This could incorrectly mark a threat as non-dominated when a later threat actually dominates it.

Copilot uses AI. Check for mistakes.

if not dominated:
filtered_threats.append(t1)
threats = filtered_threats

for entry in threats:
timestamps.append(datetime.fromtimestamp(entry["timestamp"]))
runtimes.append(entry["runtime"] / 3.6e12)

ax = plt.subplot(111)
ax.plot(timestamps, runtimes, "o--")
ax.set_yscale("log")
ax.set_xlabel("Timestamp")
ax.set_ylabel("Algorithm runtime (hours)")
ax.set_title("Threats against " + protocol)
return ax

Expand All @@ -258,13 +273,9 @@ def plot_threats(self, protocol: str | None = None) -> Axes:
timestamps.append(datetime.fromtimestamp(entry["threats"][0]["timestamp"]))
runtimes.append(entry["threats"][0]["runtime"] / 3.6e12)

ax = plt.subplot(111)
ax.scatter(timestamps, runtimes)
for i, txt in enumerate(labels):
ax.annotate(txt, (timestamps[i], runtimes[i]))
ax.set_yscale("log")
ax.set_xlabel("Timestamp")
ax.set_ylabel("Algorithm runtime (hours)")
ax.set_title("Estimates of when cryptographic protocols will be broken")
ax.spines[["right", "top"]].set_visible(False)
return ax
2 changes: 1 addition & 1 deletion tests/algorithms/test_algorithm_lister.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
@pytest.mark.parametrize(
"crypt_params, expected_algorithms",
[
(CryptParams("RSA", 1024), ["GidneyEkera"]),
(CryptParams("RSA", 1024), ["GidneyBasic", "GidneyEkera"]),
(CryptParams("DH-SP", 1024), ["DLogSafePrimeEH", "DLogSafePrimeShor"]),
(CryptParams("DH-SCH", 1024), ["DLogSchnorrEH", "DLogSchnorrShor"]),
(CryptParams("ECDH", 256), ["LitinskiECC"]),
Expand Down
44 changes: 44 additions & 0 deletions tests/algorithms/test_gidney_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Tests for the `GidneyBasic` class."""

import pytest

from quantumthreattracker.algorithms.quantum_algorithm import CryptParams
from quantumthreattracker.algorithms.rsa.gidney_basic import (
GidneyBasic,
GidneyBasicParams,
)


def test_gidney_basic_rsa_supported_key_sizes() -> None:
"""Test key size support and algorithm summary generation."""
key_sizes = [1024, 1536, 2048, 3072, 4096, 6144, 8192]
for key_size in key_sizes:
crypt_params = CryptParams(protocol="RSA", key_size=key_size)
algo = GidneyBasic(crypt_params)
summary = algo.get_algorithm_summary()
assert summary.n_algo_qubits > 0
assert summary.n_logical_gates.toffoli > 0


def test_gidney_basic_rsa_unsupported_key_size_raises() -> None:
"""Test that GidneyBasic raises ValueError for unsupported RSA key sizes."""
crypt_params = CryptParams(protocol="RSA", key_size=10000)
algo = GidneyBasic(crypt_params)
with pytest.raises(ValueError):
algo.get_algorithm_summary()


def test_gidney_basic_non_rsa_protocol_raises() -> None:
"""Test that GidneyBasic raises NameError when a non-RSA protocol is provided."""
crypt_params = CryptParams(protocol="ECC", key_size=2048)
algo = GidneyBasic(crypt_params)
with pytest.raises(NameError):
algo.get_algorithm_summary()


def test_gidney_basic_generate_search_space() -> None:
"""Test search space generation."""
params_list = GidneyBasic.generate_search_space()
assert isinstance(params_list, list)
assert len(params_list) == 1
assert isinstance(params_list[0], GidneyBasicParams)