{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "462cbb74-364b-411e-9e3d-f2bdfbf70a94",
   "metadata": {},
   "source": [
    "# Attacking a congruential public key cryptosystem using Gaussian lattice reduction in 2 dimension\n",
    "\n",
    "In this example we use our library to first construct a **Congruential Public Key Cryptosystem** and then attack it and retrieve **private key** using **Gaussian lattice reduction**."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12f3ab40-9636-426f-bdef-11215987972b",
   "metadata": {},
   "source": [
    "# Congruential Public Key cryptosystem\n",
    "\n",
    "## Key creation\n",
    "We start by choosing a large integer modulus $q$ which will be a public parameter of the cryptosystem.  \n",
    "Then we need to choose two secret integers $f$ and $g$ that will form **private key** such that:\n",
    "$$\n",
    "\\begin{aligned} \n",
    "0 < &f < \\sqrt{q/2} \\\\\n",
    "\\sqrt{q/4} < &g < \\sqrt{q/2}\n",
    "\\end{aligned}\n",
    "$$\n",
    "We will be calculating multiplicative modular inverses so we need to make sure that they exists:\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\gcd(f,q) &= 1 \\\\\n",
    "\\gcd(f,g) &= 1\n",
    "\\end{aligned}\n",
    "$$\n",
    "we then calculate\n",
    "$$\n",
    "h \\equiv f^{-1} g \\mod{q}\n",
    "$$\n",
    "that value will serve as our **public key**.\\\n",
    "Let's see this in code"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "bc8ec861-555c-4d9d-801b-2186f9d6235d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "private key: (231241, 195698)\n",
      "public key: 79751031608\n"
     ]
    }
   ],
   "source": [
    "import pqlattice as pq\n",
    "\n",
    "q = 122430513841\n",
    "f = 231241\n",
    "g = 195698\n",
    "\n",
    "assert 0 < f < (q/2) ** 0.5\n",
    "assert (q/4) ** 0.5 < g < (q/2) ** 0.5\n",
    "assert pq.integer.gcd(f, q * g) == 1\n",
    "\n",
    "R = pq.integer.ModIntRing(q)\n",
    "\n",
    "h = R.mul(R.inv(f), g)\n",
    "\n",
    "public_key = h\n",
    "private_key = (f,g)\n",
    "\n",
    "print(\"private key:\", private_key)\n",
    "print(\"public key:\", public_key)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5ab369d4-bf18-44b6-b525-cf21b86120d1",
   "metadata": {},
   "source": [
    "# Encryption\n",
    "In order to perform encryption we have to chose **plaintext integer** $m$ that we want to encrypt and **random integer** $r$ that will serve as noise.  \n",
    "$m$ must satisfy $ 0 < m < \\sqrt{q/4} $ and $r$ must satisfy $ 0 < r < \\sqrt{q/2} $.  \n",
    "We can then use **public key** $h$ to compute **ciphertext** $e$ with the following formula:\n",
    "$$\n",
    "e \\equiv rh + m \\mod{q}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "09cb9700-74a7-41a3-98aa-89262003a93f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "plaintext: 123456\n",
      "ciphertext: 91183651259\n"
     ]
    }
   ],
   "source": [
    "m = 123456\n",
    "r = 101010\n",
    "\n",
    "assert 0 < m < (q/4) ** 0.5\n",
    "assert 0 < r < (q/2) ** 0.5\n",
    "\n",
    "h = public_key\n",
    "e = R.mod(r * public_key + m)\n",
    "\n",
    "plaintext = m\n",
    "ciphertext = e\n",
    "\n",
    "print(\"plaintext:\", plaintext)\n",
    "print(\"ciphertext:\", ciphertext)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0626436-67fd-4a38-9334-8a77c137501c",
   "metadata": {},
   "source": [
    "# Decryption\n",
    "We will use **private key** to decrypt the message. First we compute the intermidiate value\n",
    "$$\n",
    "a \\equiv fe \\mod{q}\n",
    "$$\n",
    "and then retrieve the plaintext\n",
    "$$\n",
    "b \\equiv f^{-1}a \\mod{g}\n",
    "$$\n",
    "it's important to notice that we calculate multiplicative inverse of $f$ modulo $g$ this time and not modulo $q$ as we did when creating the key.  \n",
    "The $b$ should be equal plaintext $m$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "5e75e5d3-bd29-435a-a244-3c80c82769fa",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "decrypted message: 123456\n"
     ]
    }
   ],
   "source": [
    "f,g = private_key\n",
    "e = ciphertext\n",
    "\n",
    "a = R.mul(f, e)\n",
    "G = pq.integer.ModIntRing(g)\n",
    "b = G.mod(G.inv(f) * a)\n",
    "\n",
    "print(\"decrypted message:\", b)\n",
    "\n",
    "assert b == plaintext"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6fe2cb89-0f27-4512-bb50-ca79354144dd",
   "metadata": {},
   "source": [
    "# Attacking the cryptosystem\n",
    "Unfortunetly this cryptosystem isn't very secure. In order to break it, all the attacker needs is to find two positive integers $F$ and $G$ that will satisfy the following equation\n",
    "$$\n",
    "Fh \\equiv G \\mod{q}\n",
    "$$\n",
    "and this integers have to be sufficiently small\n",
    "$$\n",
    "F = \\mathcal{O}(\\sqrt{q}) \\quad \\textrm{and} \\quad G = \\mathcal{O}(\\sqrt{q})\n",
    "$$\n",
    "We can rewrite this congruence as\n",
    "$$\n",
    "Fh = G + qR\n",
    "$$\n",
    "It's then quite easy to reformulate this problem into finding shortest vector in lattice.  \n",
    "Let's define two vectors\n",
    "$$\n",
    "\\begin{aligned}\n",
    "v_1 &= (1,h)\\\\\n",
    "v_2 &= (0,q)\n",
    "\\end{aligned}\n",
    "$$\n",
    "To break the cryptosystem we have to find a linear combination\n",
    "$$\n",
    "\\boldsymbol{w} = a_1 \\boldsymbol{v_1} + a_2 \\boldsymbol{v_2}; \\quad a_1, a_2 \\in \\mathbb{Z}\n",
    "$$\n",
    "such that $\\boldsymbol{w}$ has length $\\mathcal{O}(\\sqrt{q})$.  \n",
    "To put it in a lattice's terms. We need to find a short nonzero vector in the set of two-dimensional lattice\n",
    "$$\n",
    "L = \\{a_1 \\boldsymbol{v_1} + a_2 \\boldsymbol{v_2} \\; : \\; a_1, a_2 \\in \\mathbb{Z} \\}\n",
    "$$\n",
    "It turns out that for **two dimensional** lattices this problem is quite trivial"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "052ce269-7f73-4021-bd16-6bd11c929ae0",
   "metadata": {},
   "source": [
    "# Gaussian Lattice Reduction in Dimension 2\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "72d3b338-e2e2-4c34-abb0-031a7172e526",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "w1: [-231241 -195698] ||w1||: 302935.8138038486\n",
      "w2: [-231241 -195698] ||w2||: 410253.69316192635\n",
      "\n",
      "F: -231241\n",
      "G: -195698\n",
      "secret f: 231241\n",
      "secret g: 195698\n"
     ]
    }
   ],
   "source": [
    "v1 = pq.as_integer([1,h])\n",
    "v2 = pq.as_integer([0,q])\n",
    "\n",
    "V = pq.as_integer([v1, v2])\n",
    "\n",
    "W = pq.lattice.glr_2dim(V)\n",
    "w1 = W[0]\n",
    "w2 = W[1]\n",
    "\n",
    "print(\"w1:\", w1, \"||w1||:\", pq.linalg.norm(w1))\n",
    "print(\"w2:\", w1, \"||w2||:\", pq.linalg.norm(w2))\n",
    "\n",
    "F, G = w1\n",
    "print()\n",
    "\n",
    "print(\"F:\", F)\n",
    "print(\"G:\", G)\n",
    "\n",
    "print(\"secret f:\", f)\n",
    "print(\"secret g:\", g)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0a4b42a9-6896-4011-a3e7-d63fa1c77079",
   "metadata": {},
   "source": [
    "As we can see using only **public** parameters of the cryptosystem, we managed to retrieve **secret** key by interpreting the underlying problem as **SVP** in dimension 2 Lattice."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
