Jekyll2023-07-20T00:37:54+00:00/feed.xmlepistemologistRandom musings on whatever I feelSome Notes on Block Ciphers2023-07-19T00:00:00+00:002023-07-19T00:00:00+00:00/2023/07/19/block-cipher-notes<h1 id="some-notes-on-the-cryptanalysis-of-block-ciphers">Some Notes on the Cryptanalysis of Block Ciphers</h1> <p>The following are some notes on the cryptanalysis of various block ciphers - most from the <a href="https://www.schneier.com/wp-content/uploads/2016/02/paper-self-study.pdf">self-study course by Bruce Schneier</a>.</p> <h2 id="rotationless-rc5">Rotationless RC5</h2> <h3 id="algorithm">Algorithm</h3> <p>Here, we ignore the key expansion step instead letting <code class="language-plaintext highlighter-rouge">S</code> be the expanded subkeys. The encryption goes as follows:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">RC5_ENCRYPT_NO_ROTATION</span><span class="p">(</span><span class="n">WORD</span> <span class="o">*</span><span class="n">pt</span><span class="p">,</span> <span class="n">WORD</span> <span class="o">*</span><span class="n">ct</span><span class="p">)</span> <span class="p">{</span> <span class="n">WORD</span> <span class="n">i</span><span class="p">,</span> <span class="n">A</span> <span class="o">=</span> <span class="n">pt</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="n">S</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">B</span> <span class="o">=</span> <span class="n">pt</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="n">S</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span> <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">r</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="n">A</span> <span class="o">=</span> <span class="p">(</span><span class="n">A</span> <span class="o">^</span> <span class="n">B</span><span class="p">)</span> <span class="o">+</span> <span class="n">S</span><span class="p">[</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="p">];</span> <span class="n">B</span> <span class="o">=</span> <span class="p">(</span><span class="n">B</span> <span class="o">^</span> <span class="n">A</span><span class="p">)</span> <span class="o">+</span> <span class="n">S</span><span class="p">[</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">];</span> <span class="p">}</span> <span class="n">ct</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">A</span><span class="p">;</span> <span class="n">ct</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">B</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>Note that for bitwise XOR and addition, the least significant bits of the result only dependon the least significant bits of the operands. More explicitly, given two words <code class="language-plaintext highlighter-rouge">A</code> and <code class="language-plaintext highlighter-rouge">B</code> of bit length <code class="language-plaintext highlighter-rouge">w</code>, we have for any $$k \le w$$ that</p> $\left( A \oplus B \right) \% 2^k = \left( (A \% 2^k) \oplus (B \% 2^k) \right) \\ \left( A + B \right) \% 2^k = \left( (A \% 2^k) + (B \% 2^k) \right)$ <p>Since these are the only operations used by the encryption algorithm, this proprety extends to the entire algorithm. Therefore, to recover the subkeys, we can simply bruteforce over all possibilities starting from the least significant bit. Some pseudocode for this algorithm is given below:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Given: list of equations of the form enc(pt_i) = ct_i curr_bit = 1 curr_subkey_guess = [all 1 bit possibilities for subkeys] while curr_bit &lt;= w: filter out any subkey guesses that don't conform to the given equations extend all guesses by 1 bit curr_bit += 1 </code></pre></div></div> <p><a href="https://gist.github.com/epistemologist/b2e3bc95c4489a8fe747f90fbdfbfd8c">Here</a> is a Python implementation of this attack on a reduced 4 round version of this cipher; however, the general attack strategy remains the same for any number of rounds.</p> <h2 id="des">DES</h2> <p>Here, we briefly describe the DES algorithm, again ignoring the key expansion steps (below is pseudocode in Python-like syntax):</p> <div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">subkeys</span> <span class="o">=</span> <span class="n">expand_key</span><span class="p">(</span><span class="n">key</span><span class="p">)</span> <span class="n">L</span><span class="p">,</span> <span class="n">R</span> <span class="o">=</span> <span class="n">apply_bit_permutation</span><span class="p">(</span><span class="n">plaintext</span><span class="p">,</span> <span class="n">IP</span><span class="p">)</span> <span class="k">def</span> <span class="nf">feistel</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">subkey</span><span class="p">):</span> <span class="n">x</span> <span class="o">=</span> <span class="n">expand</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="c1"># Expands x from 32 bits to 48 bits </span> <span class="n">x</span> <span class="o">^=</span> <span class="n">subkey</span> <span class="c1"># Key mixing </span> <span class="n">x</span> <span class="o">=</span> <span class="n">substitute</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="c1"># Apply S-boxes </span> <span class="n">x</span> <span class="o">=</span> <span class="n">apply_bit_permutation</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">P</span><span class="p">)</span> <span class="k">return</span> <span class="n">x</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">ROUNDS</span><span class="p">):</span> <span class="c1"># Typically 16 </span> <span class="n">L</span><span class="p">,</span> <span class="n">R</span> <span class="o">=</span> <span class="n">R</span><span class="p">,</span> <span class="n">L</span> <span class="o">^</span> <span class="n">feistel</span><span class="p">(</span><span class="n">R</span><span class="p">,</span> <span class="n">subkeys</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="n">ct</span> <span class="o">=</span> <span class="n">apply_bit_permutation</span><span class="p">(</span> <span class="n">R</span> <span class="o">||</span> <span class="n">L</span><span class="p">,</span> <span class="n">IP_INV</span><span class="p">)</span> </code></pre></div></div> <p>In order to assist with learning, <a href="https://gist.github.com/epistemologist/29f9de15312293ff0f946e873068f9c4">an implementation of DES</a> was made that allowed for the usage of normal Python integers as well as symbolic bit-vectors from the <a href="https://github.com/Z3Prover/z3">z3 library</a>. This allows us to represent the state of the entire cipher symbolically - for example, we can check that decryption is indeed the inverse of encryption:</p> <div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">des</span> <span class="kn">import</span> <span class="n">DES</span> <span class="kn">import</span> <span class="nn">z3</span> <span class="n">z3</span><span class="p">.</span><span class="n">set_param</span><span class="p">(</span><span class="s">'parallel.enable'</span><span class="p">,</span> <span class="bp">True</span><span class="p">)</span> <span class="n">z3</span><span class="p">.</span><span class="n">set_option</span><span class="p">(</span><span class="n">verbose</span><span class="o">=</span><span class="mi">20</span><span class="p">)</span> <span class="n">key</span> <span class="o">=</span> <span class="n">z3</span><span class="p">.</span><span class="n">BitVec</span><span class="p">(</span><span class="s">"k"</span><span class="p">,</span> <span class="mi">64</span><span class="p">)</span> <span class="n">pt</span> <span class="o">=</span> <span class="n">z3</span><span class="p">.</span><span class="n">BitVec</span><span class="p">(</span><span class="s">"p"</span><span class="p">,</span> <span class="mi">64</span><span class="p">)</span> <span class="n">cipher</span> <span class="o">=</span> <span class="n">DES</span><span class="p">(</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span><span class="p">,</span> <span class="n">rounds</span> <span class="o">=</span> <span class="mi">16</span><span class="p">)</span> <span class="n">s</span> <span class="o">=</span> <span class="n">z3</span><span class="p">.</span><span class="n">Solver</span><span class="p">()</span> <span class="n">s</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">pt</span> <span class="o">!=</span> <span class="n">cipher</span><span class="p">.</span><span class="n">decrypt</span><span class="p">(</span><span class="n">cipher</span><span class="p">.</span><span class="n">encrypt</span><span class="p">(</span><span class="n">pt</span><span class="p">)))</span> <span class="k">print</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">check</span><span class="p">())</span> </code></pre></div></div> <pre><code class="language-txt">(combined-solver "using solver 1") (simplifier :num-exprs 1 :num-asts 1015947 :time 9.03 :before-memory 135.27 :after-memory 266.42) simplifier (goal false) unsat </code></pre> <h3 id="des-with-no-s-boxes">DES with no S-Boxes</h3> <p>Note that in encrypytion, the only non-linear operation comes from the S-box. Therefore, removing it makes the entire encryption process affine. Let</p> \begin{align*} \text{DES}_{\text{NO_S_BOX}}: \mathbb{Z}_2^{56} \to (\mathbb{Z}_2^{64}\to \mathbb{Z}_2^{64}) \\ k \mapsto E_k: (p \mapsto c)\end{align*} <p>be our new encryption function. For any given key $$k \in \mathbb{Z}_2^{56}$$<br /> we have that the corresponding map $$E_k$$ is affine. We can therefore construct a linear map from $$E_k$$</p> <p>$$L(x) = E_k(x) \oplus E_k(0)$$.</p> <p>Note that given the encryptions of $$0$$ and all $$e_j$$ we can construct the matrix of this linear map: $$M=\begin{bmatrix} \vert &amp; \vert &amp; \cdots &amp; \vert &amp; \vert \\ f(\hat{e_1}) &amp; f(\hat{e_2}) &amp; \cdots &amp; f(\hat{e_{63}}) &amp; f(\hat{e_{64}}) \\ \vert &amp; \vert &amp; \cdots &amp; \vert &amp; \vert \\ \end{bmatrix}$$, where $$[\{\hat{e_j}\}_{0&lt;j\le64}] = I_{64}$$ is the identity matrix.</p> <p>It remains an exercise of algebra to construct the decryption map:</p> $D_k(x) = M^{-1}(x \oplus E_k(0))$ <p>Below is some Sage code that implements this. Note that DES uses 8 different S-boxes that each produce a 4 bit output from a 6 bit input. For this weakened version of DES, I made each of these S-boxes simply return the middle 4 bits of the 6 bit input (i.e. $$\mathtt{x} \mapsto \mathtt{(x \&amp; 011110_2)&gt;&gt;1}$$ )</p> <p>With this, we can see that our constructed decryption map correctly decrypts 10000 randomly chosen plaintexts.</p> <p><strong>TODO</strong>: Prove that this actually works with Z3.</p> <div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">des</span> <span class="kn">import</span> <span class="n">DES</span> <span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="nb">reduce</span> <span class="kn">import</span> <span class="nn">random</span> <span class="kn">from</span> <span class="nn">tqdm</span> <span class="kn">import</span> <span class="n">tqdm</span> <span class="kn">from</span> <span class="nn">sage.all</span> <span class="kn">import</span> <span class="n">GF</span><span class="p">,</span> <span class="n">matrix</span><span class="p">,</span> <span class="n">vector</span> <span class="n">IDENTITY_S_BOX</span> <span class="o">=</span> <span class="p">{</span><span class="n">i</span><span class="p">:</span> <span class="p">[(</span><span class="n">j</span> <span class="o">&amp;</span> <span class="mb">0b011110</span><span class="p">)</span> <span class="o">&gt;&gt;</span> <span class="mi">1</span> <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">64</span><span class="p">)]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">)}</span> <span class="k">def</span> <span class="nf">_popcnt</span><span class="p">(</span><span class="n">X</span><span class="p">):</span> <span class="k">return</span> <span class="nb">bin</span><span class="p">(</span><span class="n">X</span><span class="p">).</span><span class="n">count</span><span class="p">(</span><span class="s">'1'</span><span class="p">)</span> <span class="k">def</span> <span class="nf">_add_parity_bit</span><span class="p">(</span><span class="n">X</span><span class="p">):</span> <span class="k">return</span> <span class="p">(</span><span class="n">X</span> <span class="o">&lt;&lt;</span> <span class="mi">1</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="mi">1</span> <span class="k">if</span> <span class="n">_popcnt</span><span class="p">(</span><span class="n">X</span><span class="p">)</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">else</span> <span class="mi">0</span><span class="p">)</span> <span class="k">def</span> <span class="nf">gen_des_key</span><span class="p">(</span><span class="n">key_bits</span><span class="p">):</span> <span class="k">return</span> <span class="nb">reduce</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="n">x</span> <span class="o">|</span> <span class="n">y</span><span class="p">,</span> <span class="p">[</span><span class="n">_add_parity_bit</span><span class="p">(</span> <span class="p">(</span><span class="n">key_bits</span> <span class="o">&amp;</span> <span class="p">(</span><span class="mb">0b1111111</span> <span class="o">&lt;&lt;</span> <span class="p">(</span><span class="mi">7</span><span class="o">*</span><span class="n">i</span><span class="p">)))</span> <span class="o">&gt;&gt;</span> <span class="p">(</span><span class="mi">7</span><span class="o">*</span><span class="n">i</span><span class="p">))</span> <span class="o">&lt;&lt;</span> <span class="p">(</span><span class="mi">8</span><span class="o">*</span><span class="n">i</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">)])</span> <span class="n">key_bits</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="o">**</span><span class="mi">56</span><span class="p">)</span> <span class="n">des_key</span> <span class="o">=</span> <span class="n">gen_des_key</span><span class="p">(</span><span class="n">key_bits</span><span class="p">)</span> <span class="n">cipher</span> <span class="o">=</span> <span class="n">DES</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="n">des_key</span><span class="p">,</span> <span class="n">rounds</span><span class="o">=</span><span class="mi">16</span><span class="p">,</span> <span class="n">Sboxes</span><span class="o">=</span><span class="n">IDENTITY_S_BOX</span><span class="p">)</span> <span class="n">L</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">p</span><span class="p">:</span> <span class="n">cipher</span><span class="p">.</span><span class="n">encrypt</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="o">^</span> <span class="n">cipher</span><span class="p">.</span><span class="n">encrypt</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="c1"># Construct a matrix representing L </span><span class="n">_to_bit_vec</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">X</span><span class="p">:</span> <span class="p">[(</span><span class="n">X</span> <span class="o">&amp;</span> <span class="p">(</span><span class="mi">1</span><span class="o">&lt;&lt;</span><span class="n">i</span><span class="p">))</span> <span class="o">&gt;&gt;</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">64</span><span class="p">)]</span> <span class="n">_from_bit_vec</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">v</span><span class="p">:</span> <span class="nb">reduce</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">:</span> <span class="n">x</span><span class="o">|</span><span class="n">y</span><span class="p">,</span> <span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">v</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="o">&lt;&lt;</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">64</span><span class="p">)])</span> <span class="n">M</span> <span class="o">=</span> <span class="n">matrix</span><span class="p">(</span><span class="n">GF</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span> <span class="p">[</span> <span class="n">_to_bit_vec</span><span class="p">(</span><span class="n">L</span><span class="p">(</span><span class="mi">1</span><span class="o">&lt;&lt;</span><span class="n">i</span><span class="p">))</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">64</span><span class="p">)]).</span><span class="n">T</span> <span class="c1"># It is now trivial to invert this map </span><span class="n">M_inv</span> <span class="o">=</span> <span class="n">M</span><span class="p">.</span><span class="n">inverse</span><span class="p">()</span> <span class="n">decrypt</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">ct</span><span class="p">:</span> <span class="n">_from_bit_vec</span><span class="p">(</span><span class="n">M_inv</span> <span class="o">*</span> <span class="n">vector</span><span class="p">(</span><span class="n">GF</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span> <span class="n">_to_bit_vec</span><span class="p">(</span><span class="n">ct</span> <span class="o">^</span> <span class="n">cipher</span><span class="p">.</span><span class="n">encrypt</span><span class="p">(</span><span class="mi">0</span><span class="p">))))</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="n">tqdm</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">10000</span><span class="p">)):</span> <span class="n">pt</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="o">**</span><span class="mi">64</span><span class="p">)</span> <span class="n">ct</span> <span class="o">=</span> <span class="n">cipher</span><span class="p">.</span><span class="n">encrypt</span><span class="p">(</span><span class="n">pt</span><span class="p">)</span> <span class="k">assert</span> <span class="n">decrypt</span><span class="p">(</span><span class="n">ct</span><span class="p">)</span> <span class="o">==</span> <span class="n">pt</span> </code></pre></div></div> <h3 id="attacks-on-reduced-round-des">Attacks on Reduced Round DES</h3> <p>In attacking a reduced number of rounds of DES, note that if we use a smaller number of rounds than the recommended 16 then we may not get the necessary amount of diffusion. We can empirically test this as follows:</p> <ul> <li>choose a random key $$K$$ and a random plaintext $$P$$</li> <li>encrypt the plaintext $$C = E_K(P)$$</li> <li>encrypt another plaintext with a 1-bit difference $$C' = E_K(P \oplus \Delta_X)$$ and observe the difference in ciphertext $$\Delta_Y = C \oplus C' = E_K(P) \oplus E_K( P \oplus \Delta_X)$$</li> </ul> <p>By observing 5000 of such random pairs, we can observe that most input/output dependence is lost after 4 rounds, with all dependence being lost after 5 rounds:</p> <p><img src="/img/block_cipher/des_bit_corr_plot.png" alt="Plot of DES bit dependence" /></p> <p>We can also view this by the following - by encoding the logical conditions of the encryption and solving with a SAT solver for varying number of rounds, thr average time it takes to recover the cipher’s key is given as follows (here we use CryptoMiniSat to recover a key given 3 plaintext/ciphertext pairs):</p> <table> <thead> <tr> <th>Rounds</th> <th>Time</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>3.28s</td> </tr> <tr> <td>2</td> <td>6.35s</td> </tr> <tr> <td>3</td> <td>11.35s</td> </tr> <tr> <td>4</td> <td>39.12s</td> </tr> <tr> <td>5</td> <td><strong>&gt;3600s</strong></td> </tr> </tbody> </table> <p>From these times, we can see that there is a large jump in complexity from between 4 rounds and 5 rounds.</p> <h3 id="attempting-to-break-5-rounds">Attempting to Break 5 Rounds</h3> <p>Following the approach given in <a href="https://link.springer.com/chapter/10.1007/978-3-540-77272-9_10">this paper</a>, instead of directly solving for the key we instead fix $$k$$ of the key and then solve for the remaining bits with a SAT solver. This will either give us the rest of the bits of the key, or the SAT solver will fail to find a solution - indicating that our partial guess was wrong. Iterating over all $$2^k$$ possibilities of what bits our partial guess could be, we will eventually guess the correct key.</p> <p>To estimate the time that this approach would take, for varying $$k$$ we generate a random DES key and fix $$k$$ of the bits. We then encode the conditons of encryption into CNF form and attempt to solve for the remaining bits with <code class="language-plaintext highlighter-rouge">CryptoMiniSat</code>. Below is a plot of how long such an approach would take for varying values of $$k$$</p> <p><img src="/img/block_cipher/5_round_attack.png" alt="Plot of elapsed time vs fixed bits" /></p> <p>From extrapolating the best fit line on the above plot, this kind of attack would take $$\approx 10^5 s$$ on a single core.</p> <p>It is difficult to make a one-to-one comparison between such an approach and a brute force one for several reasons - a major one being that a SAT solver’s heuristics will prune down the key’s search space in a manner that a brute force approach would not be able. Additionally, we are making several assumptions in order to simplify the analysis (e.g. the choice of the bits we guess has no effect on the time taken by the SAT solver, searching for a solution takes the same amount of time as not finding any solutions).</p> <p>However, even with generous assumptions, a SAT-solver based approach like the one described is orders of magnitude faster than a naive brute force approach. With a quick shell script, we can benchmark approximately how long a single DES encryption takes.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span> <span class="nv">tmp_file</span><span class="o">=</span><span class="si">$(</span><span class="nb">mktemp</span><span class="si">)</span> <span class="nb">dd </span><span class="k">if</span><span class="o">=</span>/dev/urandom <span class="nv">bs</span><span class="o">=</span>1024 <span class="nv">count</span><span class="o">=</span>500000 2&gt;/dev/null <span class="o">&gt;</span> <span class="nv">$tmp_file</span> <span class="nv">time_start</span><span class="o">=</span><span class="si">$(</span><span class="nb">date</span> +%s.%N<span class="si">)</span> openssl enc <span class="nt">-des-ecb</span> <span class="nt">-e</span> <span class="nt">-K</span> 133457799BBCDFF1 <span class="nt">-provider</span> legacy <span class="nt">-provider</span> default <span class="nt">-in</span> <span class="nv">$tmp_file</span> <span class="o">&gt;</span> /dev/null <span class="nv">time_end</span><span class="o">=</span><span class="si">$(</span><span class="nb">date</span> +%s.%N<span class="si">)</span> <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$time_end</span><span class="s2"> - </span><span class="nv">$time_start</span><span class="s2">"</span> | bc <span class="nb">rm</span> <span class="s2">"</span><span class="nv">$tmp_file</span><span class="s2">"</span> </code></pre></div></div> <p>With this, we can see that encrypting $$8 \cdot 10^6$$ blocks takes approximately $$6.6 s$$ or approximately $$8.2 \cdot 10^{-7} s$$ per encryption. At this speed, a full brute force of all $$2^{56}$$ DES keys would take 1800 years; our SAT-solver based attack only takes an equivalent of $$\approx 2^{37}$$ DES encryptions.</p>Some Notes on the Cryptanalysis of Block CiphersCracking a 9 Year Old Safe2023-04-11T00:00:00+00:002023-04-11T00:00:00+00:00/2023/04/11/cracking-safe<p>Recently, I came across <a href="https://codegolf.stackexchange.com/q/36822/">an old StackExchange thread</a> wherein users submitted “lockers” or functions which only accepted a certain arithmetic sequence - then other users attempted to “crack” these lockers by providing said sequence. As a fun weekend challenge, I decided to crack one of these lockers that had remained uncracked for 9 years.</p> <h1 id="the-locker">The Locker</h1> <p>The provided function is as follows:</p> <div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">a</span><span class="p">(</span><span class="n">b</span><span class="p">):</span> <span class="n">c</span><span class="o">=</span><span class="mi">1</span> <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">b</span><span class="p">:</span> <span class="n">c</span><span class="o">=</span><span class="p">(</span><span class="n">c</span><span class="o">&lt;&lt;</span><span class="mi">32</span><span class="p">)</span><span class="o">+</span><span class="n">d</span> <span class="k">return</span> <span class="nb">pow</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span><span class="n">c</span><span class="p">,</span><span class="mh">0xf494eca63dcab7b47ac21158799ffcabca8f2c6b3</span><span class="p">)</span><span class="o">==</span><span class="mh">0xa3742a4abcb812e0c3664551dd3d6d2207aecb9be</span> </code></pre></div></div> <p>Note that the function returns true only if $$7^c=\underbrace{\texttt{0xa3}\cdots\texttt{be}}_{\equiv H}$$ modulo a prime $$p=\texttt{0xf4}\cdots\texttt{b3}$$. This is an example of the <a href="https://en.wikipedia.org/wiki/Discrete_logarithm#Properties">discrete logarithm problem</a>, an important primitive that has various applications in cryptography.</p> <h1 id="breaking-the-discrete-log">Breaking the Discrete Log</h1> <p>Note that the underlying group $$G = \mathbb{Z}^{\times}_p$$ that we are working in has order</p> \begin{align*} |G| &amp;= \phi(p) = p-1 \\ &amp;= 2 \cdot 3 \cdot 23 \cdot 1057807 \cdot 2132567 \cdot \underbrace{717\cdots661}_{\text{35 digits}} \end{align*} <p>Since the order of the group factors into (relatively) small factors, we can use the <a href="https://en.wikipedia.org/wiki/Pohlig%E2%80%93Hellman_algorithm">Pohlig-Hellman algorithm</a> to solve the discrete logarithm in this case. Briefly, this algorithm solves the discrete logarithm by solving the discrete logarithm modulo each of the factors and then using the Chinese Remainder Theorem to reconstruct the final solution.</p> <h1 id="a-slight-problem">A Slight Problem</h1> <p>To solve the discrete logarithm modulo each of the factors, the Pohlig-Hellman algorithm defaults to using the <a href="https://en.wikipedia.org/wiki/Baby-step_giant-step">baby-step giant-step algorithm (or BSGS)</a> which calculates the discrete logarithm in $$\mathbb{Z}_n$$ in time $$O(\sqrt{n})$$.</p> <p>Note that the largest prime factor of $$|G|$$ is $$p'=71765404858975364469794424368755661$$ and therefore, the application of BSGS at this step will take approximately $$\sqrt{p'} \approx 10^{17}$$. Therefore, a simple application of BSGS would be intractable - we instead opt for an application of the <a href="https://en.wikipedia.org/wiki/General_number_field_sieve">number field sieve</a> as implemented by <a href="https://cado-nfs.gitlabpages.inria.fr">CADO-NFS</a></p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$./cado-nfs.py -dlp -ell 71765404858975364469794424368755661 target=14930497059761124952555442014579540362281335175614 22341037975022202529922405433894518512705789413043 -t all --workdir //home/epistemologist/cado-nfs/_work_dir/ [...] Info:Complete Factorization / Discrete logarithm: Total cpu/elapsed time for entire Discrete logarithm: 63.44/47.7757 Info:root: log(target) = 55489157388397020258158645620533604 mod ell Info:root: logbase = 3313713443676474566743726004211879966369713471586 Info:root: target = 14930497059761124952555442014579540362281335175614 Info:root: log(target) = 55489157388397020258158645620533604 mod ell 55489157388397020258158645620533604$ ./cado-nfs.py //home/epistemologist/cado-nfs/_work_dir/p50.parameters_snapshot.0 target=7 Info:root: logbase = 3313713443676474566743726004211879966369713471586 Info:root: target = 7 Info:root: log(target) = 25109992438216120939941563608435562 mod ell 25109992438216120939941563608435562 </code></pre></div></div> <p>We therefore have that</p> $c = \frac{55489157388397020258158645620533604}{25109992438216120939941563608435562} \mod{p'}$ <h1 id="solving-the-rest-of-the-discrete-logarithm">Solving the Rest of the Discrete Logarithm</h1> <p>We now apply Pohlig-Hellman for the rest of the factors to find the value of $c$.</p> <div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">sage.groups.generic</span> <span class="kn">import</span> <span class="n">bsgs</span> <span class="n">p</span> <span class="o">=</span> <span class="mh">0xf494eca63dcab7b47ac21158799ffcabca8f2c6b3</span> <span class="n">g</span> <span class="o">=</span> <span class="mi">7</span> <span class="n">h</span> <span class="o">=</span> <span class="mh">0xa3742a4abcb812e0c3664551dd3d6d2207aecb9be</span> <span class="n">exponents</span><span class="p">,</span> <span class="n">moduli</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">[]</span> <span class="k">for</span> <span class="n">p_</span><span class="p">,</span> <span class="n">e_</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span><span class="n">factor</span><span class="p">(</span><span class="n">p</span><span class="o">-</span><span class="mi">1</span><span class="p">)):</span> <span class="n">g_i</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="p">(</span><span class="n">p</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">//</span> <span class="p">(</span><span class="n">p_</span><span class="o">^</span><span class="n">e_</span><span class="p">),</span> <span class="n">p</span><span class="p">)</span> <span class="n">h_i</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="n">h</span><span class="p">,</span> <span class="p">(</span><span class="n">p</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">//</span> <span class="p">(</span><span class="n">p_</span><span class="o">^</span><span class="n">e_</span><span class="p">),</span> <span class="n">p</span><span class="p">)</span> <span class="k">if</span> <span class="n">p_</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="o">^</span><span class="mi">10</span><span class="p">:</span> <span class="n">exponents</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">bsgs</span><span class="p">(</span><span class="n">g_i</span><span class="p">,</span> <span class="n">h_i</span><span class="p">,</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">p_</span><span class="o">^</span><span class="n">e_</span><span class="o">-</span><span class="mi">1</span><span class="p">)))</span> <span class="k">else</span><span class="p">:</span> <span class="n">exponents</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">Integers</span><span class="p">(</span><span class="mi">71765404858975364469794424368755661</span><span class="p">)(</span><span class="mi">55489157388397020258158645620533604</span><span class="o">/</span><span class="mi">25109992438216120939941563608435562</span><span class="p">)))</span> <span class="n">moduli</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">p_</span><span class="o">^</span><span class="n">e_</span><span class="p">)</span> <span class="n">c</span> <span class="o">=</span> <span class="n">crt</span><span class="p">(</span><span class="n">exponents</span><span class="p">,</span> <span class="n">moduli</span><span class="p">)</span> </code></pre></div></div> <p>Running this code, we get the value of $$c = 1068574207815876554047411521461868356487653669046$$. We can verify this:</p> <div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="n">c</span> <span class="o">=</span> <span class="mi">1068574207815876554047411521461868356487653669046</span> <span class="o">&gt;&gt;&gt;</span> <span class="nb">pow</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span><span class="n">c</span><span class="p">,</span><span class="mh">0xf494eca63dcab7b47ac21158799ffcabca8f2c6b3</span><span class="p">)</span><span class="o">==</span><span class="mh">0xa3742a4abcb812e0c3664551dd3d6d2207aecb9be</span> <span class="bp">True</span> </code></pre></div></div> <h1 id="getting-the-sequence">Getting the Sequence</h1> <p>To get the original sequence, it is possible to reverse the code by hand as I did originally - however, it is also possible to pass this into Z3 and let it spit out the answer:</p> <div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">z3</span> <span class="kn">import</span> <span class="o">*</span> <span class="n">a0</span><span class="p">,</span> <span class="n">d</span> <span class="o">=</span> <span class="n">Ints</span><span class="p">(</span><span class="s">"a0 d"</span><span class="p">)</span> <span class="n">b</span> <span class="o">=</span> <span class="p">[</span><span class="n">a0</span><span class="o">+</span><span class="n">k</span><span class="o">*</span><span class="n">d</span> <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">)]</span> <span class="n">c</span> <span class="o">=</span> <span class="mi">1</span> <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">b</span><span class="p">:</span> <span class="n">c</span> <span class="o">=</span> <span class="n">c</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1</span><span class="o">&lt;&lt;</span><span class="mi">32</span><span class="p">)</span> <span class="o">+</span> <span class="n">d</span> <span class="n">c_actual</span> <span class="o">=</span> <span class="mi">1068574207815876554047411521461868356487653669046</span> <span class="n">s</span> <span class="o">=</span> <span class="n">Solver</span><span class="p">()</span> <span class="n">s</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">c</span> <span class="o">==</span> <span class="n">c_actual</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">b</span><span class="p">:</span> <span class="n">s</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="o">-</span><span class="p">(</span><span class="mi">1</span><span class="o">&lt;&lt;</span><span class="mi">32</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">i</span><span class="p">)</span> <span class="n">s</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">i</span> <span class="o">&lt;</span> <span class="p">(</span><span class="mi">1</span><span class="o">&lt;&lt;</span><span class="mi">32</span><span class="p">))</span> <span class="n">s</span><span class="p">.</span><span class="n">check</span><span class="p">()</span> <span class="k">print</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">model</span><span class="p">())</span> <span class="c1"># [d = 316804249, a0 = -1154709934] </span></code></pre></div></div> <p>With this, we get that our final sequence is <code class="language-plaintext highlighter-rouge">[-1154709934, -837905685, -521101436, -204297187, 112507062]</code>.</p>Recently, I came across an old StackExchange thread wherein users submitted “lockers” or functions which only accepted a certain arithmetic sequence - then other users attempted to “crack” these lockers by providing said sequence. As a fun weekend challenge, I decided to crack one of these lockers that had remained uncracked for 9 years.100 Digit Challenge Solutions2023-02-10T00:00:00+00:002023-02-10T00:00:00+00:00/2023/02/10/100-digit-challenge<p>The following are solutions to the <a href="https://en.wikipedia.org/wiki/Hundred-dollar,_Hundred-digit_Challenge_problems">Hundred-dollar, Hundred-digit Challenge problems</a> posed by Professor Nick Trefethen - various mathematical problems where the goal is to provide an answer to within 10 digits of accuracy. The solutions are implemented in Python as well as using additional scientific computing libraries (e.g. numpy, scipy, sympy for symbolic calculation, mpmath for extended precision). Originally, these started as a final project for a class on <a href="https://faculty.math.illinois.edu/~hirani/teaching/490sp20/index.html">Computational Mathematics</a>, but I ended up continuing it after the class ended. For many of the problems, in order to get sufficiently high accuracy, a combination of mathematical enginuity as well as good implementation of efficient algorithms are needed.</p> <p>Many of the approaches and exposition used follow the book written by some of the original contest’s participants: <a href="http://www-m3.ma.tum.de/m3old/bornemann/challengebook/index.html"><em>The SIAM 100-Digit Challenge: A Study in High-Accuracy Numerical Computing</em></a></p> <ul> <li><a href="https://nbviewer.org/github/epistemologist/100-Digit-Challenge/blob/main/Problem%201.ipynb">Problem 1</a></li> <li><a href="https://nbviewer.org/github/epistemologist/100-Digit-Challenge/blob/main/Problem%202.ipynb">Problem 2</a></li> <li><a href="https://nbviewer.org/github/epistemologist/100-Digit-Challenge/blob/main/Problem%203.ipynb">Problem 3</a></li> <li><a href="https://nbviewer.org/github/epistemologist/100-Digit-Challenge/blob/main/Problem%204.ipynb">Problem 4</a></li> <li><a href="https://nbviewer.org/github/epistemologist/100-Digit-Challenge/blob/main/Problem%205.ipynb">Problem 5</a></li> <li><a href="https://nbviewer.org/github/epistemologist/100-Digit-Challenge/blob/main/Problem%206.ipynb">Problem 6</a></li> <li><a href="https://nbviewer.org/github/epistemologist/100-Digit-Challenge/blob/main/Problem%207.ipynb">Problem 7</a></li> <li><a href="https://nbviewer.org/github/epistemologist/100-Digit-Challenge/blob/main/Problem%208.ipynb">Problem 8</a></li> <li><a href="https://nbviewer.org/github/epistemologist/100-Digit-Challenge/blob/main/Problem%209.ipynb">Problem 9</a></li> </ul>The following are solutions to the Hundred-dollar, Hundred-digit Challenge problems posed by Professor Nick Trefethen - various mathematical problems where the goal is to provide an answer to within 10 digits of accuracy. The solutions are implemented in Python as well as using additional scientific computing libraries (e.g. numpy, scipy, sympy for symbolic calculation, mpmath for extended precision). Originally, these started as a final project for a class on Computational Mathematics, but I ended up continuing it after the class ended. For many of the problems, in order to get sufficiently high accuracy, a combination of mathematical enginuity as well as good implementation of efficient algorithms are needed.Notes on Littlewood’s ‘Large Numbers’ and Related Thoughts2022-01-03T14:28:12+00:002022-01-03T14:28:12+00:00/2022/01/03/big-numbers-notes<p>In his essay titled <a href="https://www.jstor.org/stable/3609933?seq=1#metadata_info_tab_contents">“Large Numbers”</a>(published as part of his larger collection of essays titled <em>A Mathematical Miscellany</em>), John Littlewood writes on the various applications of large numbers. In this post, we give a more modern take on some of his examples as well as give some new ones.</p> <h2 id="human-scale-numbers">Human Scale Numbers</h2> <h3 id="scales-of-measurement">Scales of Measurement</h3> <p>In §7, Littlewood discusses the size of various scales of measurement (e.g. the range of sound from just perceptible to just tolerable - Littlewood mentions a figure of $$10^{12}$$, however the range between <a href="https://en.wikipedia.org/wiki/Sound_pressure#Examples">0 decibels and 120 decibels</a> represents a range of $$10^6$$).</p> <p>Other examples include</p> <ul> <li><strong>luminance</strong>: $$10^{-6} \frac{\text{cd}}{\text{m}^2}$$ (threshold of vision) to $$10^{6} \frac{\text{cd}}{\text{m}^2}$$ (incandescent lamp) - <em>range of $$10^{12}$$</em></li> <li><strong>length</strong>: $$10^{-3} m$$ to $$10^{3} m$$ - <em>range of $$10^6$$</em></li> <li><strong>time</strong>: $$10^{-2} s$$ (human reaction time) to $$10^9 s$$ (human lifetime) - <em>range of $$10^{11}$$</em></li> <li><strong>mass</strong>: $$10^{-6} \text{kg}$$ (milligram) to $$10^3 \text{kg}$$ (metric ton) - <em>range of $$10^9$$</em></li> </ul> <h3 id="coincidences--probabilities">Coincidences / Probabilities</h3> <h4 id="will-west-case">Will West Case</h4> <p>Littlewood writes:</p> <blockquote> <p>Dorothy Sayers in Unpopular Opinions, cites the case of two negroes, each named Will West, confined simultaneously in Leavenworth Penitentiary, U.S.A. (in 1903), and with the same Bertillon measurements. (Is this really credible ?)</p> </blockquote> <p>We estimate this probability, let $$P$$ be the probability that two people share the same facial features, Bertillon measurements and name. We have:</p> <p>$$P \approx \underbrace{\text{Prob(same body measurements)}}_{:=P_b}$$ $$\cdot \; \underbrace{\text{Prob(same name)}}_{:=P_n}$$</p> <h5 id="estimating-p_n">Estimating $$P_n$$</h5> <p>Let $$n_1, n_2$$ be the names of two arbitrary people. We have the crude approximation:</p> $P(n_1 = n_2) = \sum_{\text{names }n} P(n_1 = n) P(n_2 = n) \\ = \sum_{\text{names } n} P(n)^2$ <p>Using data from <a href="https://www.ssa.gov/OACT/babynames/">U.S. Social Security Adminbistration</a> and using the top 1000 names from the years 1880 and 2010 (assuming that 50% of births are male and 50% of births are female), we <a href="https://gist.github.com/epistemologist/12cff945e6b8c918055f5585fc373c7f">get estimates</a> of probabilities of 1.8% and 0.21% respectively. Assuming that choosing first names and last names are independent, we estimate that $$P_n \approx 10^{-2} \cdot 10^{-2} = 10^{-4}$$</p> <h5 id="estimating-p_b">Estimating $$P_b$$</h5> <p>Littlewood mentions that the two Wests’ shared Bermillon measurements - these are a system of 11 different biometric measurements that were taken of prisoners as means of identification. In the original case, the measurements of the two Wests’ were <a href="https://web.archive.org/web/20120505160629/opinionator.blogs.nytimes.com/2012/05/01/whats-in-a-name-part-2/">not exactly the same, but remarkably similar</a>.</p> <p>Bertillon <a href="https://rss.onlinelibrary.wiley.com/doi/full/10.1111/j.1740-9713.2014.00739.x">himself predicted</a> that the probability of two people sharing all 11 measurements was $$P_b \approx 1/4^{32} = 1/268435456$$. However, <a href="https://link.springer.com/article/10.1007%2Fs00414-015-1158-6">more recent studies</a> in forensic anthropometry place the probability of two people sharing 8 different body measurement traits at $$P_b \approx 10^{-20}$$.</p> <p>We therefore make the estimate $$P_b \approx 10^{-12}$$ and therefore, $$P \approx 10^{-12} \cdot 10^{-4} = 10^{-16}$$.</p> <h5 id="wrapping-things-up">Wrapping Things Up</h5> <p>We now calculate the probability that for any one person being incarcerated that they share the same features to someone in the same prison that they are being held similarly to the two Will Wests.</p> <p>Assuming an average prison houses an average of <a href="https://www.americanjail.org/jail-statistics">250 inmates</a>, the probability that a person who is imprisoned meets their doppelganger in the same prison is $$p = 1 - (1-P)^{250}$$.</p> <p>Therefore, if $n$ people are incarcerated, we have that the probability that any one person will find their doppelganger is $$p' = 1 - (1-p)^n = 1 - (1-P)^{250n}$$. Considering that the U.S. made <a href="https://www.ojjdp.gov/ojstatbb/crime/ucr.asp?table_in=2">10 million arrests in 2019</a>, we estimate $$N \approx 5 \cdot 10^6$$. We therefore have that:</p> $p' = 1 - (1-P)^{N} \\ = 1 - \left( 1 - NP + \frac{N(N-1)}{2} P^2 - \cdots \right) \\ = NP + O(P^2)$ <p>Plugging in $$N = 250 \cdot 5 \cdot 10^6, P = 10^{-16}$$, we get that $$p' \approx 10^{-7}$$.</p> <p>This probability is reasonable as <a href="https://en.wikipedia.org/wiki/Doppelg%C3%A4nger#Examples_in_real_life">various doppelgangers have been found in real life</a> and so we expect it to be within the realm of possibility.</p> <h4 id="a-perfect-bridge-deal">A Perfect Bridge Deal</h4> <p>Littlewood writes:</p> <blockquote> <p>A report of holding 13 of a suit at Bridge used to be an annual event. The chance of this in a given deal is 2.4 * 10^-9 ; if we suppose that 2 * 10^6 people in England each play an average of 30 hands a week the probability is of the right order. I confess that I used to suppose that Bridge hands were not random, on account of inadequate shuffling ; Borel’s book on Bridge, however, shows that since the distribution within the separate hands is irrelevant the usual procedure of shuffling is adequate. (There is a marked difference where games of Patience are concerned : to destroy all organisation far more shuffling is necessary than one would naturally suppose ; I learned this from experience during a period of addiction, and have since compared notes with others.)</p> </blockquote> <p>Note that Littlewood gives a probability for holding 13 of a suit to be $$2.4 \cdot 10^{-9}$$; however, the <a href="https://stats.stackexchange.com/a/288235">actual probability</a> seems to be $$\approx 2.5 \cdot 10^{-11}$$. Using Littlewood’s assumptions, we have that the expected rate of perfect bridge suits is $$2.5 \cdot 10^{-11} \cdot \frac{\text{30 hands}}{\text{week}} \cdot 2 \cdot 10^6 \; \text{players} = \frac{0.0015 \text{ hands}}{\text{week}} \approx \frac{7.8 \text{ hands}}{\text{century}}$$</p> <p>Meanwhile, the probability of all 4 players receiving perfect deals is $$\approx 4.5 \cdot 10^{-28}$$, a much rarer occurance - this is discussed in <a href="https://www.youtube.com/watch?v=s9-b-QJZdVA">this video by Matt Parker</a>.</p> <h3 id="computation">Computation</h3> <h4 id="factorizations">Factorizations</h4> <p>In §13, Littlewood discusses the scope of factorizations of large numbers, quoting <a href="https://en.wikipedia.org/wiki/D._H._Lehmer">D.H. Lehmer</a></p> <blockquote> <p>Professor Lehmer further tells me that numbers up to 2.7 * 10^9 can be completely factorised in 40 minutes; up to 10^15 in a day; up to 10^20 in a week; finally up to 10^100, with some luck, in a year</p> </blockquote> <p>The cutting edge of the largest integers we can factor now has well evolved since the time of Littlewood due to both advances in better hardware and the development of better algorithms.</p> <p>For reference, I can factor numbers of the magnitude $$10^{40}$$ nearly instantly on my nearly 10-year old laptop:</p> <pre><code class="language-gp">? x = random(10^40); x %1 = 8986749531375339305571207626880980158008 ? factor(x) %2 = [ 2 3] [ 3 3] [ 319519 1] [ 2078212777 1] [62655931019629323359651 1] ? ## *** last result computed in 12 ms. </code></pre> <p>In a 1977 issue of Scientific American, Martin Gardener <a href="https://en.wikipedia.org/wiki/The_Magic_Words_are_Squeamish_Ossifrage">encoded a message with RSA</a> using a modulus on the order of $$10^{129}$$. The message was eventually decrypted by a distributed computation effort lead by <a href="https://link.springer.com/chapter/10.1007%2FBFb0000440">Atkins et al.</a>.</p> <p>As of writing, the largest number that has been factored has <a href="https://en.wikipedia.org/wiki/RSA_numbers#RSA-250">250 decimal digits</a> and was factored using 2700 CPU core-years with the Number Field Sieve algorithm.</p> <h4 id="training-ai-models">Training AI Models</h4> <p>With the rise of large AI models like GPT-2/3 with billions of parameters, the amount of computation to train these models has risen exponentially in recent years. <a href="https://openai.com/blog/ai-and-compute/">This blog post from OpenAI</a> shows a <em>3.4 month doubling time</em> in the increase of AI training. Note that models like AlphaGo and AlphaGoZero exceed $$10^2$$ petaflop/s-days, or around $$10^{22}$$ operations $$\approx 0.1 \text{mol}$$</p> <h4 id="bitcoin-mining">Bitcoin Mining</h4> <p>Looking at the <a href="https://www.blockchain.com/charts/hash-rate">hashrate for Bitcoin</a>, as of January 2022 the Bitcoin network is performing $$\approx 177 \cdot 10^{18}$$ SHA 256 hashes <strong>per second</strong>. Assuming that each hash is equivalent <a href="https://bitcoin.stackexchange.com/questions/1293/how-many-integer-operations-on-a-gpu-are-necessary-for-one-hash">to around 1000 arithmetic operations</a>, we have that the Bitcoin network can perform $$10^{23} \frac{\text{operations}}{s} \approx 1 \frac{\text{mol}}{s}$$. Throughout its entire existence, the Bitcoin network has performed $$10^{28}$$ hashes or around $$10^{31} \text{operations} \approx 10^8 \text{mol}$$.</p> <h4 id="breaking-cryptographic-schemes">Breaking Cryptographic Schemes</h4> <h5 id="distributednet">Distributed.net</h5> <p><a href="https://en.wikipedia.org/wiki/Distributed.net">Distributed.net</a> is a platform designed to organize large-scale distributed computation projects. One of these projects was to break messages encrypted under <a href="https://en.wikipedia.org/wiki/RC5">RC5</a> with varying key lengths. The projects’ statuses are as follows:</p> <ul> <li><a href="https://stats.distributed.net/projects.php?project_id=3">cracking a key of 56 bits</a>: $$26 \cdot 10^{15}$$ keys attempted over 193 days, completed</li> <li><a href="https://stats.distributed.net/projects.php?project_id=5">cracking a key of 64 bits</a>: $$15 \cdot 10^{18}$$ keys attempted over 1726 days, completed</li> <li><a href="https://stats.distributed.net/projects.php?project_id=8">cracking a key of 72 bits</a>: $$390 \cdot 10^{18}$$ keys attempted over 6972 days, incomplete</li> </ul> <h2 id="nonhuman-scale-numbers">Nonhuman Scale Numbers</h2> <h3 id="a-mouse-surviving-in-hell">A Mouse Surviving in Hell</h3> <p>We expand on some of the ommited details in Littlewood’s calculation on the probability that a mouse can survive a week in Hell.</p> <p>Following Littlewood’s notation, let</p> <ul> <li>$$T_0$$ be average room temperature (note all temperature units are measured <a href="https://en.wikipedia.org/wiki/Thermodynamic_temperature">absolutely</a> - i.e. absolute zero is at $$T = 0$$)</li> <li>$$T_H$$ be the temperature of Hell (we assume that $$T_h \gg T_0$$)</li> <li>$$\mu = k n_0$$ be the number of particles in the mouse (here, $$n_0$$ is <a href="https://en.wikipedia.org/wiki/Avogadro%27s_number">Avogadro’s number</a>, so $$k$$ represents the number of moles)</li> </ul> <p>We assume, as Littlewood does, that “…we should treat the problem as classical, and suppose that the molecules and densities are terrestial.” - let the particles in Hell (or somewhere extremely hot if you prefer) follow a <a href="https://en.wikipedia.org/wiki/Maxwell%E2%80%93Boltzmann_distribution">Maxwell-Boltzmann distribution</a></p> <p>Littlewood also defines two other variables, $$c_0, c_H$$ or the average speed of particles at temperatures of $$T_0$$ and $$T_H$$ respectively. We can describe this by calculating the <a href="https://en.wikipedia.org/wiki/Maxwell%E2%80%93Boltzmann_distribution#Typical_speeds">mean velocity</a> at various temperatures, giving $$c_0 = \sqrt{\frac{8 k_B T_0}{ \pi m}}, c_H = \sqrt{\frac{8 k_B T_H}{\pi m}}$$.</p> <p>Next, for any given particle at temperature $$T = T_H$$, the probability $$p$$ that its speed does not exceed $$c_0$$ can be found by integrating <a href="https://en.wikipedia.org/wiki/Maxwell%E2%80%93Boltzmann_distribution#Distribution_for_the_speed">the distribution function of the particle’s speed</a> as follows:</p> $p = \int_0^{c_0} f(v) = \sqrt{\frac{2}{\pi}} \left(\frac{m}{k T_H}\right)^{3/2} \int_0^{c_0} v^2 e^{-\frac{mv^2}{2k T_H}} dv \\ = \sqrt{\frac{2}{\pi}} \left(\frac{m}{k T_H}\right)^{3/2} \int_0^{c_0} \left( v^2 + O(1/T_H) \right) dv \\ \sim T_H^{-3/2} c_0^3 \sim (T_0 / T_H)^{3/2}$ <p>Littlewood then states that the probability that most of the particles in the mouse have $$c \le c_0$$ is of the order of $$p^\mu$$. He then breaks up the time interval of a week into time intervals of length $$\tau$$ similar to the <a href="https://en.wikipedia.org/wiki/Mean_free_time">mean free time</a> or average time between molecular collisions.</p> <p>Here, we simply take Littlewood’s word [TODO: actually derive this] and say that $$\tau_H \sim \frac{n_0^{-1/3}}{c_0} \sqrt{\frac{T_0}{T_H}}$$ and so the total number of time periods in a week is $$\nu = w / \tau_H$$</p> <p>We therefore have that the total probability of survival is $$p = 1/C$$, where</p> $C = (p^{-\mu})^\nu = \left(\sqrt{\frac{T_H}{T_0}}\right)^{\frac{3}{2} c_0 k w n_0^{4/3} \sqrt{\frac{T_H}{T_0}}}$ <p>Let’s now substitute actual numbers into this equation (here, we depart from the values that Littlewood uses as his values are just to get a nice final result):</p> <ul> <li>we set room temperature at $$T_0 \approx 300 \text{K}$$ or around $$80^{\circ}\text{F}$$</li> <li>the temperature of Hell is difficult to estimate - while Biblical accounts such as one in <a href="https://www.biblegateway.com/passage/?search=Revelation+21">Revelations 21:8</a> describe a “fiery lake of burning sulfur” and therefore a temperature less than the boiling point of sulfur at <a href="https://en.wikipedia.org/wiki/Sulfur">around 700 K</a>, we take this as metaphor and instead take Littlewood’s value of $$T_H \approx 10^{12} \text{K}$$. For comparison, <a href="https://en.wikipedia.org/wiki/Orders_of_magnitude_(temperature)">similar temperatures</a> can be seen in the formation of stars and microseconds after the Big Bang.</li> <li>we use a value of $$k = 10^2$$; for reference, $$10^2 \text{mol}$$ of water weighs around $$1.8 \text{kg}$$</li> <li>we have that $$w = 7 \text{days} \approx 6 \cdot 10^5 \text{s}$$</li> <li>we <a href="https://www.wolframalpha.com/input/?i=sqrt%28+%288+*+Boltzmann%27s+constant+*+300+Kelvin%29+%2F+%28pi+*+18+grams+%2F+Avogadro%27s+number%29+%29+">can calculate</a> $$c_0$$ from our earlier definition: $$c_0 = \sqrt{\frac{8 \cdot (1.4 \cdot 10^{-23} \frac{J}{K} ) \cdot 300K }{\pi \left( \frac{18 g}{6 \cdot 10^{23}} \right) }} \approx 600 \frac{m}{s}$$</li> </ul> <p>We can now explicitly calculate C:</p> <div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">In</span> <span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="kn">from</span> <span class="nn">math</span> <span class="kn">import</span> <span class="n">sqrt</span><span class="p">,</span> <span class="n">log</span> <span class="p">...:</span> <span class="n">T_0</span> <span class="o">=</span> <span class="mi">300</span> <span class="p">...:</span> <span class="n">T_H</span> <span class="o">=</span> <span class="mf">1e12</span> <span class="p">...:</span> <span class="n">k</span> <span class="o">=</span> <span class="mf">1e2</span> <span class="p">...:</span> <span class="n">w</span> <span class="o">=</span> <span class="mf">6e5</span> <span class="p">...:</span> <span class="n">c_0</span> <span class="o">=</span> <span class="mi">600</span> <span class="p">...:</span> <span class="n">n_0</span> <span class="o">=</span> <span class="mf">6e23</span> <span class="p">...:</span> <span class="n">exp</span> <span class="o">=</span> <span class="mi">3</span> <span class="o">/</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">c_0</span> <span class="o">*</span> <span class="n">k</span> <span class="o">*</span> <span class="n">w</span> <span class="o">*</span> <span class="n">n_0</span><span class="o">**</span><span class="p">(</span><span class="mi">4</span><span class="o">/</span><span class="mi">3</span><span class="p">)</span> <span class="o">*</span> <span class="n">sqrt</span><span class="p">(</span><span class="n">T_H</span> <span class="o">/</span> <span class="n">T_0</span><span class="p">)</span> <span class="p">...:</span> <span class="n">base</span> <span class="o">=</span> <span class="n">sqrt</span><span class="p">(</span><span class="n">T_H</span> <span class="o">/</span> <span class="n">T_0</span><span class="p">)</span> <span class="p">...:</span> <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"C = 10^(</span><span class="si">{</span><span class="n">exp</span><span class="o">*</span><span class="n">log</span><span class="p">(</span><span class="n">base</span><span class="p">)</span> <span class="o">/</span> <span class="n">log</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span><span class="si">}</span><span class="s">)"</span><span class="p">)</span> <span class="n">C</span> <span class="o">=</span> <span class="mi">10</span><span class="o">^</span><span class="p">(</span><span class="mf">7.512302344442364e+47</span><span class="p">)</span> </code></pre></div></div> <h4 id="other-examples">Other Examples</h4> <p>This is not the only example of large numbers arising from the study of physical phenomena:</p> <ul> <li><a href="http://www.fpx.de/fp/Fun/Googolplex/GetAGoogol.html">Here</a>, it is described that a black hole of mass $$6.14 \cdot 10^{41} \text{kg} \approx 3 \cdot 10^{11}$$ times the mass of the sun has a dimensionless entropy of $$10^{100}$$ and therefore has $$e^{10^{100}} \approx 10^{10^{99.6}}$$ different macroscopic states</li> <li><a href="https://arxiv.org/pdf/1712.08465.pdf">In this paper</a>, the probability that a human teleports due to random quantum fluctuations is calculated to be approximately $$10^{-4.5 \cdot 10^{29}}$$</li> </ul>In his essay titled “Large Numbers”(published as part of his larger collection of essays titled A Mathematical Miscellany), John Littlewood writes on the various applications of large numbers. In this post, we give a more modern take on some of his examples as well as give some new ones.