diff --git a/basefold/basefold-notebook.ipynb b/basefold/basefold-notebook.ipynb new file mode 100644 index 0000000..618fcaf --- /dev/null +++ b/basefold/basefold-notebook.ipynb @@ -0,0 +1,1240 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0909f09b-35cd-4674-85a0-e89994da2ea5", + "metadata": {}, + "source": [ + "#### What does Sumcheck prove?\n", + "\n", + "For a polynomial `f : 𝔽² → 𝔽` (here `n = 3`) given *as a black box*, \n", + "the Prover wants to convince the Verifier that\n", + "$$\n", + "S = \\sum_{x \\in \\{0,1\\}^3} f(x) \\quad (= 36 \\text{ in our example})\n", + "$$\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "5be3a3ca-01fd-4918-af2b-99dca051cd09", + "metadata": {}, + "source": [ + "\n", + "without sending all `2ⁿ` evaluations. \n", + "Sumcheck does it in *n* rounds:\n", + "\n", + "1. **Round i** Prover sends a univariate degree-≤ d polynomial `gᵢ`. \n", + "2. Verifier checks `gᵢ(0)+gᵢ(1) = claimed_sum`. \n", + "3. Verifier samples a random field element `rᵢ` and sends it back. \n", + "4. Both parties restrict `f` to that slice and continue with `n − i − 1` variables.\n", + "\n", + "Soundness error ≤ `d/|𝔽|` per round. (We use plain integers for clarity.)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "9103b2b6-97e0-4b15-bd4c-36c1203ab0c3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "evaluations= [1, 2, 3, 4, 5, 6, 7, 8]\n", + "MLE polynomial in evaluation form:\n", + "\n", + "a[0] = 1 => term = 1*(-(x0 - 1)*(x1 - 1)*(x2 - 1))\n", + "a[1] = 2 => term = 2*((x0 - 1)*(x1 - 1)*x2)\n", + "a[2] = 3 => term = 3*((x0 - 1)*x1*(x2 - 1))\n", + "a[3] = 4 => term = 4*(-(x0 - 1)*x1*x2)\n", + "a[4] = 5 => term = 5*(x0*(x1 - 1)*(x2 - 1))\n", + "a[5] = 6 => term = 6*(-x0*(x1 - 1)*x2)\n", + "a[6] = 7 => term = 7*(-x0*x1*(x2 - 1))\n", + "a[7] = 8 => term = 8*(x0*x1*x2)\n", + "\n", + "Expanded polynomial:\n", + "4⋅x₀ + 2⋅x₁ + x₂ + 1\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZkAAAGkCAYAAADjQVi9AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAA3FtJREFUeJzsfXd4HOW5/dmmVd2VVqtq9WLJkpuabckhQGihBUhyQ+AXSiiBOJd7wSlA4BIgJFwgIb4JmBsSB0NICCGBUC4BTDPNGGz1XqyulbapbW/z+0P5htnV9p3Rjsyc5/GTsJqdmd2d+c6873ve84ooiqIgQIAAAQIEcABxvE9AgAABAgScvBBIRoAAAQIEcAaBZAQIECBAAGcQSEaAAAECBHAGgWQECBAgQABnEEhGgAABAgRwBoFkBAgQIEAAZxBIRoAAAQIEcAaBZAQIECBAAGcQSEaAAAGfC4yPj6O+vh6nnnoqampq8K1vfQtmsznep3XSQyAZPzh48CBEIlHAf++++y5nxz7ttNNw2mmncbZ/AOjt7cXdd9+NsbGxVX+7+uqrUVJSwunx+YT9+/fj4MGD8T4NAMCdd96JoqIiSKVSpKenx/t0wsLPf/5zVFRUQC6X45Zbbln199bWVpx55plITU1Feno6vvrVr+LEiRNh7fuDDz7Addddh4aGBsjlcohEIr/XLAB8+9vfRmJiIqqrq/G73/3O7zb5+fn45JNPcPjwYbS1teGjjz7CH/7wh7A/q4DoIJBMEDzxxBM4cuTIqn/19fXxPrWY0Nvbi3vuucfvDftf//VfeOGFF9b+pOIEvpDMiy++iJ/97Ge48sorcfjwYbz55pvxPqWQ6OzsxB133IHKykq88soruPnmm73+3t/fj9NOOw0OhwN//etf8Yc//AGDg4M45ZRToNPpQu7/rbfewptvvomioiK0tLQE3fbOO+/Eiy++iLy8POzZswcGg2HVNjKZDFKpFABgMplAURTq6urC/8ACooI03ifAZ2zevBmNjY3xPo01RXl5ebxPYd3D7XbD5XJBLpeH/Z7u7m4AwH/8x38gOzubq1NjFT09PQCAH/3oRzj99NNX/f2uu+6CXC7HK6+8AoVCAQBoaGhAZWUlfvGLX+CBBx4Iuv//+q//wk9+8hMAwC9+8YugGYTy8nKUl5fDbrfj3XffxeDgIJqbm1dtZzKZcO6556KzsxMtLS2ora0N9+MKiBJCJBMD6urqcMopp6x63e12Y8OGDfjqV79Kv3bPPfdg586dUKlUUCgUqK+vx4EDBxDKBPvdd9/1m6IbGxuDSCTyego/duwYvvnNb6KkpARJSUkoKSnBZZddhvHxcXqbgwcP4t/+7d8AAKeffjqdAiT78Zcus9lsuP3221FaWoqEhARs2LAB3/ve97CwsOC1XUlJCS644AK89tprqK+vR1JSEqqrq0OmJJxOJ7Kzs3HFFVes+tvCwgKSkpKwd+9e+rWlpSX84Ac/8Dqfm2++eVV+3ePx4De/+Q22b9+OpKQkpKenY9euXXjppZfo8+3p6cHhw4fp74H52ScmJvCtb30L2dnZkMvl2LRpE375y1/C4/Gs+h0efPBB3HfffSgtLYVcLsc777wT9DP7fm933nknACAnJwcikQh333132O8n6O7uxkUXXYSMjAwkJiZi+/btePLJJ722IdfTM888gzvuuAP5+flQKBQ488wzMTAwENHx7HY7ACAtLW3V31wuF1555RV87WtfowkGAIqLi3H66aeHFS2LxZEvT+RY5Nx8kZqaivfffx8TExOYmpoKSXQCYocQyQQBeSJlQiQSQSKRAFjJA//nf/4nhoaGUFlZSW/zxhtvYGZmBt/+9rfp18bGxnDDDTegqKgIAPDxxx/jpptuwvT0NO666y5WzndsbAxVVVX45je/CZVKBY1Gg8ceewxNTU3o7e2FWq3G+eefj5///Of48Y9/jEcffZRO/QWKYCiKwsUXX4y33noLt99+O0455RR0dnbiJz/5CZ0+ZD6xd3R04Pvf/z5uu+025OTk4Pe//z2uvfZaVFRU4Itf/KLfY8hkMnzrW9/C//7v/+LRRx/1WpSeeeYZ2Gw2+ru0WCw49dRTMTU1hR//+MfYunUrenp6cNddd6GrqwtvvvkmRCIRgBXCfPrpp3Httdfi3nvvRUJCAlpbW+k04QsvvICvf/3rUCqV2L9/PwDQn0Wn06GlpQUOhwM//elPUVJSgldeeQU/+MEPMDIyQm9P8Otf/xobN27EL37xCygUCq/rIRReeOEFPProozhw4ABee+01KJVKFBQUhP1+ABgYGEBLSwuys7Px61//GpmZmXj66adx9dVXY25uDj/60Y+8tv/xj3+M3bt34/e//z2WlpZw66234sILL0RfXx99fceCkZERWK1WbN26ddXftm7dikOHDsFmsyExMTHmY4ULq9WKpKQkAEBKSgqSk5ORmpq6Zsf/3IISsApPPPEEBcDvP4lEQm+n1+uphIQE6sc//rHX+7/xjW9QOTk5lNPp9Lt/t9tNOZ1O6t5776UyMzMpj8dD/+3UU0+lTj31VPq/33nnHQoA9c4773jtY3R0lAJAPfHEEwE/h8vlokwmE5WSkkL9z//8D/36c88953efFEVRV111FVVcXEz/92uvvUYBoB588EGv7Z599lkKAPX444/TrxUXF1OJiYnU+Pg4/ZrVaqVUKhV1ww03BDxPiqKozs7OVfujKIrasWMH1dDQQP/3/fffT4nFYurTTz/12u5vf/sbBYB69dVXKYqiqPfee48CQN1xxx1Bj1tbW+v1fRPcdtttFADq6NGjXq9/97vfpUQiETUwMEBR1Ge/Q3l5OeVwOIIeKxh+8pOfUAAonU4X1fu/+c1vUnK5nJqYmPB6/dxzz6WSk5OphYUFiqI+u57OO+88r+3++te/UgCoI0eOhH3MX/7ylxQAqq+vb9XfPvzwQwoA9cwzz6z6289//nMKADUzMxP2sR566CEKADU6Ohp0u6NHj1IAqOeff37V31555RWqpaWF+uIXv0ht3bqVuuaaayiz2Rz2OQiIDkK6LAieeuopfPrpp17/jh49Sv89MzMTF154IZ588kk6hTI/P48XX3wRV155JV1kBIC3334bZ555JpRKJSQSCWQyGe666y4YDAZotVpWztdkMuHWW29FRUUFpFIppFIpUlNTYTab0dfXF9U+3377bQArUQET//Zv/4aUlBS89dZbXq9v376djtYAIDExERs3bvRK2fnDli1b0NDQgCeeeIJ+ra+vD5988gmuueYa+rVXXnkFmzdvxvbt2+Fyueh/55xzjlda8Z///CcA4Hvf+17EnxlY+dw1NTXYsWOH1+tXX301KIqivxeCr3zlK5DJZFEdiw28/fbbOOOMM1BYWOj1+tVXXw2LxYIjR454vf6Vr3zF679JxBHqdwJW0pv9/f148sknkZOTg7KysoDbkqgy0r9Fi5qaGqSlpeHRRx/F6OioVybi/PPPx4cffojDhw+jo6MDBw4cQHJyMuvnIMAbAskEwaZNm9DY2Oj1r6GhwWuba665BtPT0zh06BCAlfSO3W73WpQ/+eQTnH322QCA3/3ud/jwww/x6aef4o477gCwEsazgcsvvxyPPPIIrrvuOrz++uv45JNP8OmnnyIrKyvqYxgMBkilUmRlZXm9LhKJkJubu0rFk5mZuWofcrk8rONfc801OHLkCPr7+wGsqPvkcjkuu+wyepu5uTl0dnZCJpN5/UtLSwNFUdDr9QBW0l0SiQS5ubkRf2Zg5XPn5eWtej0/P5/+OxP+tl1LRHq+vr8TSROG8ztVVlZi06ZNmJubw8svv4yEhIRV25D9+1N5GY1GiEQiTmTaqampeP755/Hpp5+irKzscyfc4SOEmkyMOOecc5Cfn48nnngC55xzDp544gns3LkTNTU19DZ/+ctfIJPJ8Morr3jloP/xj3+E3D/Z3reQSRZTgsXFRbzyyiv4yU9+gttuu41+3W63w2g0RvPRAKwsFi6XCzqdzotoKIrC7Owsmpqaot63Ly677DLs3bsXBw8exM9+9jP88Y9/xMUXX4yMjAx6G7VajaSkpIBiArVaDQDIysqC2+3G7OxsVASQmZkJjUaz6vWZmRmv4xBw8VQeCSI931jwj3/8A729vfjBD36A73znO2htbV31+cvLy5GUlISurq5V7+/q6kJFRQUn9Rin04nvfOc79D1ZVVXF+jEERAYhkokREokEV1xxBf7xj3/g/fffx7Fjx7zSO8DKAiSVSr0KqlarFX/84x9D7p+onTo7O71eJwop5jEoilolm/39738Pt9vt9VokT61nnHEGAODpp5/2ev3vf/87zGYz/Xc2kJGRgYsvvhhPPfUUXnnlFczOzq76Li+44AKMjIwgMzNzVZTZ2NhIf1/nnnsuAOCxxx4LesxAUdYZZ5yB3t5etLa2er3+1FNPQSQS+ZXsxhNnnHEG3n77bZpUCJ566ikkJydj165drB1r+/btuPzyy7Fnzx60t7f7ba6USqW48MIL8fzzz2N5eZl+fWJiAu+8846X8pJNdHd3Y3R0FHfeeSe++tWvChJlHkCIZIKgu7t7lboMWHlKYz7VX3PNNXjggQdw+eWXIykpCZdeeqnX9ueffz4efvhhXH755fjOd74Dg8GAX/ziF2H1UeTm5uLMM8/E/fffj4yMDBQXF+Ott97C888/77WdQqHAF7/4RTz00ENQq9UoKSnB4cOHceDAgVVpic2bNwMAHn/8caSlpSExMRGlpaV+U11nnXUWzjnnHNx6661YWlrC7t27aXVZXV2dX9lxLLjmmmvw7LPP4t///d9RUFCAM8880+vvN998M/7+97/ji1/8Im655RZs3boVHo8HExMTeOONN/D9738fO3fuxCmnnIIrrrgC9913H+bm5nDBBRdALpejra0NycnJuOmmmwCs1IL+8pe/4Nlnn0VZWRkSExOxZcsW3HLLLXjqqadw/vnn495770VxcTH+7//+D/v378d3v/tdbNy4kdXPHSt+8pOf4JVXXsHpp5+Ou+66CyqVCn/605/wf//3f3jwwQehVCpZP+aGDRsAYJWUneCee+5BU1MTLrjgAtx2222w2Wy46667oFar8f3vf99r29NOOw2HDx/2kvTrdDocPnwYAOiI6J///CeysrKQlZWFU089ddUxl5aWAHyWJhTAA8RVdsBTBFOXAaB+97vfrXpPS0sLBYD6f//v//nd5x/+8AeqqqqKksvlVFlZGXX//fdTBw4cWKWY8VWXURRFaTQa6utf/zqlUqkopVJJfetb36KOHTu2Sl02NTVFfe1rX6MyMjKotLQ06stf/jLV3d1NFRcXU1dddZXXPvft20eVlpZSEonEaz++6jKKWlGI3XrrrVRxcTElk8movLw86rvf/S41Pz/vtV1xcTF1/vnnr/rs/j5TILjdbqqwsDCoMsxkMlF33nknVVVVRSUkJFBKpZLasmULdcstt1Czs7Ne+/rVr35Fbd68md6uubmZevnll+ltxsbGqLPPPptKS0ujAHh99vHxceryyy+nMjMzKZlMRlVVVVEPPfQQ5Xa76W2Iuuyhhx4K6/MFQqzqMoqiqK6uLurCCy+klEollZCQQG3btm2V+pCoy5577jmv18NRK/qC3Ce+Sj8mjh07Rp1xxhlUcnIypVAoqIsvvpgaHh5etV1DQwOVm5vr91z9/Qt0PQVSYwqIH0QUFaIbUIAAAQL84Omnn8YVV1yB999/H1/4whei3s/y8jJUKhX27dsXtRqQ4LXXXsO5556LDz74ALt3745pXwLYgVCTESBAQFQg9a+//e1vWFhY8HJCiATvvfceNmzYgOuvvz7qc/F4PJifn6fTyEwZvYD4QohkBAgQEBU8Hg++/vWv4x//+AcoisJ//ud/Yt++fXE5l6uvvhpPPvkkxGIxbrjhhlWODALiB4FkBAgQEBOMRiOmpqaQkZGxqhl0rTA2Nobl5WUU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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "── First-round Sumcheck polynomial h₁(X) ──\n", + "h₁(X) = 16 + 4·X\n", + "h₁(0) = 16 (even-half sum)\n", + "h₁(1) = 20 (odd-half sum)\n", + "h₁(2) = 24 (third evaluation sent to the verifier)\n" + ] + } + ], + "source": [ + "\n", + "import matplotlib.pyplot as plt\n", + "import sympy as sp\n", + "from mpl_toolkits.mplot3d import Axes3D \n", + "\n", + "\n", + "def bits(i, n):\n", + " \"Return i as an n-bit *list* (big-endian).\"\n", + " return list(map(int, format(i, f'0{n}b')))\n", + "\n", + "def bits_reverse(i, n):\n", + " \"Little-endian version (least-significant bit first).\"\n", + " return bits(i, n)[::-1]\n", + "\n", + "def eq_tilde(bits_i, u_vector):\n", + " result=1\n", + " for bit,u in zip(bits_i,u_vector):\n", + " result *= (1-bit)*(1-u) + bit*u\n", + " return result\n", + "\n", + "def bits_of(i: int, n: int = 3):\n", + " \"\"\"Return the n-bit little-endian tuple of i (e.g. 5 → (1,0,1)).\"\"\"\n", + " return tuple((i >> j) & 1 for j in range(n))\n", + "\n", + "f_vec = [1, 2, 3, 4, 5, 6, 7, 8] # f(0,0,0) … f(1,1,1)\n", + "print(\"evaluations= \", f_vec)\n", + "\n", + "n = 3\n", + "X0, X1, X2 = sp.symbols(f\"x0:{n}\") # good\n", + "X = (X0, X1, X2) # (X0, X1, X2)\n", + "\n", + "f_tilde = sum(coeff * eq_tilde(bits(i, n), X)\n", + " for i, coeff in enumerate(f_vec))\n", + "\n", + "\n", + "N = len(f_vec)\n", + "\n", + "print(\"MLE polynomial in evaluation form:\\n\")\n", + "for i in range(N):\n", + " term = eq_tilde(bits(i, n), X)\n", + " print(f\"a[{i}] = {f_vec[i]} => term = {f_vec[i]}*({term})\")\n", + "\n", + "print(\"\\nExpanded polynomial:\")\n", + "sp.pretty_print(sp.expand(f_tilde))\n", + "\n", + "coords = [bits_of(i, 3) for i in range(8)] \n", + "\n", + "# 3-D scatter plot\n", + "fig = plt.figure()\n", + "ax = fig.add_subplot(111, projection='3d')\n", + "xs, ys, zs = zip(*coords)\n", + "\n", + "ax.scatter(xs, ys, zs, s=60)\n", + "\n", + "# annotate each vertex with its f-value\n", + "for (x0, x1, x2), val in zip(coords, f_vec):\n", + " ax.text(x0, x1, x2, f\"{val}\", fontsize=10, ha='center')\n", + "\n", + "ax.set_xlabel(\"X₀\")\n", + "ax.set_ylabel(\"X₁\")\n", + "ax.set_zlabel(\"X₂\")\n", + "ax.set_title(\"Evaluation vector f on {0,1}³\")\n", + "\n", + "plt.show()\n", + "\n", + "# first-round h₁(X) polynomial\n", + "\n", + "# Even half (X₀ = 0)\n", + "g1_0 = sum(f_vec[idx] for idx in range(8) if bits_of(idx)[0] == 0)\n", + "# Odd half (X₀ = 1)\n", + "g1_1 = sum(f_vec[idx] for idx in range(8) if bits_of(idx)[0] == 1)\n", + "\n", + "# h₁(X) = a + b·X\n", + "a = g1_0\n", + "b = g1_1 - g1_0\n", + "\n", + "print(\"\\n── First-round Sumcheck polynomial h₁(X) ──\")\n", + "print(f\"h₁(X) = {a} + {b}·X\")\n", + "print(f\"h₁(0) = {a} (even-half sum)\")\n", + "print(f\"h₁(1) = {a + b} (odd-half sum)\")\n", + "print(f\"h₁(2) = {a + 2*b} (third evaluation sent to the verifier)\")\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d6940fa4-99f2-4300-a5f6-35e20a5fbf3e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Verifier samples α₀ = 8\n", + "\n", + "Folded 4-point vector f¹ on the (X₁,X₂) plane:\n", + " (0, 0) → 33\n", + " (1, 0) → 34\n", + " (0, 1) → 35\n", + " (1, 1) → 36\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import random\n", + "random.seed(int(12345)) # deterministic for the demo\n", + "\n", + "# verifier samples α₀\n", + "alpha0 = random.randint(2, 9)\n", + "print(f\"Verifier samples α₀ = {alpha0}\")\n", + "\n", + "# fold the vector: f¹(x₁,x₂) = f(α₀, x₁, x₂) \n", + "# even half = indices where X₀ = 0 (0,1,2,3)\n", + "# odd half = indices where X₀ = 1 (4,5,6,7)\n", + "f_even = f_vec[0:4]\n", + "f_odd = f_vec[4:8]\n", + "\n", + "f_fold = [(1 - alpha0) * e + alpha0 * o # (1-α)·even + α·odd\n", + " for e, o in zip(f_even, f_odd)]\n", + "\n", + "print(\"\\nFolded 4-point vector f¹ on the (X₁,X₂) plane:\")\n", + "for idx, val in enumerate(f_fold):\n", + " print(f\" ({bits_of(idx,2)[0]}, {bits_of(idx,2)[1]}) → {val}\")\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# Arrange f_fold = [f(0,0), f(1,0), f(0,1), f(1,1)] into a 2-by-2 matrix\n", + "grid = np.array([[f_fold[0], f_fold[1]],\n", + " [f_fold[2], f_fold[3]]])\n", + "\n", + "fig, ax = plt.subplots(figsize=(4, 4))\n", + "im = ax.imshow(grid,\n", + " cmap=\"Blues\",\n", + " origin=\"lower\", # (0,0) in the lower-left corner\n", + " extent=[-0.5, 1.5, -0.5, 1.5],\n", + " vmin=grid.min(), vmax=grid.max())\n", + "\n", + "# Annotate each cell with its numeric value\n", + "for (i, j), val in np.ndenumerate(grid):\n", + " ax.text(j, i, f\"{val:.0f}\",\n", + " ha=\"center\", va=\"center\", color=\"black\", fontsize=14)\n", + "\n", + "ax.set_xticks([0, 1])\n", + "ax.set_yticks([0, 1])\n", + "ax.set_xlabel(\"X₁\")\n", + "ax.set_ylabel(\"X₂\")\n", + "ax.set_title(\"Folded function $f^{(1)}$ after Round 1\")\n", + "\n", + "plt.colorbar(im, shrink=0.75, label=\"value\")\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a9a40b6c-7751-4226-8a68-4ce664f5c709", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "── Second-round polynomial h₂(X) ──\n", + "h₂(X) = 67.0 + 4.0·X\n", + "h₂(0) = 67.0\n", + "h₂(1) = 71.0\n", + "h₂(2) = 75.0\n", + "a2 = 67\n", + "b2 = 4\n", + "h₂(0)+h₂(1) = 138\n", + "Σ f_fold = 138\n", + "Verifier check h₂(0)+h₂(1) = Σ f¹(x) ✓\n" + ] + } + ], + "source": [ + "# partial sums over X₁\n", + "g2_0 = f_fold[0] + f_fold[1] # X₁ = 0 (indices 00, 01)\n", + "g2_1 = f_fold[2] + f_fold[3] # X₁ = 1 (indices 10, 11)\n", + "\n", + "a2 = g2_0\n", + "b2 = g2_1 - g2_0 \n", + "\n", + "print(\"\\n── Second-round polynomial h₂(X) ──\")\n", + "print(f\"h₂(X) = {a2:.1f} + {b2:.1f}·X\")\n", + "print(f\"h₂(0) = {a2:.1f}\")\n", + "print(f\"h₂(1) = {a2 + b2:.1f}\")\n", + "print(f\"h₂(2) = {a2 + 2*b2:.1f}\")\n", + "\n", + "print(f\"a2 = {a2}\")\n", + "print(f\"b2 = {b2}\")\n", + "print(f\"h₂(0)+h₂(1) = {a2 + (a2 + b2)}\")\n", + "print(f\"Σ f_fold = {sum(f_fold)}\")\n", + "\n", + "\n", + "# verifier checks the sum relation\n", + "assert abs((a2) + (a2 + b2) - sum(f_fold)) < 1e-9\n", + "print(\"Verifier check h₂(0)+h₂(1) = Σ f¹(x) ✓\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "577e119b-6044-4c10-8a1d-81c93f97ca53", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Verifier samples α₁ = 2\n", + "Folded vector f² (depends only on X₂ now): [37, 38]\n", + "\n", + "── Third-round polynomial h₃(X) ──\n", + "h₃(X) = 37.0 + 1.0·X\n", + "h₃(0) = 37.0\n", + "h₃(1) = 38.0\n", + "h₃(2) = 39.0\n", + "\n", + "Verifier picks α₂ = 1 and asks for f²(α₂) = 38.0\n", + "Leaf equals h₃(α₂) ✓\n", + "\n", + "✅ Sumcheck for n = 3 completed – all rounds verified.\n", + "\n" + ] + } + ], + "source": [ + "# sample α₁ \n", + "alpha1 = random.randint(2, 9)\n", + "print(f\"\\nVerifier samples α₁ = {alpha1}\")\n", + "\n", + "# fold again over X₁ \n", + "f1_even = f_fold[0:2] # X₁ = 0 slice\n", + "f1_odd = f_fold[2:4] # X₁ = 1 slice\n", + "f_fold2 = [(1 - alpha1) * e + alpha1 * o\n", + " for e, o in zip(f1_even, f1_odd)] # length 2\n", + "\n", + "print(\"Folded vector f² (depends only on X₂ now):\", f_fold2)\n", + "\n", + "# build h₃(X) over the last variable X₂ \n", + "g3_0, g3_1 = f_fold2 \n", + "a3 = g3_0\n", + "b3 = g3_1 - g3_0\n", + "\n", + "print(\"\\n── Third-round polynomial h₃(X) ──\")\n", + "print(f\"h₃(X) = {a3:.1f} + {b3:.1f}·X\")\n", + "print(f\"h₃(0) = {a3:.1f}\")\n", + "print(f\"h₃(1) = {a3 + b3:.1f}\")\n", + "print(f\"h₃(2) = {a3 + 2*b3:.1f}\")\n", + "\n", + "# final leaf opening \n", + "alpha2 = random.randint(0, 1) \n", + "leaf = f_fold2[alpha2]\n", + "print(f\"\\nVerifier picks α₂ = {alpha2} and asks for f²(α₂) = {leaf:.1f}\")\n", + "assert abs(leaf - (a3 + b3*alpha2)) < 1e-9\n", + "print(\"Leaf equals h₃(α₂) ✓\")\n", + "\n", + "print(\"\\n✅ Sumcheck for n = 3 completed – all rounds verified.\\n\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "230abf1e-76ee-49eb-bf37-8dc59f99faa3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "────────────────────────────────────────────\n", + "f̃(α₀, α₁, α₂) = f̃(8, 2, 1) = 38\n", + "leaf opened in the final Sumcheck round = 38\n", + "────────────────────────────────────────────\n", + "✅ They match – Sumcheck transcript is fully consistent.\n", + "\n" + ] + } + ], + "source": [ + "\n", + "n = 3\n", + "X = sp.symbols(f'X0:{n}') # (X0, X1, X2)\n", + "f_tilde = sum(coeff * eq_tilde(bits(i, n), X)\n", + " for i, coeff in enumerate(f_vec))\n", + "\n", + "\n", + "\n", + "# 2. Evaluate it at the verifier’s random challenges\n", + "poly_val = f_tilde.subs({X[0]: alpha0,\n", + " X[1]: alpha1,\n", + " X[2]: alpha2})\n", + "\n", + "print(\"────────────────────────────────────────────\")\n", + "print(f\"f̃(α₀, α₁, α₂) = f̃({alpha0}, {alpha1}, {alpha2}) = {poly_val}\")\n", + "print(f\"leaf opened in the final Sumcheck round = {leaf}\")\n", + "print(\"────────────────────────────────────────────\")\n", + "assert poly_val == leaf\n", + "print(\"✅ They match – Sumcheck transcript is fully consistent.\\n\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "69adbd57-f290-43fd-83ff-9be33c8991da", + "metadata": {}, + "source": [ + "#### What’s still missing after Sumcheck?\n", + "\n", + "At the end of the previous section the verifier is convinced that\n", + "$$\n", + "\\text{leaf} = \\sum_{b \\in \\{0,1\\}^3} \\text{f\\_vec}[b] \\cdot \\text{eq}(b, \\alpha)\n", + "$$\n" + ] + }, + { + "cell_type": "markdown", + "id": "9a2fabb3-028a-4af2-9c00-182d3215d165", + "metadata": {}, + "source": [ + "\n", + "…but the verifier has **never seen `f_vec` itself**.\n", + "A cheating prover could:\n", + "\n", + "1. Run Sumcheck with any **fake** vector `f_fake`.\n", + "2. Compute `leaf_fake = Σ f_fake[b]·eq(b, α)` (easy because α is known).\n", + "3. Open that scalar in the last round.\n", + "\n", + "Sumcheck would verify perfectly – yet the statement\n", + "“`leaf` equals _your_ polynomial at α” might be **false**.\n", + "We therefore need a separate mechanism that binds the prover to\n", + "a *single* multilinear polynomial before Sumcheck starts.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "0e9d5d91-e3a0-4984-9f3c-ecb19244e05c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Honest vector : [1, 2, 3, 4, 5, 6, 7, 8]\n", + "Expanded polynomial:\n", + "4⋅X₀ + 2⋅X₁ + X₂ + 1\n", + "\n", + "\n", + "Fake vector : [8, 7, 6, 5, 4, 3, 2, 1] (same sum, different values)\n", + "Expanded polynomial:\n", + "-4⋅X₀ - 2⋅X₁ - X₂ + 8\n", + "\n", + "\n", + "Total sum S : 36 \n", + "\n", + "Verifier challenges: α0 = 8 α1 = 2 α2 = 0\n", + "Honest leaf = 38 Fake leaf = -28 \n", + "\n", + "\n", + "===== Verifier run on HONEST transcript =====\n", + "Prover’s messages:\n", + " h₁ values = (10, 26, 42) ⇒ h₁(X) = 10.0 + 16.0·X\n", + " h₂ values = (67, 71, 75) ⇒ h₂(X) = 67.0 + 4.0·X\n", + " h₃ values = (37, 38, 39) ⇒ h₃(X) = 37.0 + 1.0·X\n", + " leaf = 38 \n", + "\n", + "① h₁(0)+h₁(1) = 36 vs S = 36 → True\n", + "② h₂(0)+h₂(1) = 138 vs h₁(α₀) = 138 → True\n", + "③ h₃(0)+h₃(1) = 75 vs h₂(α₁) = 75 → True\n", + "④ leaf = 38 vs h₃(α₂) = 38 → True\n", + "Result: ✔ PASSED\n", + "\n", + "===== Verifier run on FAKE transcript =====\n", + "Prover’s messages:\n", + " h₁ values = (26, 10, -6) ⇒ h₁(X) = 26.0 + -16.0·X\n", + " h₂ values = (-49, -53, -57) ⇒ h₂(X) = -49.0 + -4.0·X\n", + " h₃ values = (-28, -29, -30) ⇒ h₃(X) = -28.0 + -1.0·X\n", + " leaf = -28 \n", + "\n", + "① h₁(0)+h₁(1) = 36 vs S = 36 → True\n", + "② h₂(0)+h₂(1) = -102 vs h₁(α₀) = -102 → True\n", + "③ h₃(0)+h₃(1) = -57 vs h₂(α₁) = -57 → True\n", + "④ leaf = -28 vs h₃(α₂) = -28 → True\n", + "Result: ✔ PASSED\n", + "Verifier result on HONEST transcript : True\n", + "Verifier result on FAKE transcript : True \n", + "\n", + "At point u = (0, 0, 0):\n", + " honest f̃(u) = 1\n", + " fake f̃(u) = 8 ← different but still passed Sumcheck!\n" + ] + } + ], + "source": [ + "# Cheating prover with *same* total sum S but different vector\n", + "import random, math\n", + "\n", + "# Choose a fake vector with the same sum \n", + "S_real = sum(f_vec) # 36 for [1…8]\n", + "\n", + "print(\"Honest vector :\", f_vec)\n", + "n = 3\n", + "X = sp.symbols(f'X0:{n}') # (X0, X1, X2)\n", + "f_tilde = sum(coeff * eq_tilde(bits(i, n), X)\n", + " for i, coeff in enumerate(f_vec))\n", + "\n", + "# 2. Evaluate it at the verifier’s random challenges\n", + "poly_val = f_tilde.subs({X[0]: alpha0,\n", + " X[1]: alpha1,\n", + " X[2]: alpha2})\n", + "\n", + "print(\"Expanded polynomial:\")\n", + "sp.pretty_print(sp.expand(f_tilde))\n", + "print(\"\\n\")\n", + "f_fake_same_sum = list(reversed(f_vec)) # [8,7,6,5,4,3,2,1]\n", + "delta = sum(f_fake_same_sum) - S_real # here 36 - 36 = 0\n", + "\n", + "\n", + "\n", + "assert sum(f_fake_same_sum) == S_real and f_fake_same_sum != f_vec\n", + "\n", + "print(\"Fake vector :\", f_fake_same_sum, \"(same sum, different values)\")\n", + "\n", + "fake_tilde = sum(coeff * eq_tilde(bits(i, n), X)\n", + " for i, coeff in enumerate(f_fake_same_sum))\n", + "\n", + "# Evaluate it at the verifier’s random challenges\n", + "poly_val = fake_tilde.subs({X[0]: alpha0,\n", + " X[1]: alpha1,\n", + " X[2]: alpha2})\n", + "\n", + "print(\"Expanded polynomial:\")\n", + "sp.pretty_print(sp.expand(fake_tilde))\n", + "print(\"\\n\")\n", + "\n", + "\n", + "print(\"Total sum S :\", S_real, \"\\n\")\n", + "\n", + "def line_triplet(v0, v1):\n", + " return (v0, v1, v0 + 2*(v1 - v0))\n", + "\n", + "def sumcheck_transcript(vec, α0, α1):\n", + " g1_0, g1_1 = sum(vec[:4]), sum(vec[4:])\n", + " h1 = line_triplet(g1_0, g1_1)\n", + "\n", + " even, odd = vec[:4], vec[4:]\n", + " f_fold = [(1-α0)*e + α0*o for e,o in zip(even, odd)]\n", + "\n", + " g2_0, g2_1 = f_fold[0]+f_fold[1], f_fold[2]+f_fold[3]\n", + " h2 = line_triplet(g2_0, g2_1)\n", + "\n", + " even2, odd2 = f_fold[:2], f_fold[2:]\n", + " f_fold2 = [(1-α1)*e + α1*o for e,o in zip(even2, odd2)]\n", + "\n", + " g3_0, g3_1 = f_fold2\n", + " h3 = line_triplet(g3_0, g3_1)\n", + "\n", + " α2 = random.randint(0, 1) \n", + " leaf = eval_line(h3, α2) \n", + " return h1, h2, h3, α2, leaf\n", + "\n", + "def eval_line(h, x):\n", + " return h[0] + (h[1]-h[0])*x\n", + "\n", + "def verifier_accepts(h1,h2,h3, α0,α1,α2, leaf, S):\n", + " \"\"\"Return True iff all 4 Sumcheck equalities hold.\"\"\"\n", + " return (\n", + " math.isclose(h1[0]+h1[1], S) and\n", + " math.isclose(h2[0]+h2[1], eval_line(h1, α0)) and\n", + " math.isclose(h3[0]+h3[1], eval_line(h2, α1)) and\n", + " math.isclose(leaf, h3[α2])\n", + " )\n", + "\n", + "h1_h, h2_h, h3_h, α2_h, leaf_h = sumcheck_transcript(f_vec, alpha0, alpha1)\n", + "h1_c, h2_c, h3_c, α2_c, leaf_c = sumcheck_transcript(f_fake_same_sum,\n", + " alpha0, alpha1)\n", + "\n", + "print(\"Verifier challenges: α0 =\", alpha0, \"α1 =\", alpha1, \"α2 =\", α2_c)\n", + "print(\"Honest leaf =\", leaf_h, \" Fake leaf =\", leaf_c, \"\\n\")\n", + "\n", + "def poly_str(h):\n", + " \"\"\"Return 'a + b·X' string for a degree-1 poly given (h0,h1, _).\"\"\"\n", + " a, b = h[0], h[1] - h[0]\n", + " return f\"{a:.1f} + {b:.1f}·X\"\n", + "\n", + "def run_and_explain(tag, h1,h2,h3, α0,α1,α2, leaf, S):\n", + " print(f\"\\n===== Verifier run on {tag} transcript =====\")\n", + " print(\"Prover’s messages:\")\n", + " print(\" h₁ values =\", h1, \" ⇒ h₁(X) =\", poly_str(h1))\n", + " print(\" h₂ values =\", h2, \" ⇒ h₂(X) =\", poly_str(h2))\n", + " print(\" h₃ values =\", h3, \" ⇒ h₃(X) =\", poly_str(h3))\n", + " print(\" leaf =\", leaf, \"\\n\")\n", + "\n", + " # --- algebraic checks ----------------------------------\n", + " check1 = math.isclose(h1[0] + h1[1], S)\n", + " check2 = math.isclose(h2[0] + h2[1], eval_line(h1, α0))\n", + " check3 = math.isclose(h3[0] + h3[1], eval_line(h2, α1))\n", + " check4 = math.isclose(leaf , h3[α2])\n", + "\n", + " print(f\"① h₁(0)+h₁(1) = {h1[0]+h1[1]:>8} vs S = {S:<8} → {check1}\")\n", + " print(f\"② h₂(0)+h₂(1) = {h2[0]+h2[1]:>8} vs h₁(α₀) = {eval_line(h1,α0):<8} → {check2}\")\n", + " print(f\"③ h₃(0)+h₃(1) = {h3[0]+h3[1]:>8} vs h₂(α₁) = {eval_line(h2,α1):<8} → {check3}\")\n", + " print(f\"④ leaf = {leaf:>8} vs h₃(α₂) = {h3[α2]:<8} → {check4}\")\n", + "\n", + " all_pass = check1 and check2 and check3 and check4\n", + " print(\"Result:\", \"✔ PASSED\" if all_pass else \"✖ REJECTED\")\n", + " return all_pass\n", + "\n", + "ok_honest = run_and_explain(\"HONEST\",\n", + " h1_h,h2_h,h3_h,\n", + " alpha0,alpha1,α2_h,\n", + " leaf_h, S_real)\n", + "\n", + "ok_fake = run_and_explain(\"FAKE \",\n", + " h1_c,h2_c,h3_c,\n", + " alpha0,alpha1,α2_c,\n", + " leaf_c, S_real) \n", + "print(\"Verifier result on HONEST transcript :\", ok_honest)\n", + "print(\"Verifier result on FAKE transcript :\", ok_fake, \"\\n\")\n", + "\n", + "def eval_mle(evals, u):\n", + " return sum(e*eq_tilde(bits_of(i,3), u) for i,e in enumerate(evals))\n", + "\n", + "u_test = (0,0,0)\n", + "print(f\"At point u = {u_test}:\")\n", + "print(\" honest f̃(u) =\", eval_mle(f_vec, u_test))\n", + "print(\" fake f̃(u) =\", eval_mle(f_fake_same_sum, u_test),\n", + " \"← different but still passed Sumcheck!\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "cc740a07-eade-4056-93cc-88ac077c487b", + "metadata": {}, + "source": [ + "#### Why add a (mock) PCS?\n", + "\n", + "* **Problem:** Sumcheck only checks algebra; the verifier never sees the \n", + " evaluation vector **f**. A prover can swap in a different vector \n", + " **f_fake** after learning the challenges and still pass all four checks.\n", + "\n", + "* **PCS idea:** Prover first sends a digest `C = Hash(f)`. \n", + " After Sumcheck it must open **the same** digest together with the claimed\n", + " value `f(α₀,α₁,α₂)`.\n", + "\n", + "* **Security:** \n", + " * Honest → digest matches **and** value matches → verifier accepts. \n", + " * Cheater → digest mismatch (or wrong value) → verifier rejects.\n", + "\n", + "The tiny SHA-256 PCS below demonstrates this: \n", + "`pcs_open` prints *why* it succeeds on the honest vector and fails on the\n", + "fake one." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "6c05abc5-efcb-413a-bda2-d9ad5045b65d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Honest evaluation vector : [1, 2, 3, 4, 5, 6, 7, 8]\n", + " Fake evaluation vector : [8, 7, 6, 5, 4, 3, 2, 1] (same Σ, different order)\n", + "\n", + "Honest commitment digest : b2ddd6543011e658aaebc223b42021e89fa9a18bd1f2207f6cb68e73ca5688b3\n", + "Fake commitment digest : 97c729b9b6163a0473bfc1d90d5bd4d8f03013dfaf2531be15f58065afe80e3f\n", + " Digests equal ? False \n", + "\n", + "[PCS] ✔ digest & value both correct\n", + "[PCS] digest mismatch (got b2ddd6543011…, expected 97c729b9b616… )\n", + "PCS-open on HONEST data : True\n", + "PCS-open on FAKE data : False \n", + " ← fails (digest mismatch)\n" + ] + } + ], + "source": [ + "\n", + "import hashlib, json, math\n", + "\n", + "def _canonical(evals):\n", + " return [int(x) for x in evals]\n", + "\n", + "def pcs_commit(evals):\n", + " blob = json.dumps(_canonical(evals), separators=(\",\", \":\")).encode()\n", + " return hashlib.sha256(blob).hexdigest()\n", + "\n", + "def pcs_open(evals, point, claimed_val, commitment, *, verbose=True):\n", + " \"\"\"\n", + " Return (all_ok, digest_ok, value_ok)\n", + " • If verbose=True, print a one-line explanation whenever something fails.\n", + " \"\"\"\n", + " digest_ok = (pcs_commit(evals) == commitment)\n", + " if verbose and not digest_ok:\n", + " print(\"[PCS] digest mismatch \"\n", + " f\"(got {commitment[:12]}…, expected {pcs_commit(evals)[:12]}… )\")\n", + "\n", + " poly_val = sum(int(e) * eq_tilde(bits_of(i, 3), point)\n", + " for i, e in enumerate(evals))\n", + " value_ok = math.isclose(poly_val, claimed_val)\n", + " if verbose and digest_ok and not value_ok:\n", + " print(\"[PCS] value mismatch \"\n", + " f\"(claimed {claimed_val}, recomputed {poly_val})\")\n", + "\n", + " if verbose and digest_ok and value_ok:\n", + " print(\"[PCS] ✔ digest & value both correct\")\n", + "\n", + " return digest_ok and value_ok, digest_ok, value_ok\n", + "\n", + "print(\" Honest evaluation vector :\", f_vec)\n", + "print(\" Fake evaluation vector :\", f_fake_same_sum, \"(same Σ, different order)\\n\")\n", + "\n", + "commit_honest = pcs_commit(f_vec)\n", + "commit_fake = pcs_commit(f_fake_same_sum)\n", + "print(\"Honest commitment digest :\", commit_honest)\n", + "print(\"Fake commitment digest :\", commit_fake)\n", + "print(\" Digests equal ? \", commit_honest == commit_fake, \"\\n\")\n", + "\n", + "query_pt = (alpha0, alpha1, α2_h) \n", + "leaf_h = eval_mle(f_vec , query_pt) \n", + "leaf_f = eval_mle(f_fake_same_sum, query_pt) \n", + "\n", + "\n", + "ok_honest, dig_honest, val_honest = pcs_open(f_vec , query_pt, leaf_h, commit_honest)\n", + "ok_fake , dig_fake , val_fake = pcs_open(f_fake_same_sum, query_pt, leaf_f, commit_honest)\n", + "\n", + "print(\"PCS-open on HONEST data :\", ok_honest)\n", + "print(\"PCS-open on FAKE data :\", ok_fake, \"\\n\",\n", + " \"← fails (digest mismatch)\")" + ] + }, + { + "cell_type": "markdown", + "id": "f2be416d-e19e-4389-a8a1-0a7d82447b5c", + "metadata": {}, + "source": [ + "#### Fact 1 — “Evaluation = Sum”\n", + "\n", + "For a multilinear-extension polynomial written in evaluation form:\n", + "\n", + "$$\n", + "\\tilde{f}(\\mathbf{X}) = \\sum_{i \\in {0,1}^n} a_i,E_i(\\mathbf{X}), \\quad\n", + "E_i(\\mathbf{X}) = \\prod_{j=0}^{n-1}\n", + "\\left( \\mathrm{bits}(i)_j,X_j + (1 - \\mathrm{bits}(i)_j)(1 - X_j) \\right)\n", + "$$\n", + "\n", + "a single point-evaluation can be rewritten as an inner product:\n", + "\n", + "$$\n", + "\\boxed{\n", + "\\tilde{f}(\\mathbf{u}) = \\sum_{i \\in {0,1}^n} a_i,\\underbrace{E_i(\\mathbf{u})}{=,eq(\\mathbf{u}, i)}\n", + "= \\left\\langle\n", + "\\underbrace{(a_0, \\dots, a{2^n - 1})}{f{\\text{vec}}},\n", + "\\underbrace{(eq(\\mathbf{u}, 0), \\dots, eq(\\mathbf{u}, 2^n - 1))}{w{\\text{vec}}}\n", + "\\right\\rangle\n", + "}\n", + "$$\n", + "\t•\tLeft-hand side — the usual “plug-and-chug” evaluation.\n", + "\t•\tRight-hand side — a length-$2^n$ sum, exactly the form the Sumcheck protocol can certify.\n", + "\n", + "⸻\n", + "\n", + "In the notebook below we will:\n", + "1. build the equality-function vector $w_{\\text{vec}}$ for the verifier’s random point $\\mathbf{u} = (\\alpha_0, \\alpha_1, \\alpha_2)$;\n", + "2.\tcompute the inner product $\\langle f_{\\text{vec}}, w_{\\text{vec}} \\rangle$;\n", + "3.\tshow it equals the direct polynomial value computed earlier.\n", + "\n", + "That makes the algebraic bridge between an MLE evaluation and a Sumcheck sum concrete." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "79a1796e-0a4d-449e-972e-3bdee25691c2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Full multilinear extension f̃(X₀,X₁,X₂):\n", + "X₀ + 2⋅X₁ + 4⋅X₂ + 1\n", + "Verifier’s point u = (8, 2, 1) \n", + "\n", + " i bits f_vec[i] w[i] contribution f[i]·w[i]\n", + "─── ───── ───────── ───── ────────────────────────\n", + " 0 (0, 0, 0) 1 0 0\n", + " 1 (1, 0, 0) 2 0 0\n", + " 2 (0, 1, 0) 3 0 0\n", + " 3 (1, 1, 0) 4 0 0\n", + " 4 (0, 0, 1) 5 7 35\n", + " 5 (1, 0, 1) 6 -8 -48\n", + " 6 (0, 1, 1) 7 -14 -98\n", + " 7 (1, 1, 1) 8 16 128\n", + "\n", + "Σ contributions = 17\n", + "Direct polynomial value f̃(u) = 17\n", + "\n", + "✔ f̃(u) equals the length-8 inner product ⟨f_vec , w_vec⟩.\n" + ] + } + ], + "source": [ + "# Fact 1 : a single evaluation is an inner product \n", + "u_point = (alpha0, alpha1, alpha2) \n", + "\n", + "try:\n", + " _ = poly_honest \n", + "except NameError:\n", + " poly_honest = sp.expand(sum(\n", + " int(f) * eq_tilde(bits_of(i, 3), X) \n", + " for i, f in enumerate(f_vec)\n", + " ))\n", + "\n", + "print(\"\\nFull multilinear extension f̃(X₀,X₁,X₂):\")\n", + "sp.pretty_print(poly_honest)\n", + "\n", + "\n", + "# Build equality-function vector w (length 8)\n", + "w_vec = [eq_tilde(bits_of(i, 3), u_point) for i in range(8)]\n", + "\n", + "\n", + "print(\"Verifier’s point u =\", u_point, \"\\n\")\n", + "print(\" i bits f_vec[i] w[i] contribution f[i]·w[i]\")\n", + "print(\"─── ───── ───────── ───── ────────────────────────\")\n", + "\n", + "contribs = []\n", + "for i, (f, w) in enumerate(zip(f_vec, w_vec)):\n", + " c = f * w\n", + " contribs.append(c)\n", + " print(f\"{i:2} {bits_of(i,3)} {f:>2} {w:>5} {c:>6}\")\n", + "\n", + "inner_prod = sum(contribs)\n", + "poly_val = eval_mle(f_vec, u_point) # direct evaluation\n", + "\n", + "print(\"\\nΣ contributions =\", inner_prod)\n", + "print(\"Direct polynomial value f̃(u) =\", poly_val)\n", + "\n", + "assert inner_prod == poly_val\n", + "print(\"\\n✔ f̃(u) equals the length-8 inner product ⟨f_vec , w_vec⟩.\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "858dc1e6-23b1-4765-a36b-5d279a772080", + "metadata": {}, + "source": [ + "#### Fact #2 — Sumcheck fold ≙ FRI split-fold\n", + "\n", + "At every round both protocols take a length-$2m$ vector, cut it into \n", + "`even` and `odd` halves, and compute:\n", + "\n", + "$$\n", + "\\texttt{fold}(\\alpha) = (1 - \\alpha)\\,\\texttt{even} + \\alpha\\,\\texttt{odd}\n", + "$$\n", + "\n", + "- **Sumcheck** uses it to reduce the multilinear inner-product dimension. \n", + "- **FRI** uses the *same* map to reduce a Reed–Solomon codeword.\n", + "\n", + "Below we fold the honest evaluation vector two ways:\n", + "\n", + "1. with the hand-written Sumcheck comprehension already in the notebook, \n", + "2. use folding code from `basefold_rs_pcs.py`, the helper the real \n", + " FRI implementation calls.\n", + "\n", + "They land on exactly the same 4-entry vector, confirming the equivalence." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "33d14510-0116-496a-b271-17686db6e6d0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "even = [1, 2, 3, 4]\n", + "odd = [5, 6, 7, 8]\n", + "α = 8 \n", + "\n", + "Sumcheck fold : [33, 34, 35, 36]\n", + "FRI fold : [33, 34, 35, 36] \n", + "\n", + "✔ Identical output ⇒ both protocols share the very same linear map.\n" + ] + } + ], + "source": [ + "\n", + "\n", + "'''\n", + "The actual folding from the implementation itself : \n", + "\n", + " # fold f_code\n", + " f_code_folded = [(Field(1)-alpha) * (f_code[2*j] + f_code[2*j+1]) / 2 \n", + " + alpha * (f_code[2*j] - f_code[2*j+1]) / (2 * coset * twiddles[j]) \n", + " for j in range(len(f_code)//2)]\n", + "'''\n", + "\n", + "def fri_split_fold(even, odd, alpha):\n", + " \"\"\"\n", + " Return (1-alpha)*even + alpha*odd element-wise, exactly what FRI\n", + " does to halve a Reed–Solomon codeword. Works for plain lists of\n", + " field elements as used in this demo.\n", + " \"\"\"\n", + " return [(1 - alpha) * e + alpha * o for e, o in zip(even, odd)]\n", + "\n", + "\n", + "even = f_vec[:4] # indices 000,001,010,011 (X₀=0)\n", + "odd = f_vec[4:] # indices 100,101,110,111 (X₀=1)\n", + "α = alpha0 # verifier’s first challenge\n", + "\n", + "print(\"even =\", even)\n", + "print(\"odd =\", odd)\n", + "print(\"α =\", α, \"\\n\")\n", + "\n", + "# Sumcheck’s explicit comprehension\n", + "sumcheck_fold = [(1-α)*e + α*o for e, o in zip(even, odd)]\n", + "print(\"Sumcheck fold :\", sumcheck_fold)\n", + "\n", + "# FRI’s comprehension\n", + "pcs = BASEFOLD_RS_PCS(MerkleTree, debug=0)\n", + "fri_fold = fri_split_fold(even, odd, α) \n", + "print(\"FRI fold :\", fri_fold, \"\\n\")\n", + "\n", + "assert sumcheck_fold == fri_fold\n", + "print(\"✔ Identical output ⇒ both protocols share the very same linear map.\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "4d55cae0-4faa-4007-a6e4-612b0f6f562f", + "metadata": {}, + "source": [ + "### Step 4 — Gluing FRI + Sumcheck into *one* PCS\n", + "\n", + "**Goal:** prove the public statement $f(u) = v$ without revealing the \n", + "whole evaluation vector.\n", + "\n", + "---\n", + "\n", + "#### 1 · Re-phrase the claim\n", + "\n", + "- Pick the **same** random challenges \n", + " $\\alpha = (\\alpha_0, \\dots, \\alpha_{n-1})$ that will drive both \n", + " sub-protocols.\n", + "- Let $w := f(\\alpha)$.\n", + "\n", + "Now proving $f(u) = v$ is equivalent to proving these two facts:\n", + "\n", + "1. **FRI-fact** $f(\\alpha) = w$ \n", + "2. **Sumcheck-fact** $v = \\sum_b f(b) \\, eq(u, b)$\n", + "\n", + "---\n", + "\n", + "#### 2 · One transcript, two jobs\n", + "\n", + "| round | prover message | α used by |\n", + "|-------|----------------|-----------|\n", + "| commit | RS–codeword → Merkle root $C_0$ | — |\n", + "| 0 | send $h_1$, fold with $\\alpha_0$ | FRI & Sumcheck |\n", + "| 1 | send $h_2$, fold with $\\alpha_1$ | FRI & Sumcheck |\n", + "| … | … | … |\n", + "| n | send $h_n$; open **one** code symbol = $w$ | FRI constant & Sumcheck leaf |\n", + "\n", + "- **FRI** + Merkle paths ⇒ verifier accepts $f(\\alpha) = w$ \n", + "- **Sumcheck**, fed with the *same* $\\alpha$’s and that single scalar \n", + " $w$, ⇒ verifier accepts $\\sum f(b) \\, eq(u, b) = v$ \n", + " ⇒ Algebra then forces $f(u) = v$\n", + "\n", + "---\n", + "\n", + "#### 3 · Soundness intuition\n", + "\n", + "Because every fold is checked **twice** (FRI *and* Sumcheck), a cheating \n", + "prover must satisfy both or be caught with probability \n", + "$\\approx 1 / |F|$ per round. The verifier inspects only \n", + "$O(\\log n)$ field elements.\n", + "\n", + "> **Shared randomness = one proof** \n", + "> FRI authenticates one hidden evaluation $w$; \n", + "> Sumcheck converts that single fact into the original claim \n", + "> $f(u) = v$." + ] + }, + { + "cell_type": "markdown", + "id": "aa44eb44-e722-4adb-9e3f-33fed95798d3", + "metadata": {}, + "source": [ + "### End-to-end picture for \\(n = 3\\)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " %% Style tweaks\n", + " classDef b fill:#f0f0ff,stroke:#9aa;\n", + " classDef s fill:#fff8dc,stroke:#d9a;\n", + "\n", + " %% Commit phase\n", + " A0([\"**8-entry eval vector**
f = [1..8]\"]):::b\n", + " A1([\"Reed–Solomon encode →
codeword c₀ (len = 8·ρ)\"]):::b\n", + " A2([\"Merkle-root
**C₀**\"]):::b\n", + "\n", + " A0 -->|encode| A1 -->|Merkle| A2\n", + " subgraph G1[ Prover side ]\n", + " A0;A1;A2\n", + " end\n", + "\n", + " %% Shared loop\n", + " B0((\"k = 3 rounds\")):::s\n", + "\n", + " %% Round boxes\n", + " R1[\"Round 0
split even/odd
fold with α₀\"]\n", + " R2[\"Round 1
split even/odd
fold with α₁\"]\n", + " R3[\"Round 2
split even/odd
fold with α₂ (last)\"]\n", + "\n", + " %% Folding arrows\n", + " A2 --> R1 --> R2 --> R3\n", + "\n", + " %% Sumcheck track\n", + " subgraph Sumcheck[ Sumcheck track ]\n", + " direction LR\n", + " S8[\"f (8)\"]-->S4[\"f¹ (4)\"]-->S2[\"f² (2)\"]-->S1[\"leaf (1)\"]\n", + " end\n", + "\n", + " %% FRI track\n", + " subgraph FRI[ FRI track ]\n", + " direction LR\n", + " C8[\"c₀ (8·ρ)\"]-->C4[\"c₁\"]-->C2[\"c₂\"]-->C1[\"constant w\"]\n", + " end\n", + "\n", + " %% α arrows\n", + " R1 -- same α₀ --> C4 & S4\n", + " R2 -- same α₁ --> C2 & S2\n", + " R3 -- same α₂ --> C1 & S1\n", + "\n", + " %% Verifier checks\n", + " V1[\"Merkle paths + FRI\\n⇒ accept f(α)=w?\"]:::b\n", + " V2[\"Sumcheck equalities\\n⇒ accept Σf eq(u,·)=v?\"]:::b\n", + " C1 --> V1\n", + " S1 --> V2\n", + " V1 --> V2\n", + "\n", + " %% Final statement\n", + " V2 --> V3([\"✓ conclude f(u)=v\"]):::b\n" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "f1087688-046d-4139-a924-eab7c09ea882", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Public claim : f([8, 2, 1]) = v = 17\n", + "\n", + "Commitment root : e219f04c2b44cc862b9b481e4db03e9e79d4b280b7e1ba860fcb683f3d7f5b1d …\n", + "\n", + "🕐 Prover: generating proof …\n", + "P> f_len=8, k=3, c_len =64\n", + "P> check f0_code\n", + "P> check f0_code passed\n", + "P> Round 0\n", + "P> alpha[0] = 2117879509\n", + "P> Round 1\n", + "P> alpha[1] = 4293038735\n", + "P> Round 2\n", + "P> alpha[2] = 3383722775\n", + "P> final_sum(33557169405938763609452287200546002464000) = f(alpha_vec)*eq(alpha_vec), 24238848080 * 1384437465641261761208756550800\n", + "P> queries=22, query_indices=[[(6, 7), (2, 3), (0, 1)], [(50, 51), (24, 25), (12, 13)], [(40, 41), (20, 21), (10, 11)], [(12, 13), (6, 7), (2, 3)], [(8, 9), (4, 5), (2, 3)], [(24, 25), (12, 13), (6, 7)], [(62, 63), (30, 31), (14, 15)], [(4, 5), (2, 3), (0, 1)], [(54, 55), (26, 27), (12, 13)], [(58, 59), (28, 29), (14, 15)], [(28, 29), (14, 15), (6, 7)], [(46, 47), (22, 23), (10, 11)], [(42, 43), (20, 21), (10, 11)], [(52, 53), (26, 27), (12, 13)], [(30, 31), (14, 15), (6, 7)], [(2, 3), (0, 1), (0, 1)], [(34, 35), (16, 17), (8, 9)], [(38, 39), (18, 19), (8, 9)], [(48, 49), (24, 25), (12, 13)], [(10, 11), (4, 5), (2, 3)], [(0, 1), (0, 1), (0, 1)], [(44, 45), (22, 23), (10, 11)]]\n", + "check query_paths\n", + "P> check query_paths passed\n", + "P> check merkle_paths\n", + "P> roots=['e219f04c2b44cc862b9b481e4db03e9e79d4b280b7e1ba860fcb683f3d7f5b1d', '1503d2106cdb0d2affd6eb899e9bde549ea676c55e5bff722c94e5660cb49300', '84182f035d9dabf6e92a3a9800f58ce2ed327936dceb3a0431e394741c5824aa']\n", + "P>> check merkle_path-0 passed\n", + "P>> check merkle_path-1 passed\n", + "P>> check merkle_path-2 passed\n", + "P>> check merkle_path-3 passed\n", + "P>> check merkle_path-4 passed\n", + "P>> check merkle_path-5 passed\n", + "P>> check merkle_path-6 passed\n", + "P>> check merkle_path-7 passed\n", + "P>> check merkle_path-8 passed\n", + "P>> check merkle_path-9 passed\n", + "P>> check merkle_path-10 passed\n", + "P>> check merkle_path-11 passed\n", + "P>> check merkle_path-12 passed\n", + "P>> check merkle_path-13 passed\n", + "P>> check merkle_path-14 passed\n", + "P>> check merkle_path-15 passed\n", + "P>> check merkle_path-16 passed\n", + "P>> check merkle_path-17 passed\n", + "P>> check merkle_path-18 passed\n", + "P>> check merkle_path-19 passed\n", + "P>> check merkle_path-20 passed\n", + "P>> check merkle_path-21 passed\n", + "P> check merkle_paths passed\n", + "ℹ️ Proof generated.\n", + "\n", + "🕐 Verifier: checking proof …\n", + "V> alpha[0] = 2117879509\n", + "V> alpha[1] = 4293038735\n", + "V> alpha[2] = 3383722775\n", + "V> len(codes)=2, queries=22\n", + "V> queries=22, query_indices=[[(6, 7), (2, 3), (0, 1)], [(50, 51), (24, 25), (12, 13)], [(40, 41), (20, 21), (10, 11)], [(12, 13), (6, 7), (2, 3)], [(8, 9), (4, 5), (2, 3)], [(24, 25), (12, 13), (6, 7)], [(62, 63), (30, 31), (14, 15)], [(4, 5), (2, 3), (0, 1)], [(54, 55), (26, 27), (12, 13)], [(58, 59), (28, 29), (14, 15)], [(28, 29), (14, 15), (6, 7)], [(46, 47), (22, 23), (10, 11)], [(42, 43), (20, 21), (10, 11)], [(52, 53), (26, 27), (12, 13)], [(30, 31), (14, 15), (6, 7)], [(2, 3), (0, 1), (0, 1)], [(34, 35), (16, 17), (8, 9)], [(38, 39), (18, 19), (8, 9)], [(48, 49), (24, 25), (12, 13)], [(10, 11), (4, 5), (2, 3)], [(0, 1), (0, 1), (0, 1)], [(44, 45), (22, 23), (10, 11)]]\n", + "V> check merkle_path-0 passed\n", + "V> check merkle_path-1 passed\n", + "V> check merkle_path-2 passed\n", + "V> check merkle_path-3 passed\n", + "V> check merkle_path-4 passed\n", + "V> check merkle_path-5 passed\n", + "V> check merkle_path-6 passed\n", + "V> check merkle_path-7 passed\n", + "V> check merkle_path-8 passed\n", + "V> check merkle_path-9 passed\n", + "V> check merkle_path-10 passed\n", + "V> check merkle_path-11 passed\n", + "V> check merkle_path-12 passed\n", + "V> check merkle_path-13 passed\n", + "V> check merkle_path-14 passed\n", + "V> check merkle_path-15 passed\n", + "V> check merkle_path-16 passed\n", + "V> check merkle_path-17 passed\n", + "V> check merkle_path-18 passed\n", + "V> check merkle_path-19 passed\n", + "V> check merkle_path-20 passed\n", + "V> check merkle_path-21 passed\n", + "V> check folding-0 passed\n", + "V> check folding-1 passed\n", + "V> check folding-2 passed\n", + "V> check folding-3 passed\n", + "V> check folding-4 passed\n", + "V> check folding-5 passed\n", + "V> check folding-6 passed\n", + "V> check folding-7 passed\n", + "V> check folding-8 passed\n", + "V> check folding-9 passed\n", + "V> check folding-10 passed\n", + "V> check folding-11 passed\n", + "V> check folding-12 passed\n", + "V> check folding-13 passed\n", + "V> check folding-14 passed\n", + "V> check folding-15 passed\n", + "V> check folding-16 passed\n", + "V> check folding-17 passed\n", + "V> check folding-18 passed\n", + "V> check folding-19 passed\n", + "V> check folding-20 passed\n", + "V> check folding-21 passed\n", + "✅ Proof verified = True\n" + ] + } + ], + "source": [ + "# Basefold PCS demo (n = 3, f = [1…8]) \n", + "import basefold_rs_pcs as pcs_mod \n", + "from merlin.merlin_transcript import MerlinTranscript\n", + "from utils import inner_product \n", + "\n", + "Field = pcs_mod.Field \n", + "MerkleTree = pcs_mod.MerkleTree\n", + "BASEFOLD_RS_PCS = pcs_mod.BASEFOLD_RS_PCS\n", + "MLEPolynomial = pcs_mod.MLEPolynomial\n", + "\n", + "# public instance \n", + "evals = [Field(i) for i in range(1, 9)] # f = [1,2,3,4,5,6,7,8]\n", + "u_point = [Field(alpha0), Field(alpha1), Field(alpha2)] # the same α’s\n", + "\n", + "MLEPolynomial.set_field_type(Field)\n", + "f_mle = MLEPolynomial(evals, 3)\n", + "\n", + "# v = f(u)\n", + "eq_vec = MLEPolynomial.eqs_over_hypercube(u_point)\n", + "v_true = inner_product(evals, eq_vec, Field.zero())\n", + "assert f_mle.evaluate(u_point) == v_true\n", + "\n", + "print(f\"Public claim : f({u_point}) = v = {v_true}\\n\")\n", + "\n", + "# set-up prover / verifier objects \n", + "pcs = BASEFOLD_RS_PCS(MerkleTree, debug=2) \n", + "pcs.security_bits = 32 \n", + "tr = MerlinTranscript(b\"basefold-rs-demo\")\n", + "\n", + "# commitment\n", + "f_cm = pcs.commit(f_mle)\n", + "print(\"Commitment root :\", f_cm.cm, \"…\\n\")\n", + "\n", + "# prover produces argument \n", + "print(\"🕐 Prover: generating proof …\")\n", + "v_proved, argument = pcs.prove_eval(f_cm, f_mle, u_point, tr.fork(b\"prove\"))\n", + "print(\"ℹ️ Proof generated.\\n\")\n", + "assert v_proved == v_true\n", + "\n", + "# verifier checks\n", + "print(\"🕐 Verifier: checking proof …\")\n", + "ok = pcs.verify_eval(f_cm, u_point, v_true, argument, tr.fork(b\"verify\"))\n", + "print(\"✅ Proof verified =\", ok)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52da1a2b-4a92-495a-95d1-87a11ca146de", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "SageMath 10.6", + "language": "sage", + "name": "sagemath" + }, + "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.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/math/reductions/logjump_reduction.ipynb b/math/reductions/logjump_reduction.ipynb new file mode 100644 index 0000000..95d92bb --- /dev/null +++ b/math/reductions/logjump_reduction.ipynb @@ -0,0 +1,462 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "92fd39be-b31f-4344-9d9b-6c9cd0aedad4", + "metadata": {}, + "source": [ + "## Classic Montgomery REDC \n", + "\n", + "Montgomery reduction rewrites a 2·*n*-word integer\n", + "\n", + "$$\n", + "c = t = \\sum_{i=0}^{2n-1} t_i \\cdot 2^{64i}\n", + "$$\n", + "\n", + "into an *n*-word residue \n", + "$t \\cdot R^{-1} \\bmod p$ with \n", + "\n", + "- $R = 2^{64n}$ (so $R \\equiv 0 \\pmod{p}$),\n", + "- a single-word constant $\\mu = -p^{-1} \\pmod{2^{64}}$.\n", + "\n", + "The outer loop runs **once per limb** (`i = 0 … n−1`):\n", + "\n", + "1. Pick $q = (t[i] \\cdot \\mu) \\bmod 2^{64}$ → forces \n", + " $t[i] + q \\cdot p \\equiv 0 \\pmod{2^{64}}$;\n", + "2. Add $q \\cdot p$ into the running array (two inner loops);\n", + "3. After the loop, the first *n* limbs are guaranteed zero \n", + " → drop them (divide by $R$);\n", + "4. Final conditional subtraction ensures the result $< p$.\n", + "\n", + "The code below is a pseudocode, parameterised by the constants we set up in the previous cell." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "8eff43c3-f0a4-47fe-9b0c-221b98637da8", + "metadata": {}, + "outputs": [], + "source": [ + "def mont_redc(c_words: List[int]) -> List[int]:\n", + " \"\"\"\n", + " Classic Montgomery reduction, limb-for-limb.\n", + " Expects c_words to have length 2*N (little-endian).\n", + " Returns an n-word little-endian list < p.\n", + " \"\"\"\n", + " assert len(c_words) == 2 * N\n", + " t = c_words.copy()\n", + "\n", + " for i in range(N):\n", + " q = (t[i] * MU) & MASK\n", + "\n", + " # ---- multiply p * q ----\n", + " pq = [0] * (N + 1)\n", + " carry = 0\n", + " for j in range(N):\n", + " prod = q * P_WORDS[j] + carry\n", + " pq[j] = prod & MASK\n", + " carry = prod >> WORD_BITS\n", + " pq[N] = carry\n", + "\n", + " # ---- add pq into t[i + ..] ----\n", + " carry = 0\n", + " for j in range(N + 1):\n", + " s = t[i + j] + pq[j] + carry\n", + " t[i + j] = s & MASK\n", + " carry = s >> WORD_BITS\n", + "\n", + " # ---- propagate carry further if needed ----\n", + " k = i + N + 1\n", + " while carry and k < 2 * N:\n", + " s = t[k] + carry\n", + " t[k] = s & MASK\n", + " carry = s >> WORD_BITS\n", + " k += 1\n", + "\n", + " # t now starts with n zeros; slice off the high half\n", + " lhs = t[N : 2 * N]\n", + "\n", + " if gte(lhs, P_WORDS[:N]):\n", + " lhs = sub(lhs, P_WORDS[:N])\n", + " return lhs\n" + ] + }, + { + "cell_type": "markdown", + "id": "a0336c3f-46ac-4897-8d4c-e1ec26333fbb", + "metadata": {}, + "source": [ + "## From REDC to **LogJump/SOS** — the big idea\n", + "\n", + "Classic Montgomery spends **one full outer loop per limb**. \n", + "LogJump collapses *n − 1* of those loops into just three *ρ-jumps* and leaves **only a single** Montgomery iteration at the end.\n", + "\n", + "\n", + "\n", + "### 1 Pre-compute a “magic” vector ρ\n", + "\n", + "For an $n = 4$ limb modulus, let\n", + "\n", + "$$\n", + "\\rho = 2^{-64} \\bmod p, \\qquad\n", + "\\rho = (\\rho_0, \\rho_1, \\rho_2, \\rho_3)_{\\text{le}} .\n", + "$$\n", + "\n", + "Because $2^{64} \\cdot \\rho \\equiv 1 \\pmod{p}$, multiplying the **low word** of any value by $\\rho$ and adding that in at a one-word offset both:\n", + "\n", + "- cancels the low word, **and**\n", + "- shifts the whole number one limb to the right \n", + " (the carry serves as the “lost” high word).\n", + "\n", + "This is exactly what each Montgomery outer loop did — but now we get the \n", + "shift **for free** once $\\rho$ is available.\n", + "\n", + "\n", + "### 2 Do three jumps instead of three REDC loops\n", + "\n", + "For a 256-bit number we need to zero and discard the first **three** limbs:" + ] + }, + { + "cell_type": "markdown", + "id": "4ea4fad4-53dd-496b-806e-b38c76ffa124", + "metadata": {}, + "source": [ + "## LogJump Example – Execution Breakdown\n", + "\n", + "```text\n", + "c = [ c0 c1 c2 c3 c4 c5 c6 c7 ]\n", + " ↓\n", + "step1: ρ·c0 added one limb up → shift 1 \n", + "step2: ρ·(new)low added one limb up → shift 1 \n", + "step3: ρ·(new)low added one limb up → shift 1\n", + "```\n", + "\n", + "```pgsql\n", + "After those three jumps the array looks like\n", + "```\n", + "\n", + "```text\n", + "[ 0 0 0 r1 r2 r3 r4 ]\n", + "```\n", + "\n", + "So we have already divided by $2^{64 \\cdot 3}$.\n", + "\n", + "---\n", + "\n", + "### 3 Finish with **one** standard Montgomery iteration\n", + "\n", + "Only limb 0 of the remaining slice may still be non-zero. \n", + "One ordinary REDC loop (with the usual constant $\\mu$) clears it and divides by the final $2^{64}$, leaving exactly four words. \n", + "A compare-and-subtract with *p* is the last step.\n", + "\n", + "**Result: multiplies saved**\n", + "\n", + "- Classic REDC → $n^2 + n$ word-multiplies \n", + "- LogJump/SOS → $n^2 + 1$ word-multiplies \n", + "\n", + "For secp256k1 (*n = 4*), that is: \n", + "20 → 17 multiplies — a ~15% cut.\n", + "\n", + "\n", + "The next cells turn this description into code." + ] + }, + { + "cell_type": "markdown", + "id": "9cdf8769-7dd2-4bcc-a282-f3fe2dfa5763", + "metadata": {}, + "source": [ + "## LogJump/SOS implementation (64-bit limbs, n = 4)\n", + "\n", + "Below we define:\n", + "\n", + "1. **`calc_m(low)`** – multiplies a single limb `low` by the pre-computed\n", + " ρ-vector and returns the 5-word result.\n", + "2. **`mul_logjumps_sos(c)`** – performs three ρ-jumps followed by one\n", + " classic Montgomery iteration, returning a 4-limb residue < p.\n", + "\n", + "All constants (`ρ`, `μ`, `p`) come from the setup cell so that the\n", + "comparison with `mont_redc` is apples-to-apples.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "db69267e-4ab7-46df-9efc-9b978a5e99af", + "metadata": {}, + "outputs": [], + "source": [ + "def calc_m(low: int) -> list[int]:\n", + " carry, m = 0, [0]*6\n", + " for i in range(5):\n", + " prod = low * RHO_WORDS[i] + carry\n", + " m[i] = prod & MASK\n", + " carry = prod >> WORD_BITS\n", + " m[5] = carry\n", + " return m\n", + "\n", + "def mul_logjumps_sos(c_words: list[int]) -> list[int]:\n", + " R = [0]*8\n", + "\n", + " # jump #1\n", + " m, carry = calc_m(c_words[0]), 0\n", + " for i in range(6):\n", + " s = c_words[i+1] + m[i] + carry\n", + " R[i], carry = s & MASK, s >> WORD_BITS\n", + " s = c_words[6] + carry\n", + " R[5], carry = s & MASK, s >> WORD_BITS\n", + " s = c_words[7] + carry\n", + " R[6], carry = s & MASK, s >> WORD_BITS\n", + " R[7] = carry\n", + "\n", + " # jump #2\n", + " m, carry = calc_m(R[0]), 0\n", + " for i in range(6):\n", + " s = R[i+1] + m[i] + carry\n", + " R[i], carry = s & MASK, s >> WORD_BITS\n", + " s = R[6] + carry\n", + " R[5], carry = s & MASK, s >> WORD_BITS\n", + " R[6] = carry\n", + " R[7] = 0\n", + "\n", + " # jump #3\n", + " m, carry = calc_m(R[0]), 0\n", + " for i in range(6):\n", + " s = R[i+1] + m[i] + carry\n", + " R[i], carry = s & MASK, s >> WORD_BITS\n", + " R[5] = carry\n", + " R[6] = R[7] = 0\n", + "\n", + " # one Montgomery iteration\n", + " q = (R[0] * MU) & MASK\n", + " pq, carry = [0]*6, 0\n", + " for i in range(5):\n", + " prod = q * U64_P[i] + carry\n", + " pq[i], carry = prod & MASK, prod >> WORD_BITS\n", + " pq[5] = carry\n", + "\n", + " carry = 0\n", + " for i in range(6):\n", + " s = R[i] + pq[i] + carry\n", + " R[i], carry = s & MASK, s >> WORD_BITS\n", + " idx = 6\n", + " while carry and idx < 8:\n", + " s = R[idx] + carry\n", + " R[idx], carry = s & MASK, s >> WORD_BITS\n", + " idx += 1\n", + "\n", + " out = R[1:5]\n", + " if gte(out, P_WORDS[:4]):\n", + " out = sub(out, P_WORDS[:4])\n", + " return out\n" + ] + }, + { + "cell_type": "markdown", + "id": "34fa2324-b741-41a7-aa6d-45ba868a1ab0", + "metadata": {}, + "source": [ + "#### Official limb-loops vs. big-int back-end\n", + "\n", + "| Variant | What it actually does |\n", + "|---------|----------------------|\n", + "| **Official limb loops** | manipulates four 64-bit limbs with explicit Python `for`-loops, carry handling, and per-limb multiplies. |\n", + "| **Big-int / GMP** | Treats the same 256-bit number as one large Python `int` (or `gmpy2.mpz`). Each limb step becomes a single C-level bigint multiply, so the benchmark measures only the difference in multiplication count. |\n", + "\n", + "> Limb loops incur heavy interpreter overhead, hiding LogJump’s saving. \n", + "> Big-int mode pushes work into optimized C code, revealing the relative speed-up." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "c4918d6b-0bef-4557-a0b0-9594957f54c3", + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Choose implementation: 1 = official limb loops, 2 = big-int > 2\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Backend : GMP (gmpy2)\n", + "Variant : big-int\n", + "\n", + "[micro] Mont 2.051 µs LogJump 2.044 µs\n", + "[batch] Mont 2076.4 ns/op LogJump 2060.3 ns/op speed-up 1.01×\n" + ] + } + ], + "source": [ + "import random, timeit, importlib.util\n", + "from typing import List\n", + "\n", + "WORD_BITS = 64\n", + "MASK = (1 << WORD_BITS) - 1\n", + "P = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47\n", + "N, R = 4, 1 << (WORD_BITS * 4)\n", + "MU0 = (-pow(P, -1, 1 << WORD_BITS)) & MASK\n", + "\n", + "has_gmp = bool(importlib.util.find_spec(\"gmpy2\"))\n", + "_int = __import__(\"gmpy2\").mpz if has_gmp else int\n", + "\n", + "choice = input(\"Choose implementation: 1 = official limb loops, 2 = big-int > \").strip()\n", + "use_official = (choice != \"2\")\n", + "\n", + "def to_words(x, m=2*N): return [(x >> (WORD_BITS*i)) & MASK for i in range(m)]\n", + "def from_words(ws): return sum(w << (WORD_BITS*i) for i, w in enumerate(ws))\n", + "def gte(a, b): return a[::-1] >= b[::-1]\n", + "def sub(a, b):\n", + " out, borrow = [], 0\n", + " for ai, bi in zip(a, b):\n", + " t = ai - bi - borrow\n", + " out.append((t + (1<> WORD_BITS\n", + " pq[N] = carry\n", + " carry = 0\n", + " for j in range(N+1):\n", + " s = t[i+j] + pq[j] + carry\n", + " t[i+j], carry = s & MASK, s >> WORD_BITS\n", + " k = i+N+1\n", + " while carry and k < 2*N:\n", + " s = t[k] + carry\n", + " t[k], carry = s & MASK, s >> WORD_BITS\n", + " k += 1\n", + " lhs = t[N:2*N]\n", + " if gte(lhs, P_WORDS[:N]): lhs = sub(lhs, P_WORDS[:N])\n", + " return lhs\n", + "\n", + "def calc_m(low):\n", + " carry, m = 0, [0]*6\n", + " for i in range(5):\n", + " prod = low * RHO_WORDS[i] + carry\n", + " m[i], carry = prod & MASK, prod >> WORD_BITS\n", + " m[5] = carry\n", + " return m\n", + "\n", + "def mul_logjumps_sos(c):\n", + " Rv = [0]*8\n", + " m, carry = calc_m(c[0]), 0\n", + " for i in range(6):\n", + " s = c[i+1] + m[i] + carry\n", + " Rv[i], carry = s & MASK, s >> WORD_BITS\n", + " s = c[6] + carry; Rv[5], carry = s & MASK, s >> WORD_BITS\n", + " s = c[7] + carry; Rv[6], carry = s & MASK, s >> WORD_BITS\n", + " Rv[7] = carry\n", + " for _ in range(2):\n", + " m, carry = calc_m(Rv[0]), 0\n", + " for i in range(6):\n", + " s = Rv[i+1] + m[i] + carry\n", + " Rv[i], carry = s & MASK, s >> WORD_BITS\n", + " s = Rv[6] + carry; Rv[5], carry = s & MASK, s >> WORD_BITS\n", + " Rv[6], Rv[7] = carry, 0\n", + " q = (Rv[0] * MU0) & MASK\n", + " pq, carry = [0]*6, 0\n", + " for i in range(5):\n", + " prod = q * P_WORDS[i] + carry\n", + " pq[i], carry = prod & MASK, prod >> WORD_BITS\n", + " pq[5] = carry\n", + " carry = 0\n", + " for i in range(6):\n", + " s = Rv[i] + pq[i] + carry\n", + " Rv[i], carry = s & MASK, s >> WORD_BITS\n", + " idx = 6\n", + " while carry and idx < 8:\n", + " s = Rv[idx] + carry\n", + " Rv[idx], carry = s & MASK, s >> WORD_BITS\n", + " idx += 1\n", + " out = Rv[1:5]\n", + " if gte(out, P_WORDS[:4]): out = sub(out, P_WORDS[:4])\n", + " return out\n", + "\n", + "def mont_big(x):\n", + " for _ in range(N):\n", + " m = (x & MASK) * MU0 & MASK\n", + " x = (x + m * P) >> WORD_BITS\n", + " return x - P if x >= P else x\n", + "\n", + "def logj_big(x):\n", + " for _ in range(N-1):\n", + " m = (x & MASK) * MU0 & MASK\n", + " x = (x + m * P) >> WORD_BITS\n", + " m = (x & MASK) * MU0 & MASK\n", + " x = (x + m * P) >> WORD_BITS\n", + " return x - P if x >= P else x\n", + "\n", + "if use_official:\n", + " mont_reduce_int = lambda z: from_words(mont_redc(to_words(int(z), 2*N)))\n", + " logjump_reduce_int = lambda z: from_words(mul_logjumps_sos(to_words(int(z), 2*N)))\n", + "else:\n", + " mont_reduce_int, logjump_reduce_int = mont_big, logj_big\n", + "\n", + "print(f\"Backend : {'GMP (gmpy2)' if has_gmp else 'Python int'}\")\n", + "print(f\"Variant : {'official limb loops' if use_official else 'big-int'}\\n\")\n", + "\n", + "rng = random.SystemRandom()\n", + "xs = [_int(rng.randrange(0, P * R)) for _ in range(10_000)]\n", + "single_m = timeit.timeit(\"mont_reduce_int(xs[0])\", globals=globals(), number=100_000)\n", + "single_l = timeit.timeit(\"logjump_reduce_int(xs[0])\", globals=globals(), number=100_000)\n", + "\n", + "setup = \"from __main__ import mont_reduce_int, logjump_reduce_int, xs\"\n", + "stmtm = \"for x in xs: mont_reduce_int(x)\"\n", + "stmtl = \"for x in xs: logjump_reduce_int(x)\"\n", + "best_m = min(timeit.repeat(stmtm, setup=setup, repeat=10, number=1))\n", + "best_l = min(timeit.repeat(stmtl, setup=setup, repeat=10, number=1))\n", + "ns_m = best_m * 1e9 / len(xs)\n", + "ns_l = best_l * 1e9 / len(xs)\n", + "\n", + "print(f\"[micro] Mont {single_m*1e6/100_000:8.3f} µs LogJump {single_l*1e6/100_000:8.3f} µs\")\n", + "print(f\"[batch] Mont {ns_m:8.1f} ns/op LogJump {ns_l:8.1f} ns/op speed-up {ns_m/ns_l:4.2f}×\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f3bdb937-aed0-4cce-bcaa-393a37315f03", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/math/reductions/montgomery_reduction.ipynb b/math/reductions/montgomery_reduction.ipynb new file mode 100644 index 0000000..b05014c --- /dev/null +++ b/math/reductions/montgomery_reduction.ipynb @@ -0,0 +1,454 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "503b796f-b8c1-4ee2-aeaa-2ef437616e5b", + "metadata": {}, + "source": [ + "# Understanding Montgomery Reduction\n", + "\n", + "## 1. Introduction: The Problem with Modular Division\n", + "\n", + "Modular arithmetic often involves computing remainders: for integers `a` and `n`, we find `q` and `r` such that:\n", + "\n", + "$$\n", + "a = qn + r,\\quad \\text{where} \\quad 0 \\leq r < |n|\n", + "$$\n", + "\n", + "Here, `r = a mod n`. While this is straightforward for small numbers, it becomes inefficient at scale, especially in cryptographic computations with large integers.\n", + "\n", + "For example, to compute:\n", + "\n", + "$$\n", + "(12 \\times 15) \\mod 7 = ((12 \\mod 7) \\times (15 \\mod 7)) \\mod 7 \\\\\n", + "= (5 \\times 1) \\mod 7 \\\\\n", + "= 5\n", + "$$\n", + "\n", + "We still need to perform `mod` operations (i.e., division), which are costly for large numbers. Since cryptographic systems rely heavily on modular multiplication, this repeated division becomes a bottleneck.\n", + "\n", + "**Montgomery reduction** addresses this by avoiding direct division, making modular multiplication more efficient for large integers." + ] + }, + { + "cell_type": "markdown", + "id": "aa5f36b9-54ca-47e9-9f67-8ebd469bfa9b", + "metadata": {}, + "source": [ + "## 2. The Core Idea: A New Domain for Faster Math\n", + "\n", + "Montgomery reduction speeds up modular arithmetic by moving calculations into a special **Montgomery domain**, avoiding costly division by `n`.\n", + "\n", + "This is done using a new modulus `R`, typically a power of 2 (like $2^{32}$ or $2^{64}$).\n", + "\n", + "### Why Use a Power of 2?\n", + "\n", + "Because computers handle powers of 2 efficiently:\n", + "\n", + "- **Division by `R`** → simple **right shift**\n", + "- **Modulo `R`** → fast **bitwise AND**\n", + "\n", + "This makes reductions much faster than regular division." + ] + }, + { + "cell_type": "markdown", + "id": "d4d16a4d-3090-4f68-b70f-fbde01fba594", + "metadata": {}, + "source": [ + "## 3. The Montgomery Algorithm: Setup and Multiplication\n", + "\n", + "With `R` as a power of 2, we set up the Montgomery system through a one-time preparation:\n", + "\n", + "### Setup Phase\n", + "\n", + "1. **Choose `R`**: \n", + " A power of 2 greater than `n`, enabling fast bitwise operations.\n", + "\n", + "2. **Compute $R^{-1}$**: \n", + " The modular inverse of `R` such that:\n", + "\n", + " $$\n", + " R \\cdot R^{-1} \\equiv 1 \\pmod{n}\n", + " $$\n", + "\n", + "3. **Compute `n'`**: \n", + " The modular inverse of `-n` modulo `R`, satisfying:\n", + "\n", + " $$\n", + " -n \\cdot n' \\equiv 1 \\pmod{R}\n", + " $$\n", + "\n", + "The values $R^{-1}$ and `n'` are precomputed once and used in all Montgomery operations." + ] + }, + { + "cell_type": "markdown", + "id": "ba12b99a-ca17-48dd-bb04-017d667bf186", + "metadata": {}, + "source": [ + "\n", + "### Conversion to Montgomery Form\n", + "\n", + "To work in the Montgomery domain, convert a number `a` to its Montgomery form `a'`:\n", + "\n", + "$$\n", + "a' = a \\cdot R \\pmod{n}\n", + "$$\n", + "\n", + "This step is a one-time, regular modular multiplication—our \"entry fee\" to faster computation.\n", + "\n", + "\n", + "### Multiplication in the Montgomery Domain\n", + "\n", + "Given `a'` and `b'` in Montgomery form, their product `c'` is:\n", + "\n", + "$$\n", + "c' = a' \\cdot b' \\cdot R^{-1} \\pmod{n}\n", + "$$\n", + "\n", + "While this still looks like it needs division by `n`, the **Montgomery Reduction (`REDC`)** algorithm efficiently handles this without actual division." + ] + }, + { + "cell_type": "markdown", + "id": "59f08ff6-4d46-4f3e-84d2-e9bf3ceadcbc", + "metadata": {}, + "source": [ + "## 4. The REDC Algorithm and Final Conversion\n", + "\n", + "To compute the Montgomery product \n", + "`c' = a' · b' · R⁻¹ mod(n)`, \n", + "we use the **`REDC`** function, which efficiently calculates \n", + "$T \\cdot R^{-1} \\mod n$ \n", + "for $T = a' \\cdot b'$.\n", + "\n", + "### The `REDC` Algorithm\n", + "\n", + "1. **Compute `m`:**\n", + "\n", + " $$\n", + " m = T \\bmod R \\cdot n’ \\bmod R\n", + " $$\n", + "\n", + "2. **Compute `t`:**\n", + "\n", + " $$\n", + " t = \\frac{T + m \\cdot n}{R}\n", + " $$\n", + "\n", + "3. **Final correction:**\n", + "\n", + " $$\n", + " \\text{If } t \\geq n, \\quad \\text{then } t = t - n\n", + " $$\n", + "\n", + "4. **Return `t` as the result.**\n", + "\n", + "The speed boost comes from dividing by `R` (a power of 2), which is just a fast bit shift.\n", + "\n", + "\n", + "### Conversion Back to Standard Form\n", + "\n", + "After all operations, the result `c'` is still in Montgomery form. \n", + "To convert back, apply one last `REDC`:\n", + "\n", + "$$\n", + "c = \\text{REDC}(c')\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "7ae90724-de55-420a-a930-206cd19f293f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "System Initialized for n=13\n", + "Calculated R=16, n'=11\n", + "------------------------------\n", + "7 in Montgomery form is: 8\n", + "8 in Montgomery form is: 11\n", + "------------------------------\n", + "Intermediate product T = 8 * 11 = 88\n", + "REDC(T) -> Product in Montgomery form: 12\n", + "------------------------------\n", + "Final Result (after converting back): 4\n", + "Standard Check: (7 * 8) % 13 = 4\n" + ] + } + ], + "source": [ + "class Montgomery:\n", + " def __init__(self, n):\n", + " if n % 2 == 0:\n", + " raise ValueError(\"Modulus n must be odd.\")\n", + " \n", + " self.n = n\n", + " self.logR = n.bit_length()\n", + " self.R = 1 << self.logR\n", + " self.R_mask = self.R - 1\n", + " \n", + " n_inv_R = self._modinv(self.n, self.R)\n", + " self.n_prime = self.R - n_inv_R\n", + "\n", + " def _egcd(self, a, b):\n", + " if a == 0:\n", + " return (b, 0, 1)\n", + " g, y, x = self._egcd(b % a, a)\n", + " return (g, x - (b // a) * y, y)\n", + "\n", + " def _modinv(self, a, m):\n", + " g, x, y = self._egcd(a, m)\n", + " if g != 1:\n", + " raise ValueError('Modular inverse does not exist')\n", + " return x % m\n", + " \n", + " def _reduce(self, T):\n", + " m = ((T & self.R_mask) * self.n_prime) & self.R_mask\n", + " t = (T + m * self.n) >> self.logR\n", + " \n", + " if t >= self.n:\n", + " return t - self.n\n", + " else:\n", + " return t\n", + "\n", + " def convert_in(self, x):\n", + " return (x * self.R) % self.n\n", + "\n", + " def convert_out(self, x_mont):\n", + " return self._reduce(x_mont)\n", + "\n", + " def multiply(self, a_mont, b_mont):\n", + " T = a_mont * b_mont\n", + " return self._reduce(T), T\n", + "\n", + "# --- Usage Example ---\n", + "# 1. One-time setup\n", + "monty_system = Montgomery(n=13)\n", + "print(f\"System Initialized for n={monty_system.n}\")\n", + "print(f\"Calculated R={monty_system.R}, n'={monty_system.n_prime}\")\n", + "print(\"-\" * 30)\n", + "\n", + "# 2. Convert numbers to Montgomery form\n", + "a = 7\n", + "b = 8\n", + "a_mont = monty_system.convert_in(a)\n", + "b_mont = monty_system.convert_in(b)\n", + "print(f\"{a} in Montgomery form is: {a_mont}\")\n", + "print(f\"{b} in Montgomery form is: {b_mont}\")\n", + "print(\"-\" * 30)\n", + "\n", + "# 3. Perform multiplication in the Montgomery domain\n", + "product_mont, intermediate_T = monty_system.multiply(a_mont, b_mont)\n", + "print(f\"Intermediate product T = {a_mont} * {b_mont} = {intermediate_T}\")\n", + "print(f\"REDC(T) -> Product in Montgomery form: {product_mont}\")\n", + "print(\"-\" * 30)\n", + "\n", + "# 4. Convert the result back to a standard number\n", + "final_result = monty_system.convert_out(product_mont)\n", + "print(f\"Final Result (after converting back): {final_result}\")\n", + "print(f\"Standard Check: (7 * 8) % 13 = {(7 * 8) % 13}\")" + ] + }, + { + "cell_type": "markdown", + "id": "0f157745-ee68-40a8-aab4-74f51d754242", + "metadata": {}, + "source": [ + "\n", + "### A Quick Clarification: Why Does Montgomery Seem Slower at First?\n", + "\n", + "> **Question:** \n", + "> Montgomery starts with divisions like 112 mod 13 or 128 mod 13, which look slower than a simple 56 mod 13. So why is it still preferred—and actually faster—when working with large numbers?\n", + "\n", + "For a single, small calculation, the setup cost of Montgomery reduction makes it **slower** than the standard method. \n", + "\n", + "The performance boost isn’t for one-off calculations; it’s for **chains of multiplications** performed with the same modulus, which is extremely common in cryptography. The classic use case is **modular exponentiation** (`a^e mod n`), which is the core of RSA and the example we will explore next." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "1f505948-24e8-4bfa-9c27-2b89cf349bc8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--- Starting Standard Modular Exponentiation ---\n", + "Executing for exponent 10 (binary: 1010)\n", + "\n", + "Step 1 (Square): (1*1) % 13 -> 1 (EXPENSIVE)\n", + "Step 1 (Mult): (1*5) % 13 -> 5 (EXPENSIVE)\n", + "Step 2 (Square): (5*5) % 13 -> 12 (EXPENSIVE)\n", + "Step 3 (Square): (12*12) % 13 -> 1 (EXPENSIVE)\n", + "Step 3 (Mult): (1*5) % 13 -> 5 (EXPENSIVE)\n", + "Step 4 (Square): (5*5) % 13 -> 12 (EXPENSIVE)\n", + "\n", + "Final Result: 12\n", + "Total Expensive (mod n) Operations: 6\n", + "\n", + "==================================================\n", + "--- Starting Montgomery Modular Exponentiation ---\n", + "Step 1: Setup & Initial Conversion\n", + " - Converting 1 -> 3 (EXPENSIVE OP #1)\n", + " - Converting 5 -> 2 (EXPENSIVE OP #2)\n", + "\n", + "Step 2: Main loop with FAST operations\n", + "Loop 1 (Square): REDC(3*3) -> 3 (FAST)\n", + "Loop 1 (Mult): REDC(3*2) -> 2 (FAST)\n", + "Loop 2 (Square): REDC(2*2) -> 10 (FAST)\n", + "Loop 3 (Square): REDC(10*10) -> 3 (FAST)\n", + "Loop 3 (Mult): REDC(3*2) -> 2 (FAST)\n", + "Loop 4 (Square): REDC(2*2) -> 10 (FAST)\n", + "\n", + "Step 3: Final conversion -> REDC(10) -> 12 (FAST)\n", + "\n", + "Final Result: 12\n", + "Total Expensive (mod n) Operations: 2\n", + "\n" + ] + } + ], + "source": [ + "# Re-using our clean Montgomery class\n", + "class Montgomery:\n", + " def __init__(self, n):\n", + " if n % 2 == 0: raise ValueError(\"Modulus n must be odd.\")\n", + " self.n = n\n", + " self.logR = n.bit_length()\n", + " self.R = 1 << self.logR\n", + " self.R_mask = self.R - 1\n", + " n_inv_R = self._modinv(self.n, self.R)\n", + " self.n_prime = self.R - n_inv_R\n", + "\n", + " def _egcd(self, a, b):\n", + " if a == 0: return (b, 0, 1)\n", + " g, y, x = self._egcd(b % a, a)\n", + " return (g, x - (b // a) * y, y)\n", + "\n", + " def _modinv(self, a, m):\n", + " g, x, y = self._egcd(a, m)\n", + " if g != 1: raise ValueError('Modular inverse does not exist')\n", + " return x % m\n", + " \n", + " def _reduce(self, T):\n", + " m = ((T & self.R_mask) * self.n_prime) & self.R_mask\n", + " t = (T + m * self.n) >> self.logR\n", + " return t - self.n if t >= self.n else t\n", + "\n", + " def convert_in(self, x):\n", + " return (x * self.R) % self.n\n", + "\n", + " def convert_out(self, x_mont):\n", + " return self._reduce(x_mont)\n", + "\n", + " def multiply(self, a_mont, b_mont):\n", + " return self._reduce(a_mont * b_mont)\n", + "\n", + "# --- Method 1: Standard Exponentiation Trace ---\n", + "def trace_standard_pow_clean(base, exp, mod):\n", + " print(\"--- Starting Standard Modular Exponentiation ---\")\n", + " expensive_ops = 0\n", + " res = 1\n", + " binary_exp = bin(exp)[2:]\n", + " print(f\"Executing for exponent {exp} (binary: {binary_exp})\\n\")\n", + " \n", + " for i, bit in enumerate(binary_exp):\n", + " # Squaring step\n", + " res_old = res\n", + " res = (res * res) % mod\n", + " expensive_ops += 1\n", + " print(f\"Step {i+1} (Square): ({res_old}*{res_old}) % {mod} -> {res} (EXPENSIVE)\")\n", + " \n", + " # Multiplication step if bit is 1\n", + " if bit == '1':\n", + " res_old = res\n", + " res = (res * base) % mod\n", + " expensive_ops += 1\n", + " print(f\"Step {i+1} (Mult): ({res_old}*{base}) % {mod} -> {res} (EXPENSIVE)\")\n", + " \n", + " print(f\"\\nFinal Result: {res}\")\n", + " print(f\"Total Expensive (mod n) Operations: {expensive_ops}\\n\")\n", + "\n", + "# --- Method 2: Montgomery Exponentiation Trace ---\n", + "def trace_montgomery_pow_clean(base, exp, n):\n", + " print(\"--- Starting Montgomery Modular Exponentiation ---\")\n", + " expensive_ops = 0\n", + " \n", + " # 1. Setup & Conversion\n", + " print(\"Step 1: Setup & Initial Conversion\")\n", + " monty = Montgomery(n)\n", + " res_mont = monty.convert_in(1)\n", + " expensive_ops += 1\n", + " base_mont = monty.convert_in(base)\n", + " expensive_ops += 1\n", + " print(f\" - Converting 1 -> {res_mont} (EXPENSIVE OP #{expensive_ops-1})\")\n", + " print(f\" - Converting {base} -> {base_mont} (EXPENSIVE OP #{expensive_ops})\\n\")\n", + "\n", + " # 2. Main Loop\n", + " print(\"Step 2: Main loop with FAST operations\")\n", + " binary_exp = bin(exp)[2:]\n", + " for i, bit in enumerate(binary_exp):\n", + " # Squaring step\n", + " res_old = res_mont\n", + " res_mont = monty.multiply(res_mont, res_mont)\n", + " print(f\"Loop {i+1} (Square): REDC({res_old}*{res_old}) -> {res_mont} (FAST)\")\n", + "\n", + " # Multiplication step if bit is 1\n", + " if bit == '1':\n", + " res_old = res_mont\n", + " res_mont = monty.multiply(res_mont, base_mont)\n", + " print(f\"Loop {i+1} (Mult): REDC({res_old}*{base_mont}) -> {res_mont} (FAST)\")\n", + "\n", + " # 3. Final Conversion\n", + " final_res = monty.convert_out(res_mont)\n", + " print(f\"\\nStep 3: Final conversion -> REDC({res_mont}) -> {final_res} (FAST)\")\n", + " \n", + " print(f\"\\nFinal Result: {final_res}\")\n", + " print(f\"Total Expensive (mod n) Operations: {expensive_ops}\\n\")\n", + "\n", + "# --- Run the cleaned traces ---\n", + "base, exp, mod = 5, 10, 13\n", + "trace_standard_pow_clean(base, exp, mod)\n", + "print(\"=\"*50)\n", + "trace_montgomery_pow_clean(base, exp, mod)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1d3e449-981e-4ce6-a671-7b0f221e8090", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}