{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "1de5aeb8-0726-476d-86ad-18b200686da0",
   "metadata": {},
   "source": [
    "# Constructing and attacking GGH public key cryptosystem\n",
    "\n",
    "GGH cryptosystem named after it's authors - Goldwasser, Goldreich and Halevi is one of the earlier cryptosystems based on some lattice problem.\n",
    "In case for GGH the underlying problem is CVP (Closest Vector Problem). In order to construct a trapdoor, the concept of good and bad basis is created.\n",
    "Although the GGH has already been already successfully cryptanalyzed for practical parameters sizes, due to it's relative simplicity it serves as a good introduction to latticed based cryptography and cryptanalysis."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d466f968-7b43-4f66-bdaa-c0f8ee26ef79",
   "metadata": {},
   "source": [
    "# GGH Public Key Cryptosystem\n",
    "\n",
    "The key concept behind the GGH cryptosystem is the notion of **good and bad basis**.  \n",
    "**Good basis** consists of relativly short and orthogonal lattice vectors, which allows (with correctly choosen parameters) for solving **CVP** with sufficiently good approximation.  \n",
    "**Bad basis** consists of long and highly non-orthogonal lattice vectors.\n",
    "# Key creation\n",
    "Let's strart by choosing a set of linearly independent vectors that will server as a good basis (to check whether a basis is good we can calculate it's **Hadamard ratio**)\n",
    "$$\n",
    "v_1,...,v_n \\in \\mathbb{Z}^{n}\n",
    "$$\n",
    "Let $V$ be a $n$-by-$n$ matrix whose rows are the vectors $v_1, ..., v_n$\n",
    "$$\n",
    "\\begin{bmatrix}\n",
    "v_1 \\newline\n",
    "\\vdots \\newline\n",
    "v_n\n",
    "\\end{bmatrix}\n",
    "$$\n",
    "In order to create the hardest possible basis that spans the same lattice we can use the **Hermite normal form** of the matrix $V$, which is an analogue of reduced echelon form for matrices over the integers $\\mathbb{Z}$ and is the same for every basis of a given lattice.  \n",
    "Let\n",
    "$$\n",
    "W = \\text{hnf}(V)\n",
    "$$\n",
    "The $V$ matrix is a good basis and will serve as a **private key**, while the matrix $W$ is a bad basis and will be used as **public key**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "64a9e50c-6545-4ad1-beab-cabef1da5bb4",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pqlattice as pq\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "ea0ba0d3-858e-4426-97cc-a41f7b99265d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Good basis V\n",
      "Matrix of integers with shape: 3 x 3\n",
      "====================================\n",
      "      [0]  [1]  [2]\n",
      "[0]   -97   19   19\n",
      "[1]   -36   30   86\n",
      "[2]  -184  -64   78\n",
      "Hadamard ratio for V: 0.75\n",
      "\n",
      "Bad basis W:\n",
      "Matrix of integers with shape: 3 x 3\n",
      "====================================\n",
      "     [0]  [1]     [2]\n",
      "[0]    1    1  285737\n",
      "[1]    0    2  181486\n",
      "[2]    0    0  429758\n",
      "Hadamard ratio for W: 0.000337862606922202\n"
     ]
    }
   ],
   "source": [
    "# let's choose the vectors for a good basis\n",
    "v1 = pq.as_integer([-97, 19, 19])\n",
    "v2 = pq.as_integer([-36, 30, 86])\n",
    "v3 = pq.as_integer([-184, -64, 78])\n",
    "\n",
    "V = pq.as_integer([v1, v2, v3])\n",
    "print(\"Good basis V\")\n",
    "pq.show(V)\n",
    "\n",
    "print(f\"Hadamard ratio for V: {pq.lattice.hadamard_ratio(V):.2f}\")\n",
    "print()\n",
    "# Now let's calculate bad basis W as a Hermite normal form of V\n",
    "W, _ = pq.linalg.hnf(V)\n",
    "print(\"Bad basis W:\")\n",
    "pq.show(W)\n",
    "print(f\"Hadamard ratio for W: {pq.lattice.hadamard_ratio(W)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d273850c-499c-4229-8741-7fbe10ee743e",
   "metadata": {},
   "source": [
    "# Encryption\n",
    "Let $ m \\in \\mathbb{Z}^{n} $ be an integer vector representing **plaintext** that is to be encrypted and   $r \\in \\mathbb{R}^{n}$ be a small perturbation vector that will act as a **noise**.  \n",
    "In order to encrypt vector $m$ using public key $W$ and noise vector $r$ we compute\n",
    "$$\n",
    "e = m \\cdot W + r\n",
    "$$\n",
    "because we add noise vector $r$, the vector $e$ is not a lattice point itself, but it should be close to the lattice point $mW$ since $r$ is small.  \n",
    "The vector $e$ is our encrypted message."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "0e6fe0b4-7e44-4d43-b579-2281e9474447",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Encrypted message e: [82 13 4469118]\n"
     ]
    }
   ],
   "source": [
    "r = pq.as_integer([-4, -3, 2])\n",
    "m = pq.as_integer([86, -35, -32])\n",
    "\n",
    "e = m @ W + r\n",
    "\n",
    "print(f\"Encrypted message e: {e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e24a02d7-295a-435f-b51e-fe3c3c027995",
   "metadata": {},
   "source": [
    "# Decryption\n",
    "In order to decrypt vector $e$ we wil use **Babai's algorithm**.  \n",
    "Given some lattice basis $B$ and some vector $w$, Babai's algorithm will find **approxCVP** in a following way\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\text{1.} & \\text{Write the vector} \\; w \\; \\text{in coordinates of basis} \\; B \\; \\text{by solving} \\; w = x \\cdot B.\\newline\n",
    "\\text{2.} & \\; x \\in \\mathbb{R}^{n} \\; \\text{so round it's coefficients to the nearest integer.}\\newline\n",
    "\\text{3.} & \\text{return x.}\n",
    "\\end{aligned}\n",
    "$$\n",
    "$$\n",
    "x = \\text{babai}(V,e)\n",
    "$$\n",
    "Vector $x$ is written using private basis, so to get to the message we need to change basis to public basis.\n",
    "$$\n",
    "d = (x \\cdot V) \\cdot W^{-1}\n",
    "$$\n",
    "Vector $d$ is the encrypted message."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "06e950e3-9e80-40f0-b84f-e68065a6121b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Message m: [86 -35 -32]\n",
      "Decrypted message d: [86 -35 -32]\n"
     ]
    }
   ],
   "source": [
    "x = pq.lattice.babai_closest_vector(V, e)\n",
    "d = pq.as_integer((x @ V) @ np.linalg.inv(W.astype(float)))\n",
    "print(f\"Message m: {m}\")\n",
    "print(f\"Decrypted message d: {d}\")\n",
    "assert np.all(m == d)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65a4ca52-ccf6-4f72-82c7-13d8965be12e",
   "metadata": {},
   "source": [
    "Let's also try to decrypt the message using only public key $W$.  \n",
    "Then $ y = \\text{babai}(W, e) \\cdot W $."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "e3eb0bab-4245-4fd1-ba3a-b3d215a07b89",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Message m: [86 -35 -32]\n",
      "Decrypted message y: [82 14 4367170]\n"
     ]
    }
   ],
   "source": [
    "y = pq.lattice.babai_closest_vector(W, e) @ W\n",
    "print(f\"Message m: {m}\")\n",
    "print(f\"Decrypted message y: {y}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65ac0947-d5e2-4df9-8ee9-6eebe1863f96",
   "metadata": {},
   "source": [
    "Because $W$ is a **bad basis**, Babai's algorithm gives a very bad approximation for **CVP**, hence the message is incorrectly decrypted."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55c3ea1e-ce4b-44c6-87f4-b8ea326f22ca",
   "metadata": {},
   "source": [
    "# Attacking the underlying problem\n",
    "Unfortunetly GGH cryptosystem is not very secure. The only thing we need to break it, is to somehow transform a known bad basis $W$ into some better basis $M$. We can use the **LLL** algorithm to do just that."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "cf56eb48-473d-47da-8e7d-b0a04e7fadf2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "LLL reduced basis\n",
      "Matrix of integers with shape: 3 x 3\n",
      "====================================\n",
      "     [0]   [1]  [2]\n",
      "[0]  -61   -11  -67\n",
      "[1]  -36    30   86\n",
      "[2]   10  -102   40\n",
      "Hadamard ratio for L: 0.96\n",
      "Message m: [86 -35 -32]\n",
      "Decrypted message s: [86 -35 -32]\n"
     ]
    }
   ],
   "source": [
    "L = pq.lattice.lll(W)\n",
    "print(\"LLL reduced basis\")\n",
    "pq.show(L)\n",
    "print(f\"Hadamard ratio for L: {pq.lattice.hadamard_ratio(L):.2f}\")\n",
    "\n",
    "s = pq.lattice.babai_closest_vector(L, e)\n",
    "s = pq.as_integer((s @ L) @ np.linalg.inv(W.astype(float)))\n",
    "\n",
    "print(f\"Message m: {m}\")\n",
    "print(f\"Decrypted message s: {s}\")\n",
    "assert np.all(m==s)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c03824e-6498-4d06-84fe-d13b5070dadc",
   "metadata": {},
   "source": [
    "Using only public key $W$ we managed to decrypt the message $e$ by utilizing the **LLL** algorithm."
   ]
  }
 ],
 "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
}
