diff --git a/scripts/G2_Points.ipynb b/scripts/G2_Points.ipynb new file mode 100644 index 00000000..fe9bb9e4 --- /dev/null +++ b/scripts/G2_Points.ipynb @@ -0,0 +1,611 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "2ff1a411", + "metadata": {}, + "outputs": [], + "source": [ + "import inspect\n", + "\n", + "# Accelerate arithmetic by accepting probabilistic proofs\n", + "from sage.structure.proof.all import arithmetic\n", + "arithmetic(False)\n", + "\n", + "def derive_BN_field(x):\n", + " params = {\n", + " 'param': x,\n", + " 'modulus': 36*x^4 + 36*x^3 + 24*x^2 + 6*x + 1,\n", + " 'order': 36*x^4 + 36*x^3 + 18*x^2 + 6*x + 1,\n", + " 'trace': 6*x^2 + 1,\n", + " 'family': 'BN'\n", + " }\n", + " return params\n", + "\n", + "def derive_BLS12_field(x):\n", + " params = {\n", + " 'param': x,\n", + " 'modulus': (x - 1)^2 * (x^4 - x^2 + 1)//3 + x,\n", + " 'order': x^4 - x^2 + 1,\n", + " 'trace': x + 1,\n", + " 'family': 'BLS12'\n", + " }\n", + " return params\n", + "\n", + "def derive_BW6_compose_BLS12_field(x, cofactor_trace, cofactor_y):\n", + " # Brezing-Weng input\n", + " r = (x^6 - 2*x^5 + 2*x^3 + x + 1) // 3 # BLS12 modulus\n", + "\n", + " # 6-th root of unity output + cofactors\n", + " t = x^5 - 3*x^4 + 3*x^3 - x + 3 + cofactor_trace*r\n", + " y = (x^5 - 3*x^4 + 3*x^3 - x + 3)//3 + cofactor_y*r\n", + "\n", + " # Curve parameters\n", + " p = (t^2 + 3*y^2)/4\n", + " trace = p+1-r # (3*y+t)/2\n", + "\n", + " params = {\n", + " 'param': x,\n", + " 'modulus': p,\n", + " 'order': r,\n", + " 'trace': trace,\n", + " 'family': 'BW6'\n", + " }\n", + " return params\n", + "\n", + "def copyright():\n", + " return inspect.cleandoc(\"\"\"\n", + " # Constantine\n", + " # Copyright (c) 2018-2019 Status Research & Development GmbH\n", + " # Copyright (c) 2020-Present Mamy André-Ratsimbazafy\n", + " # Licensed and distributed under either of\n", + " # * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).\n", + " # * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).\n", + " # at your option. This file may not be copied, modified, or distributed except according to those terms.\n", + " \"\"\")\n", + "\n", + "Curves = {\n", + " 'BN254_Nogami': {\n", + " 'field': derive_BN_field(-(2^62 + 2^55 + 1)),\n", + " 'curve': {\n", + " 'form': 'short_weierstrass',\n", + " 'a': 0,\n", + " 'b': 2\n", + " },\n", + " 'tower': {\n", + " 'embedding_degree': 12,\n", + " 'twist_degree': 6,\n", + " 'QNR_Fp': -1,\n", + " 'SNR_Fp2': [1, 1],\n", + " 'twist': 'D_Twist'\n", + " }\n", + " },\n", + " 'BN254_Snarks': {\n", + " 'field': derive_BN_field(Integer('0x44e992b44a6909f1')),\n", + " 'curve': {\n", + " 'form': 'short_weierstrass',\n", + " 'a': 0,\n", + " 'b': 3\n", + " },\n", + " 'tower': {\n", + " 'embedding_degree': 12,\n", + " 'twist_degree': 6,\n", + " 'QNR_Fp': -1,\n", + " 'SNR_Fp2': [9, 1],\n", + " 'twist': 'D_Twist'\n", + " }\n", + " },\n", + " 'BLS12_377': {\n", + " 'field': derive_BLS12_field(3 * 2^46 * (7 * 13 * 499) + 1),\n", + " 'curve': {\n", + " 'form': 'short_weierstrass',\n", + " 'a': 0,\n", + " 'b': 1\n", + " },\n", + " 'tower': {\n", + " 'embedding_degree': 12,\n", + " 'twist_degree': 6,\n", + " 'QNR_Fp': -5,\n", + " 'SNR_Fp2': [0, 1],\n", + " 'twist': 'D_Twist'\n", + " }\n", + " },\n", + " 'BLS12_381': {\n", + " 'field': derive_BLS12_field(-(2^63 + 2^62 + 2^60 + 2^57 + 2^48 + 2^16)),\n", + " 'curve': {\n", + " 'form': 'short_weierstrass',\n", + " 'a': 0,\n", + " 'b': 4\n", + " },\n", + " 'tower': {\n", + " 'embedding_degree': 12,\n", + " 'twist_degree': 6,\n", + " 'QNR_Fp': -1,\n", + " 'SNR_Fp2': [1, 1],\n", + " 'twist': 'M_Twist'\n", + " }\n", + " },\n", + " 'BW6_761': {\n", + " 'field': derive_BW6_compose_BLS12_field(\n", + " 3 * 2^46 * (7 * 13 * 499) + 1,\n", + " cofactor_trace = 13,\n", + " cofactor_y = 9\n", + " ),\n", + " 'curve': {\n", + " 'form': 'short_weierstrass',\n", + " 'a': 0,\n", + " 'b': -1\n", + " },\n", + " 'tower': {\n", + " 'embedding_degree': 6,\n", + " 'twist_degree': 6,\n", + " 'SNR_Fp': -4,\n", + " 'twist': 'M_Twist'\n", + " }\n", + " },\n", + " 'Pallas': {\n", + " 'field': {\n", + " 'modulus': Integer('0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001'),\n", + " 'order': Integer('0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001')\n", + " },\n", + " 'curve': {\n", + " 'form': 'short_weierstrass',\n", + " 'a': 0,\n", + " 'b': 5\n", + " }\n", + " },\n", + " 'Vesta': {\n", + " 'field': {\n", + " 'modulus': Integer('0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001'),\n", + " 'order': Integer('0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001'),\n", + " },\n", + " 'curve': {\n", + " 'form': 'short_weierstrass',\n", + " 'a': 0,\n", + " 'b': 5\n", + " }\n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "e06e3c6b", + "metadata": {}, + "outputs": [], + "source": [ + "def genScalarMulG2(curve_name, curve_config, count, seed, scalarBits = None):\n", + " p = curve_config[curve_name]['field']['modulus']\n", + " r = curve_config[curve_name]['field']['order']\n", + " form = curve_config[curve_name]['curve']['form']\n", + " a = curve_config[curve_name]['curve']['a']\n", + " b = curve_config[curve_name]['curve']['b']\n", + " embedding_degree = curve_config[curve_name]['tower']['embedding_degree']\n", + " twist_degree = curve_config[curve_name]['tower']['twist_degree']\n", + " twist = curve_config[curve_name]['tower']['twist']\n", + "\n", + "\n", + " G2_field_degree = embedding_degree // twist_degree\n", + " G2_field = f'Fp{G2_field_degree}' if G2_field_degree > 1 else 'Fp'\n", + "\n", + "\n", + " if G2_field_degree == 2:\n", + " non_residue_fp = curve_config[curve_name]['tower']['QNR_Fp']\n", + " elif G2_field_degree == 1:\n", + " if twist_degree == 6:\n", + " # Only for complete serialization\n", + " non_residue_fp = curve_config[curve_name]['tower']['SNR_Fp']\n", + " else:\n", + " raise NotImplementedError()\n", + " else:\n", + " raise NotImplementedError()\n", + "\n", + "\n", + " Fp = GF(p)\n", + " K. = PolynomialRing(Fp)\n", + "\n", + "\n", + " if G2_field == 'Fp2':\n", + " Fp2. = Fp.extension(u^2 - non_residue_fp)\n", + " G2F = Fp2\n", + " if twist_degree == 6:\n", + " non_residue_twist = curve_config[curve_name]['tower']['SNR_Fp2']\n", + " else:\n", + " raise NotImplementedError()\n", + " elif G2_field == 'Fp':\n", + " G2F = Fp\n", + " if twist_degree == 6:\n", + " non_residue_twist = curve_config[curve_name]['tower']['SNR_Fp']\n", + " else:\n", + " raise NotImplementedError()\n", + " else:\n", + " raise NotImplementedError()\n", + "\n", + "\n", + " if twist == 'D_Twist':\n", + " G2 = EllipticCurve(G2F, [0, b/G2F(non_residue_twist)])\n", + " elif twist == 'M_Twist':\n", + " G2 = EllipticCurve(G2F, [0, b*G2F(non_residue_twist)])\n", + " else:\n", + " raise ValueError('G2 must be a D_Twist or M_Twist but found ' + twist)\n", + "\n", + "\n", + " cofactor = G2.order() // r\n", + "\n", + "\n", + " out = {\n", + " 'curve': curve_name,\n", + " 'group': 'G2',\n", + " 'modulus': serialize_bigint(p),\n", + " 'order': serialize_bigint(r),\n", + " 'cofactor': serialize_bigint(cofactor),\n", + " 'form': form,\n", + " 'twist_degree': int(twist_degree),\n", + " 'twist': twist,\n", + " 'non_residue_fp': int(non_residue_fp),\n", + " 'G2_field': G2_field,\n", + " 'non_residue_twist': [int(coord) for coord in non_residue_twist] if isinstance(non_residue_twist, list) else int(non_residue_twist)\n", + " }\n", + " if form == 'short_weierstrass':\n", + " out['a'] = serialize_bigint(a)\n", + " out['b'] = serialize_bigint(b)\n", + "\n", + "\n", + " vectors = []\n", + " set_random_seed(seed)\n", + " for i in progressbar(range(count)):\n", + " v = {}\n", + " P = G2.random_point()\n", + " scalar = randrange(1 << scalarBits) if scalarBits else randrange(r)\n", + "\n", + "\n", + " P *= cofactor # clear cofactor\n", + " Q = scalar * P\n", + "\n", + "\n", + " v['id'] = i\n", + " if G2_field == 'Fp2':\n", + " v['P'] = serialize_EC_Fp2(P)\n", + " v['scalarBits'] = scalarBits if scalarBits else r.bit_length()\n", + " v['scalar'] = serialize_bigint(scalar)\n", + " v['Q'] = serialize_EC_Fp2(Q)\n", + " elif G2_field == 'Fp':\n", + " v['P'] = serialize_EC_Fp(P)\n", + " v['scalarBits'] = scalarBits if scalarBits else r.bit_length()\n", + " v['scalar'] = serialize_bigint(scalar)\n", + " v['Q'] = serialize_EC_Fp(Q)\n", + " vectors.append(v)\n", + "\n", + "\n", + " out['vectors'] = vectors\n", + " return out" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "97802950", + "metadata": {}, + "outputs": [], + "source": [ + "def serialize_bigint(x):\n", + " return '0x' + Integer(x).hex()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0d775894", + "metadata": {}, + "outputs": [], + "source": [ + "from sage.structure.proof.all import arithmetic" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "04014569", + "metadata": {}, + "outputs": [], + "source": [ + "def progressbar(it, prefix=\"\", size=60, file=sys.stdout):\n", + " count = len(it)\n", + " def show(j):\n", + " x = int(size*j/count)\n", + " file.write(\"%s[%s%s] %i/%i\\r\" % (prefix, \"#\"*x, \".\"*(size-x), j, count))\n", + " file.flush()\n", + " show(0)\n", + " for i, item in enumerate(it):\n", + " yield item\n", + " show(i+1)\n", + " file.write(\"\\n\")\n", + " file.flush()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0ee19fe0", + "metadata": {}, + "outputs": [], + "source": [ + "def serialize_EC_Fp2(P):\n", + " (Px, Py, Pz) = P\n", + " Px = vector(Px)\n", + " Py = vector(Py)\n", + " coords = {\n", + " 'x': {\n", + " 'c0': serialize_bigint(Px[0]),\n", + " 'c1': serialize_bigint(Px[1])\n", + " },\n", + " 'y': {\n", + " 'c0': serialize_bigint(Py[0]),\n", + " 'c1': serialize_bigint(Py[1])\n", + " }\n", + " }\n", + " return coords" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "eb41c5e0", + "metadata": {}, + "outputs": [], + "source": [ + "def genAddG2(curve_name, curve_config, count, seed, scalarBits = None):\n", + " p = curve_config[curve_name]['field']['modulus']\n", + " r = curve_config[curve_name]['field']['order']\n", + " form = curve_config[curve_name]['curve']['form']\n", + " a = curve_config[curve_name]['curve']['a']\n", + " b = curve_config[curve_name]['curve']['b']\n", + " embedding_degree = curve_config[curve_name]['tower']['embedding_degree']\n", + " twist_degree = curve_config[curve_name]['tower']['twist_degree']\n", + " twist = curve_config[curve_name]['tower']['twist']\n", + "\n", + "\n", + " G2_field_degree = embedding_degree // twist_degree\n", + " G2_field = f'Fp{G2_field_degree}' if G2_field_degree > 1 else 'Fp'\n", + "\n", + "\n", + " if G2_field_degree == 2:\n", + " non_residue_fp = curve_config[curve_name]['tower']['QNR_Fp']\n", + " elif G2_field_degree == 1:\n", + " if twist_degree == 6:\n", + " # Only for complete serialization\n", + " non_residue_fp = curve_config[curve_name]['tower']['SNR_Fp']\n", + " else:\n", + " raise NotImplementedError()\n", + " else:\n", + " raise NotImplementedError()\n", + "\n", + "\n", + " Fp = GF(p)\n", + " K. = PolynomialRing(Fp)\n", + "\n", + "\n", + " if G2_field == 'Fp2':\n", + " Fp2. = Fp.extension(u^2 - non_residue_fp)\n", + " G2F = Fp2\n", + " if twist_degree == 6:\n", + " non_residue_twist = curve_config[curve_name]['tower']['SNR_Fp2']\n", + " else:\n", + " raise NotImplementedError()\n", + " elif G2_field == 'Fp':\n", + " G2F = Fp\n", + " if twist_degree == 6:\n", + " non_residue_twist = curve_config[curve_name]['tower']['SNR_Fp']\n", + " else:\n", + " raise NotImplementedError()\n", + " else:\n", + " raise NotImplementedError()\n", + "\n", + "\n", + " if twist == 'D_Twist':\n", + " G2 = EllipticCurve(G2F, [0, b/G2F(non_residue_twist)])\n", + " elif twist == 'M_Twist':\n", + " G2 = EllipticCurve(G2F, [0, b*G2F(non_residue_twist)])\n", + " else:\n", + " raise ValueError('G2 must be a D_Twist or M_Twist but found ' + twist)\n", + "\n", + "\n", + " cofactor = G2.order() // r\n", + "\n", + "\n", + " out = {\n", + " 'curve': curve_name,\n", + " 'group': 'G2',\n", + " 'modulus': serialize_bigint(p),\n", + " 'order': serialize_bigint(r),\n", + " 'cofactor': serialize_bigint(cofactor),\n", + " 'form': form,\n", + " 'twist_degree': int(twist_degree),\n", + " 'twist': twist,\n", + " 'non_residue_fp': int(non_residue_fp),\n", + " 'G2_field': G2_field,\n", + " 'non_residue_twist': [int(coord) for coord in non_residue_twist] if isinstance(non_residue_twist, list) else int(non_residue_twist)\n", + " }\n", + " if form == 'short_weierstrass':\n", + " out['a'] = serialize_bigint(a)\n", + " out['b'] = serialize_bigint(b)\n", + "\n", + "\n", + " vectors = []\n", + " set_random_seed(seed)\n", + " for i in progressbar(range(count)):\n", + " v = {}\n", + " P1 = G2.random_point()\n", + " P2 = -P1\n", + "\n", + "\n", + " P1 *= cofactor # clear cofactor\n", + " P2 *= cofactor\n", + "\n", + " Q = P1 + P1 + P1 \n", + "\n", + "\n", + " v['id'] = i\n", + " if G2_field == 'Fp2':\n", + " v['P1'] = serialize_EC_Fp2(P1)\n", + " v['P2'] = serialize_EC_Fp2(P2)\n", + " v['Q'] = serialize_EC_Fp2(Q)\n", + " elif G2_field == 'Fp':\n", + " v['P1'] = serialize_EC_Fp2(P1)\n", + " v['P2'] = serialize_EC_Fp2(P2)\n", + " v['Q'] = serialize_EC_Fp(Q)\n", + " vectors.append(v)\n", + "\n", + "\n", + " out['vectors'] = vectors\n", + " return out" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "a3fc218d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[############################################################] 1/1\n" + ] + }, + { + "data": { + "text/plain": [ + "{'curve': 'BN254_Snarks',\n", + " 'group': 'G2',\n", + " 'modulus': '0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47',\n", + " 'order': '0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001',\n", + " 'cofactor': '0x30644e72e131a029b85045b68181585e06ceecda572a2489345f2299c0f9fa8d',\n", + " 'form': 'short_weierstrass',\n", + " 'twist_degree': 6,\n", + " 'twist': 'D_Twist',\n", + " 'non_residue_fp': -1,\n", + " 'G2_field': 'Fp2',\n", + " 'non_residue_twist': [9, 1],\n", + " 'a': '0x0',\n", + " 'b': '0x3',\n", + " 'vectors': [{'id': 0,\n", + " 'P1': {'x': {'c0': '0x1536b2e79d6f2116ce8f06e33cb7997612d2514df64a41e1e5fae866b1c0d779',\n", + " 'c1': '0x1f60381668f1c1d04cc11964dd3937c2ef0b11db4bbac18501c3ccd53ec87c6e'},\n", + " 'y': {'c0': '0x2dfe2cb093eafecb76994ca8cabb488d3171747c4e9fe6b3afc2123e31ac9c6f',\n", + " 'c1': '0x1bddcd8f468cc2657ef23699b3bc07a16c314cd67d46c8dbb9372985c2dcc8ec'}},\n", + " 'P2': {'x': {'c0': '0x1536b2e79d6f2116ce8f06e33cb7997612d2514df64a41e1e5fae866b1c0d779',\n", + " 'c1': '0x1f60381668f1c1d04cc11964dd3937c2ef0b11db4bbac18501c3ccd53ec87c6e'},\n", + " 'y': {'c0': '0x26621c24d46a15e41b6f90db6c60fd0660ff61519d1e3d98c5e79d8a6d060d8',\n", + " 'c1': '0x148680e39aa4ddc4395e0f1ccdc550bc2b501dbaeb2b01b182e9629115a0345b'}},\n", + " 'Q': {'x': {'c0': '0x2bcdbbafb3db4a1748d74f93d44f5ce6048db29f84891551445a4e0df9529f23',\n", + " 'c1': '0x1a9297c469a211acbf8097b563d44a6271303d7642a3d0fce718665cf3a1d0a9'},\n", + " 'y': {'c0': '0x2de2dc5f1a2fa03ae5e0a2a005a50e16ed96f84190374247fdce23168936fcf1',\n", + " 'c1': '0x5806bf069d9add525afc8d7db7c37c81822de782addec36f4596a3dd63490e9'}}}]}" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "genAddG2('BN254_Snarks',Curves, 1,3)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "d919ea26", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[############################################################] 2/2\n" + ] + }, + { + "data": { + "text/plain": [ + "{'curve': 'BN254_Snarks',\n", + " 'group': 'G2',\n", + " 'modulus': '0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47',\n", + " 'order': '0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001',\n", + " 'cofactor': '0x30644e72e131a029b85045b68181585e06ceecda572a2489345f2299c0f9fa8d',\n", + " 'form': 'short_weierstrass',\n", + " 'twist_degree': 6,\n", + " 'twist': 'D_Twist',\n", + " 'non_residue_fp': -1,\n", + " 'G2_field': 'Fp2',\n", + " 'non_residue_twist': [9, 1],\n", + " 'a': '0x0',\n", + " 'b': '0x3',\n", + " 'vectors': [{'id': 0,\n", + " 'P': {'x': {'c0': '0x2d8a913eeee7c28aa12c81a2dbf4e8a753b7745c4910254a0404f09c4f36d867',\n", + " 'c1': '0x686e06cae2b68521cfe51921e30ef9291eeee283f1b3b503a1c8c8f70b86017'},\n", + " 'y': {'c0': '0x1b1210574a3c68090fbaa2c595adcf2d5b0dadbaba73796d4f56f0c5aba15bfa',\n", + " 'c1': '0x179448931f57e3bff2dbbc2f394afa1ba523ec54ca8aabd344095d98ed99ce90'}},\n", + " 'scalarBits': 254,\n", + " 'scalar': '0x2f3ee6ad9ed78b75bf31c54d279ec79494a54793bacbffc439f186ece6f08d3f',\n", + " 'Q': {'x': {'c0': '0x24215fc5a8eb0a718c7d9bfe8cd257c661ad0951abf5c9905f3cb182f9f38d71',\n", + " 'c1': '0xf699078f604cf68bd76a78b7e071d7af2588901489a05d110588aeb35fc1d93'},\n", + " 'y': {'c0': '0xa771b9d173b01a3aac71d109933e47dda4eafa3033c89d637c89c800f4eac81',\n", + " 'c1': '0xeaa8f99cf0eebafddedaaedc95b8003e3255fc584a84e37664179bba510b5d'}}},\n", + " {'id': 1,\n", + " 'P': {'x': {'c0': '0x96d5e6af840ba27b51f9bd326ae18c17135f6f7c5aa2ac7579840113888c0',\n", + " 'c1': '0x4d2463026cb19a0e51a55aee7949ed5b7807056e5348c16a4656e3ab35973bc'},\n", + " 'y': {'c0': '0x240a69ceabd8f50bc18cb07d52f40cfc4f88d493471815f5214b204bd34100a5',\n", + " 'c1': '0x1634ac6acf25be203d8b61a06297d60d00d2143df741847cb10b278eb5fb12cc'}},\n", + " 'scalarBits': 254,\n", + " 'scalar': '0x284812b537be5f93b6e71c951db201ab8206195a90d61b08dfcb02444beca7a7',\n", + " 'Q': {'x': {'c0': '0x175ee8ec116f531ccbbefa19d7158e76817a6ee1730e4fadca89dbee55aad9c0',\n", + " 'c1': '0x13206e9631dd12bc3281e3eb5513d2588325e77dd3df229dd36e50e45768f952'},\n", + " 'y': {'c0': '0x22b5174fd5843aae5aaa432e503b425cdce1b698e7c5deba8529fd7cdfadb8d5',\n", + " 'c1': '0x2dd49e88eb4abc2c27ba786d37c21a031c0a2c4b1ed786a6427565caabe170b0'}}}]}" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "genScalarMulG2('BN254_Snarks', Curves, 2, 4)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a91362f", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "SageMath 10.2", + "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.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}