infinitemonkeytheoremhttps://www.inmoth.ca/2024-03-27T00:00:00-06:00Keyboard Latency Testing2024-03-27T00:00:00-06:002024-03-27T00:00:00-06:00Calebtag:www.inmoth.ca,2024-03-27:/keyboard-latency-testing.html<p>Lightweight keyboard re-mapper latency testing</p><p>I recently wanted to experiment with <a href="https://precondition.github.io/home-row-mods">home-row mods</a>. I have a QMK enabled keyboard, but I want my mods to be portable for when I'm travelling and don't have my external keyboard. There are several remapping tools that can implement home-row mods on linux, notably <a href="https://github.com/kmonad/kmonad">KMonad</a>, <a href="https://github.com/jtroo/kanata">Kanata</a>, and <a href="https://github.com/rvaiya/keyd">keyd</a>. While these tools have different feature sets and goals, they all overlap in meeting my needs. The deciding factor for me is latency, I want the tool that imparts smallest additional latency to my typing <label for="sn-0" class="margin-toggle sidenote-number"></label>
<input type="checkbox" id="sn-0" class="margin-toggle"/>
<span class="sidenote"> See <a href="https://danluu.com/keyboard-latency/">Dan Luu's</a> writings about latency</span>
. To compare latency between the different tools, I wrote a small python script; which is the subject of this post.</p>
<p><em>Note about latency testing: robust end-to-end latency testing is done using a circuit that triggers a key-press and a light sensor to catch actual rendering<label for="sn-1" class="margin-toggle sidenote-number"></label>
<input type="checkbox" id="sn-1" class="margin-toggle"/>
<span class="sidenote"> See a cool setup by <a href="https://thume.ca/2020/05/20/making-a-latency-tester/">Tristan Hume</a></span>
. This is a great way to determine actual latency. But for my use, I only care about relative latency (which tool introduces the most latency), so a lightweight method will be suitable.</em></p>
<p>I would like to directly measure the latency (delay) introduced by the remapping tool from the point it receives my keypress, to the time the application receives the keypress — but I don't know how to do that. What I can do is prompt myself to press a key, and measure how long it takes from the start of the prompt, to when my script receives the keypress. The measured latency includes roughly 3 components.</p>
<ol>
<li>System/OS latency. This is from my keyboard, the OS, my terminal, etc.</li>
<li>My reaction time. <a href="https://en.wikipedia.org/wiki/Mental_chronometry#Distribution_of_response_times">Wikipedia</a> says the fastest human reaction times are somewhere between 100ms and 200ms.</li>
<li>Latency introduced by the remapping tool.</li>
</ol>
<p>While none of these components will be consistent across every keypress, we can assume they all have a consistent distribution <label for="sn-2" class="margin-toggle sidenote-number"></label>
<input type="checkbox" id="sn-2" class="margin-toggle"/>
<span class="sidenote"> The <a href="https://en.wikipedia.org/wiki/Mental_chronometry#Distribution_of_response_times">distribution of human reaction times</a> seems to be consistent at least</span>
. Since all the delay distributions are consistent, I can directly compare the mean reaction delay using each tool to determine the relative latencies. The differences in mean reaction time will be the differences in latency of each remapping tool. </p>
<details>
<summary>
Boring Math
</summary>
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
<mstyle displaystyle="true" scriptlevel="0">
<mrow data-mjx-texclass="ORD">
<mtable rowspacing=".5em" columnspacing="1em" displaystyle="true">
<mtr>
<mtd>
<mtable columnalign="left left" columnspacing="1em" rowspacing="4pt">
<mtr>
<mtd>
<msub>
<mi>l</mi>
<mi>s</mi>
</msub>
<mo>=</mo>
<mtext>System/OS latency</mtext>
</mtd>
</mtr>
<mtr>
<mtd>
<msub>
<mi>l</mi>
<mi>h</mi>
</msub>
<mo>=</mo>
<mtext>Human reaction time</mtext>
</mtd>
</mtr>
<mtr>
<mtd>
<msubsup>
<mi>l</mi>
<mi>t</mi>
<mi>a</mi>
</msubsup>
<mo>=</mo>
<mtext>Latency from remapping tool A</mtext>
</mtd>
</mtr>
<mtr>
<mtd>
<msubsup>
<mi>l</mi>
<mi>t</mi>
<mi>b</mi>
</msubsup>
<mo>=</mo>
<mtext>Latency from remapping tool B</mtext>
</mtd>
</mtr>
<mtr>
<mtd>
<msub>
<mi>m</mi>
<mi>t</mi>
</msub>
<mo>=</mo>
<mtext>Mean Latency from </mtext>
<mi>n</mi>
<mtext> trials</mtext>
</mtd>
</mtr>
<mtr>
<mtd>
<msub>
<mi>m</mi>
<mi>t</mi>
</msub>
<mo>=</mo>
<msub>
<mi>l</mi>
<mi>s</mi>
</msub>
<mo>+</mo>
<msub>
<mi>l</mi>
<mi>h</mi>
</msub>
<mo>+</mo>
<msub>
<mi>l</mi>
<mi>t</mi>
</msub>
</mtd>
</mtr>
</mtable>
</mtd>
</mtr>
</mtable>
</mrow>
</mstyle>
</math>
<br>
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
<mstyle displaystyle="true" scriptlevel="0">
<mrow data-mjx-texclass="ORD">
<mtable rowspacing=".5em" columnspacing="1em" displaystyle="true">
<mtr>
<mtd>
<mtable displaystyle="true" columnalign="right left" columnspacing="0em" rowspacing="3pt">
<mtr>
<mtd>
<mtext>Latency Difference</mtext>
</mtd>
<mtd>
<mi></mi>
<mo>=</mo>
<msubsup>
<mi>m</mi>
<mi>t</mi>
<mi>a</mi>
</msubsup>
<mo>−</mo>
<msubsup>
<mi>m</mi>
<mi>t</mi>
<mi>b</mi>
</msubsup>
</mtd>
</mtr>
<mtr>
<mtd></mtd>
<mtd>
<mi></mi>
<mo>=</mo>
<mo stretchy="false">(</mo>
<msub>
<mi>l</mi>
<mi>s</mi>
</msub>
<mo>+</mo>
<msub>
<mi>l</mi>
<mi>h</mi>
</msub>
<mo>+</mo>
<msubsup>
<mi>l</mi>
<mi>t</mi>
<mi>a</mi>
</msubsup>
<mo stretchy="false">)</mo>
<mo>−</mo>
<mo stretchy="false">(</mo>
<msub>
<mi>l</mi>
<mi>s</mi>
</msub>
<mo>+</mo>
<msub>
<mi>l</mi>
<mi>h</mi>
</msub>
<mo>+</mo>
<msubsup>
<mi>l</mi>
<mi>t</mi>
<mi>b</mi>
</msubsup>
<mo stretchy="false">)</mo>
</mtd>
</mtr>
<mtr>
<mtd></mtd>
<mtd>
<mi></mi>
<mo>=</mo>
<msubsup>
<mi>l</mi>
<mi>t</mi>
<mi>a</mi>
</msubsup>
<mo>−</mo>
<msubsup>
<mi>l</mi>
<mi>t</mi>
<mi>b</mi>
</msubsup>
</mtd>
</mtr>
</mtable>
</mtd>
</mtr>
</mtable>
</mrow>
</mstyle>
</math>
</details>
<p><img alt="image demonstrating the distribution of human response times" src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/8d/Reaction_time_density_plot.svg/2880px-Reaction_time_density_plot.svg.png" />
<p><center>Distribution of Human Reaction times. Source:<a href="https://commons.wikimedia.org/wiki/File:Reaction_time_density_plot.svg">Emily Willoughby</a>, <a href="https://creativecommons.org/licenses/by-sa/4.0">CC BY-SA 4.0</a>, via Wikimedia Commons</center></p></p>
<p>To measure reaction time, I set up a basic python script that prompts me to press a key. The trick is that the prompt comes after a random delay, which prevents me from accidentally finding a rhythm and reflexively pressing early.</p>
<p>This is done with the following python code</p>
<div class="codehilite"><pre><span></span><span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">random</span><span class="o">.</span><span class="n">random</span><span class="p">()</span> <span class="o">*</span> <span class="mf">1.5</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="c1"># 1s - 2.5s delay</span>
<span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
<span class="n">os</span><span class="o">.</span><span class="n">system</span><span class="p">(</span><span class="s1">'read -n 1 -s -r -p "Press any key"'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</pre></div>
<p>The reaction time from each key press is measured, and then reduced into mean, and median. I would also like to calculate mode, but I didn't feel I was working with enough samples to calculate it accurately <label for="sn-3" class="margin-toggle sidenote-number"></label>
<input type="checkbox" id="sn-3" class="margin-toggle"/>
<span class="sidenote"> This is probably an indication that I don't have enough samples to draw <em>any</em> meaningful conclusion, but <code>¯\_(ツ)_/¯</code></span>
. I can then compare these statistics between keyd, kanata, and the baseline of nothing.</p>
<p>While gathering data I did occasionally twitch and get a sub 100ms reaction time, or lose focus and get a 1s reaction time. Outliers were removed with the following code:</p>
<div class="codehilite"><pre><span></span><span class="c1"># These boundaries were chosen based on my own reaction times</span>
<span class="c1"># They might need tuning on other systems </span>
<span class="n">delays</span> <span class="o">=</span> <span class="p">[</span><span class="n">d</span> <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">delays</span> <span class="k">if</span> <span class="n">d</span> <span class="o">></span> <span class="mf">0.1</span> <span class="ow">and</span> <span class="n">d</span> <span class="o"><</span> <span class="mf">0.4</span><span class="p">]</span>
</pre></div>
<p>For the tests I measured the latencies of pressing my home-row mod key (<code>f</code>) on my base system, keyd, and kanata. I did an additional test with kanata using a different key (<code>j</code>). Each metric was calculated based on 50 keypresses done 10 at a time — I should do more, but it's boring.</p>
<table>
<thead>
<tr>
<th></th>
<th>Mean (s)</th>
<th>Median (s)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Base (f)</td>
<td>0.2772</td>
<td>0.2732</td>
</tr>
<tr>
<td>keyd (f)</td>
<td>0.3112</td>
<td>0.3099</td>
</tr>
<tr>
<td>Kanata (f)</td>
<td>0.3216</td>
<td>0.3174</td>
</tr>
<tr>
<td>Kanata (j)*</td>
<td>0.2628</td>
<td>0.2602</td>
</tr>
</tbody>
</table>
<p><em>*Note: The <code>j</code> Kanata test was done the next day after a good sleep. A quick retest of the Base shows a mean of 0.2537s. I didn't want to go through all 50 again, so the discrepancy stands.</em></p>
<p>While this isn't the most statistically sound test, the results definitely show that adding home-row mods can add latency. While it seems clear that keyd and kanata are adding a delay; the keypress isn't triggered until the key-up event (vs. key-down in the base) for the home-row mod remapping. This means that there is an additional component to the delay (how low it takes me to lift my finger back up after pressing). The <code>j</code> test shows that the tools are not adding meaningful latencies to other characters, which suggest that much of the latency difference is the time it takes me to lift my finger off the key. Based on my testing, there does still seem to be a small difference in the keyd and Kanata latencies. Speed is <a href="https://github.com/rvaiya/keyd?tab=readme-ov-file#goals">a core goal</a> of keyd, so I'm not surprised that it performs well here.</p>
<p>At the beginning of this post I said that I would choose a remapping tool based on latencies alone, but I ended up just using kanata. At the time I was setting up my system, keyd didn't quite support my desired configuration, but it does now. I've stayed with Kanata because I like the direction of the project and how responsive <a href="https://github.com/jtroo">jtroo</a> is to new ideas. That said, I admire the design of keyd and it's minimalism, this testing shows that it's worth checking out again.</p>
<p>Please let me know if you find this technique helpful, or if you have any additions to improve it!</p>
<p><br></p>
<details>
<summary>
Full Code Listing
</summary>
The up-to-date code, as well as the raw data from my testing can be found <a href="https://github.com/CalebJohn/latency_testing">on github</a>.
<div class="codehilite"><pre><span></span><span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">random</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="sd">"""</span>
<span class="sd">This script measures the latency of the keyboard input by prompting the user for a </span>
<span class="sd">keypress at a random interval between 1 and 3 seconds. The latency is measured as the</span>
<span class="sd">time between the prompt and the keypress. The script repeats this process 11 times and</span>
<span class="sd">prints the mean, median, max, and min latency.</span>
<span class="sd">This is not sufficient for measuring absolute latency. But is useful for comparing</span>
<span class="sd">relative latency between different systems (QMK configurations in my case).</span>
<span class="sd">"""</span>
<span class="n">delays</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">try</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">50</span><span class="p">):</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">random</span><span class="o">.</span><span class="n">random</span><span class="p">()</span> <span class="o">*</span> <span class="mf">1.5</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
<span class="n">os</span><span class="o">.</span><span class="n">system</span><span class="p">(</span><span class="s1">'read -n 1 -s -r -p "Press any key "'</span><span class="p">)</span>
<span class="n">delay</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
<span class="nb">print</span><span class="p">(</span><span class="n">delay</span><span class="p">)</span>
<span class="n">delays</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">delay</span><span class="p">)</span>
<span class="k">if</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="o">%</span> <span class="mi">10</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="n">os</span><span class="o">.</span><span class="n">system</span><span class="p">(</span><span class="s1">'read -n 1 -s -r -p "Take a quick break, press a key when you</span><span class="se">\'</span><span class="s1">re ready to continue "'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">()</span>
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
<span class="k">pass</span>
<span class="n">delays</span> <span class="o">=</span> <span class="p">[</span><span class="n">d</span> <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">delays</span> <span class="k">if</span> <span class="n">d</span> <span class="o">></span> <span class="mf">0.1</span> <span class="ow">and</span> <span class="n">d</span> <span class="o"><</span> <span class="mf">0.4</span><span class="p">]</span>
<span class="n">mean</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">delays</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">delays</span><span class="p">)</span>
<span class="n">bucketed</span> <span class="o">=</span> <span class="p">[</span><span class="nb">round</span><span class="p">(</span><span class="n">d</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">delays</span><span class="p">]</span>
<span class="c1"># This is the formula for variance of sample, rather than</span>
<span class="c1"># variance of a population</span>
<span class="n">variance</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">([(</span><span class="n">x</span> <span class="o">-</span> <span class="n">mean</span><span class="p">)</span> <span class="o">**</span> <span class="mi">2</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">delays</span><span class="p">])</span> <span class="o">/</span> <span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">delays</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">mean: "</span><span class="p">,</span> <span class="n">mean</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"median: "</span><span class="p">,</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">delays</span><span class="p">)[</span><span class="nb">len</span><span class="p">(</span><span class="n">delays</span><span class="p">)</span> <span class="o">//</span> <span class="mi">2</span><span class="p">])</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"mode: "</span><span class="p">,</span> <span class="nb">max</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">bucketed</span><span class="p">),</span> <span class="n">key</span><span class="o">=</span><span class="n">bucketed</span><span class="o">.</span><span class="n">count</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"std. dev.:"</span><span class="p">,</span> <span class="n">variance</span> <span class="o">**</span> <span class="mf">0.5</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"max: "</span><span class="p">,</span> <span class="nb">max</span><span class="p">(</span><span class="n">delays</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"min: "</span><span class="p">,</span> <span class="nb">min</span><span class="p">(</span><span class="n">delays</span><span class="p">))</span>
</pre></div>
</details>Product Annealing2022-11-14T00:00:00-07:002022-11-14T00:00:00-07:00Calebtag:www.inmoth.ca,2022-11-14:/product-annealing.html<p>Framing new projects as optimization problems</p><p>Starting a new project is difficult. Really difficult. You don't necessarily know what your actual product will be. Ideally you would arrive at the final solution quickly, and iterate from there. For example, you might have decided that the existing secure messaging tools (signal et. al.) are insufficient and you will build your own. At this stage, you don't know if the communication will be P2P, centralized, or federated (or some combination). You don't know what crypto to use (is AES-256 still secure?). To what extent should it leverage blockchain technology? Does it need to be quantum-hardened? Will it use a push or pull model? etc. </p>
<p>This is a common position to be in when you're not a domain expert (and sometimes even if you are) and will occur on a project of any scale. Even building a modest CRUD app will require you to be familiar with multiple technology stacks (frontend/backend), the trade-offs between databases, and the different hosting options. </p>
<p>Put differently, you want to find the optimal solution for your project (i.e. optimal technologies, optimal feature set, etc.) as quickly as possible. Thus you should employ an optimization technique.</p>
<p>I propose <a href="https://en.wikipedia.org/wiki/Simulated_annealing">Simulated Annealing</a>. The name comes from the metallurgical process of annealing, which involves heating, then slowly cooling a material. For example, glass is annealed to provide a uniform rigid structure, glass that is improperly annealed will contain internal stress points making it fragile.</p>
<p>Simulated Annealing uses an internal "temperature" or "energy", which is intuitively understood as the willingness of the algorithm to jump around the search space. The "temperature" is slowly lowered until the algorithm (hopefully) settles on the global optimum. If the temperature is lowered too quickly the algorithm will settle on a local maximum (the probability of finding a global optimum approaches 1 as annealing time increases), lowering to slowly wastes compute time.</p>
<p>When starting a new project, I suggest keeping the annealing metaphor in mind. Early on the project should be in a high "temperature" state. This means testing out and researching many solutions without commitment. As time progresses the "temperature" should decrease, resulting in less broad solution changes, and more refinements on a few chosen solutions. Eventually, the system should be in a state with very few changes, and primarily refinements as requirements change/issues are discovered.</p>
<p>This is not an exact prescription to be followed<label for="sn-0" class="margin-toggle sidenote-number"></label>
<input type="checkbox" id="sn-0" class="margin-toggle"/>
<span class="sidenote"> In the case of a new project where you are already familiar with the space, it might make sense to start from a lower "temperature" for example. </span>
, but I believe the following lessons are important to keep in mind.</p>
<ol>
<li>
<p>The start of a new project requires a lot of energy </p>
<ul>
<li>It's important to spend this time experimenting and researching many diverse solutions to the problem at hand. Don't commit (or over-commit) to anything at this point. This means that discovering a flaw in a critical dependency should not result in the death of the project.</li>
</ul>
</li>
<li>
<p>Reduce the "temperature" with time </p>
<ul>
<li>It's never too late to make modifications to a project, but as time goes on changes should get smaller. Stability is important.</li>
</ul>
</li>
<li>
<p>Don't let the "temperature" reach 0 </p>
<ul>
<li>Even though the "temperature" of the project is always trending down, remember that it should never reach <a href="https://en.wikipedia.org/wiki/Absolute_zero">absolute zero</a>; there is always room to correct technological mistakes.</li>
</ul>
</li>
</ol>Personalized Wallet Addresses2022-05-05T00:00:00-06:002022-05-05T00:00:00-06:00Calebtag:www.inmoth.ca,2022-05-05:/personalized-wallet-addresses.html<p>The 0xABCs of generating custom wallet addresses</p><p>A Personalized Address (or Vanity Address) is a wallet address that is customized in some way. It's the web3 equivalent of a <a href="https://en.wikipedia.org/wiki/Vanity_plate">vanity license plate</a>. A typical (Ethereum) wallet address looks like this:</p>
<div class="codehilite"><pre><span></span><span class="mf">0</span><span class="n">x60C42Ecb80C2069eb7aC1Ee18A84244c8617E8Ab</span>
</pre></div>
<p>But when sharing your Ethereum address, you might want something more reflective of your culinary skills (maybe just mine):</p>
<div class="codehilite"><pre><span></span><span class="mf">0</span><span class="n">xbadf00db80C2069eb7aC1Ee18A84244c8617E8Ab</span>
</pre></div>
<p>If you're like me, then you're in the right place, keep reading. If not, enjoy your random address ¯\_(ツ)_/¯.</p>
<h2 id="why-do-it-yourself"><a class="toclink" href="#why-do-it-yourself">Why do it yourself?</a></h2>
<p>You could perform an internet search for Personalized Wallet Addresses or Vanity Addresses, and run whatever code you find. If you plan on using the generated address, it might be worth using a generator that you trust.</p>
<p>To trust a generator you'll probably want to read the source and understand its funky dependencies. Especially for Ethereum addresses, it's just easier to write your own generator that directly uses <a href="<https://github.com/ethereum/go-ethereum"><code>go-ethereum</code></a>.</p>
<h2 id="how"><a class="toclink" href="#how">How</a></h2>
<p>A wallet address in Ethereum is a <a href="https://info.etherscan.com/what-is-an-ethereum-address">42 character</a> hexadecimal string. "0x" takes the first two characters, so the actual address is 40 characters.</p>
<p>An Ethereum wallet address is generated in four stages. First, use a random entropy source to generate a private key (anything that samples <a href="https://arxiv.org/abs/1703.00559">vacuum fluctuations</a> will be fine). Second, derive a public key that corresponds to the generated private key. Next, calculate the address that corresponds to the public key. Finally, compute the checksum<label for="sn-0" class="margin-toggle sidenote-number"></label>
<input type="checkbox" id="sn-0" class="margin-toggle"/>
<span class="sidenote"> See <a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md">EIP-55</a></span>
(which affects the case of the hexadecimal letters). For the rest of this post I'm going to ignore the checksum stage because I don't care about casing.</p>
<p>It's important that the private key (and thus the public key and address) not be generated with anything deterministic. Using a non-random source will make it possible for an attacker to duplicate your generation process, generate your private/public keys, and gain access to your wallet.</p>
<p>This begs the question, how do we generate a custom address if we can only use a random source? Don't generate just one address, generate millions! It's a game of guess-and-check. You can continually generate addresses, until you find one that matches your criteria. The below pseudo code demonstrates an example of searching for a substring at the beginning of an address.</p>
<div class="codehilite"><pre><span></span><span class="k">def</span> <span class="nf">personalized_address</span><span class="p">(</span><span class="n">vanity_prefix</span><span class="p">:</span> <span class="n">string</span><span class="p">)</span>
<span class="n">private_key</span> <span class="o">=</span> <span class="n">generate_key</span><span class="p">()</span>
<span class="n">public_key</span> <span class="o">=</span> <span class="n">generate_public_key</span><span class="p">(</span><span class="n">private_key</span><span class="p">)</span>
<span class="n">address</span> <span class="o">=</span> <span class="n">generate_address</span><span class="p">(</span><span class="n">public_key</span><span class="p">)</span>
<span class="k">if</span> <span class="n">address</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="n">vanity_prefix</span><span class="p">)</span>
<span class="c1"># Securely save the private key</span>
</pre></div>
<p>You might think this process will be slow (it is). But, for short substrings (~5 characters) the generation can be done in a few minutes. There are 16 hexadecimal characters (again, ignoring the checksum).
When looking for an address that starts with an <code>n</code> length substring, the probability of finding a match in one iteration is <code>(1/16)^n</code><label for="sn-1" class="margin-toggle sidenote-number"></label>
<input type="checkbox" id="sn-1" class="margin-toggle"/>
<span class="sidenote"> The probability is <code>(40-n)</code> times higher if you don't care where the substring is</span>
.
The probability of finding a match after <code>i</code> iterations is <code>1-(1-p)^i</code> where <code>p</code> is <code>(1/16)^n</code> and <code>n</code> is the substring length<label for="sn-2" class="margin-toggle sidenote-number"></label>
<input type="checkbox" id="sn-2" class="margin-toggle"/>
<span class="sidenote"> This comes from the <a href="https://en.wikipedia.org/wiki/Binomial_distribution">binomial distribution</a></span>
.
I can generate ~16,000 addresses/sec on my laptop. This means you can find a 5 character substring with 99% probability in about 5 minutes. You can find a 6 character substring (99% probability) in about 80 minutes, and an 8 character substring in around 15 days.</p>
<p>And that's all. I leave it as an exercise for the reader to implement the code for themselves (hint: take a look at <a href="https://github.com/ethereum/go-ethereum/tree/master/crypto"><code>go-ethereum</code></a>).</p>Mechs n' Match2021-01-31T21:00:00-07:002021-01-31T21:00:00-07:00Claytag:www.inmoth.ca,2021-01-31:/mechs-n-match.html<p>A game made during the recent Global Game Jam</p><h2 id="the-game"><a class="toclink" href="#the-game">The Game</a></h2>
<p>This year we participated in the Global Game Jam with a few friends. The theme was "Lost and Found".
To that end, the game has the player scrounge through abandoned weapon parts looking for something to
outfight their mech for a series of battles with a diverse group of enemies. </p>
<p><a href="apps/MechsnMatch/MechsnMatch.html">You can play it now in your browser!</a></p>
<p>The game is also available for Windows, Linux, and Android on our <a href="https://globalgamejam.org/2021/games/mechs-n-match-4">Global Game Jam page</a>.</p>
<h2 id="the-team"><a class="toclink" href="#the-team">The Team</a></h2>
<p>Lukas was our software architect, he handled setting up the framework that held up the entire game. He
also did the lion's share of the coding for the Scrounging mini game. He is responsible for the ease of
which we built the game without resorting to any unfortunate hacks</p>
<p>Zach created all of our art assets. He inked everything by hand and then used his computer to
add colour. You can thank him for the creative visuals and the high level of polish that the game presents.
Much of the gameplay and visual direction is a direct result of Zach's imaginative and whimsical mind.</p>
<p>You can check out high resolution images of Zach's work and some of his other projects on his <a href="https://zmschuster.com/mechs-n-match-global-game-jam-2021">website</a>!</p>
<p>Caleb was responsible for programming the combat scene which was certainly the most complex chunk of the game. </p>
<p>Clay mostly focused on visual effects and adding that extra level of pizazz that can only be achieved with custom shaders.</p>
<h2 id="the-engine-godot"><a class="toclink" href="#the-engine-godot">The Engine: Godot</a></h2>
<p>We chose Godot for this project. In part because Clay is a frequent contributor to Godot on an engine level, but also because Lukas has good experience with Godot on personal projects. </p>
<p>We found the experience completely smooth. Godot's scene system made it a breeze for us to have 3 coders working together on a small codebase
without bumping into one another. We were able to export the game to 4 platforms minutes before the deadline without hassle. But most importantly,
Godot allowed us to rapidly build systems in a clean way. We are confident that, if we return to this project, we can complete all our stretch goals
without having to rip apart our core systems.</p>Draw Tones!2018-08-19T17:00:00-06:002018-08-19T17:00:00-06:00Calebtag:www.inmoth.ca,2018-08-19:/draw-tones.html<p>A simple web app that allows a user to draw a volume envelope and a frequency envelope for a specific tone.</p><h2 id="background"><a class="toclink" href="#background">Background</a></h2>
<p>A few years ago I started a test project that would allow a user to have fine
grained control over a tone by drawing the frequency and volume envelopes
themselves. I've since come back to the project and added a little polish.
You can find the app below.</p>
<p><a href="apps/audiodraw/audio.html">Skip the words and jump to it!</a></p>
<h2 id="some-details"><a class="toclink" href="#some-details">Some details</a></h2>
<p>The idea is simple (and if you follow the link you'll see the implementation is even simpler), what if a creator could just draw what they wanted their sound to be? This project emerged out of that idea, and basic follows the vision. Albeit a rather simple more proof-of-concept version of that vision. I won't go into details here about how this was done (as it's actually rather simple) becuase the entire source, a little under 200 lines with some javascript glue, is <a href="https://github.com/CalebJohn/infinitemonkeytheorem/tree/gh-pages/apps/audiodraw">available on github.</a></p>
<p>Still not convinced? Check out this sweet screenshot showing a tone with an approximately gaussian volume envelope and a chirping frequency.</p>
<p><img alt="Screenshot of the application displaying a chirp" src="images/envelope.png" /></p>
<p>Notice that the above interface is plain:</p>
<ul>
<li>"Play Note" does exactly as it sounds (makes a sound)</li>
<li>"Toggle Line" switches between volume and frequency input</li>
<li>The slider allows you to change the timescale of the program
With that in place you can freely draw on the canvas to creat the tones of your dreams!</li>
</ul>
<p><a href="apps/audiodraw/audio.html">Now go ahead and create your own sounds!</a>
Please share anything cool you come up with!</p>
<p>This project was created with clojurescript and the <a href="https://github.com/r0man/sablono">sablono</a> library. You can see the <a href="https://github.com/CalebJohn/infinitemonkeytheorem/tree/gh-pages/apps/audiodraw/audio.cljs">entire file here</a>.</p>Endless Forest2018-06-24T11:26:00-06:002018-06-24T11:26:00-06:00Claytag:www.inmoth.ca,2018-06-24:/endless-forest.html<p>Trying to create an endless forest full of vistas</p><style>
#blocker {
position: fixed;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 3;
}
#instructions {
width: 100%;
height: 100%;
display: -webkit-box;
display: -moz-box;
display: box;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
box-orient: horizontal;
-webkit-box-pack: center;
-moz-box-pack: center;
box-pack: center;
-webkit-box-align: center;
-moz-box-align: center;
box-align: center;
color: #ffffff;
text-align: center;
cursor: pointer;
}
#top {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2;
}
div {
user-select: auto;
}
canvas {
user-select: none;
}
</style>
<div id="top" style="display:none">
</div>
<script src="scripts/three.min.js"></script>
<script src="scripts/EF_include/hsluv.min.js"></script>
<script src="scripts/EF_include/PointerLockControls.js"></script>
<script src="scripts/EF_include/DeviceOrientationControls.js"></script>
<script src="scripts/EF_include/perlin.js"></script>
<script src="scripts/EF_include/groundShader.js"></script>
<script src="scripts/EF_include/instanceShader.js"></script>
<script src="scripts/EF_include/infiniteTerrain.js"></script>
<script src="scripts/EF_include/Flora.js"></script>
<!--Seeded RNG from http://davidbau.com/archives/2010/01/30/random_seeds_coded_hints_and_quintillions.html-->
<script src="scripts/EF_include/seedrandom.min.js"></script>
<script src="scripts/EF_include/tween.min.js"></script>
<script src="scripts/EF_include/ConvexGeometry.js"></script>
<script src="scripts/EF_include/QuickHull.js"></script>
<script>
var clock, controls, scene, renderer, camera, terrain, deltas, waterPlane;
var init = function() {
clock = new THREE.Clock();
clock.start();
scene = new THREE.Scene();
scene.background = new THREE.Color(0xaaaaff);
scene.fog = new THREE.FogExp2( new THREE.Color(0xaaaaff), 0.003 );
camera = new THREE.PerspectiveCamera( 50, window.innerWidth/window.innerHeight, 0.1, 10000 );
renderer = new THREE.WebGLRenderer({antialias: true && quality.medium}); //move to SMAA for low end computers
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById("top").appendChild( renderer.domElement );
window.addEventListener( 'resize', onWindowResize, false );
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|BB|PlayBook|IEMobile|Windows Phone|Kindle|Silk|Opera Mini/i.test(navigator.userAgent)) {
// Take the user to a different screen here.
controls = new THREE.DeviceOrientationControls(camera);
var blocker = document.getElementById( 'blocker' );
blocker.remove();
var list = document.getElementsByTagName("div");
for (var i=0;i<list.length;i++) {
list[i].style.userSelect = "none";
}
} else {
controls = new THREE.PointerLockControls( camera );
scene.add( controls.getObject() );
}
terrain = new Terrain(360, 10, camera, scene);
//place a single blue plane in the scene that moves with the camera to act as water
var waterGeometry = new THREE.PlaneBufferGeometry(720, 720);
var waterMaterial = new THREE.MeshBasicMaterial( {color: 0x4477aa} );
waterPlane = new THREE.Mesh( waterGeometry, waterMaterial );
scene.add( waterPlane );
waterPlane.rotateX(-Math.PI*0.5);
waterPlane.position.y = -30;
//just for debugging terrain
//var directionalLight = new THREE.DirectionalLight( 0xffffff, 1.5 );
//scene.add( directionalLight );
//this requires:
//c) a plant generator for each tile //including grass, flowers, maybe shrubs
//ii) flowers can be done same as rock and trees
//h) Monuments that have a random chance at spawning
deltas = [];
controls.update(0, terrain.group);
terrain.update( controls.getObject().position.clone() );
renderer.render(scene, camera);
animate();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
var animate = function (time) {
requestAnimationFrame( animate );
var delta = clock.getDelta();
controls.update( delta , terrain.group);
if (controls.enabled) {
terrain.update( controls.getObject().position.clone() );
TWEEN.update(time);
waterPlane.position.x = controls.getObject().position.x;
waterPlane.position.z = controls.getObject().position.z;
renderer.render(scene, camera);
//record frame time
deltas.push(delta);
var ft = 0;
for (let i=0;i<deltas.length;i++) {
ft+=deltas[i];
}
ft/=deltas.length;
while (deltas.length > 100) { deltas.pop(); }
//console.log(ft*1000);
}
};
var setQuality = function(it) {
blocker.style.display = "block";
window.quality = {low: true, medium: false, high:false};
if (it.value != "Low") {
window.quality.medium = true;
if (it.value == "High") {
window.quality.high = true;
}
}
window.low_gravity = document.getElementById( 'gravity' ).checked;
window.free_fly = document.getElementById( 'flight' ).checked;
it.parentNode.remove();
document.getElementById("top").style.display = '';
init();
};
</script>
<div>
<p>Select Options:</p>
<input type="radio" id="gravity" name="gravity" value="on">
<label for="gravity">Low-Gravity</label>
<input type="radio" id="flight" name="flight" value="on">
<label for="flight">Free Fly</label>
<p>Select Desired Quality:</p>
<input type="button" id="Low"
name="contact" value="Low" onclick="setQuality(this)">
<input type="button" id="Medium"
name="contact" value="Medium" onclick="setQuality(this)">
<input type="button" id="High"
name="contact" value="High" onclick="setQuality(this)">
</div>
<!--Used directly from the threejs pointer lock control example. https://github.com/mrdoob/three.js/blob/master/examples/misc_controls_pointerlock.html-->
<div id="blocker" style="display:none">
<div id="instructions">
<span style="font-size:40px">Click to play</span>
<br />
(W, A, S, D = Move, SPACE = Jump, MOUSE = Look around)
</div>
</div>
<p>Trying to put together a few ideas I have had going for awhile.</p>Mountain Study2016-02-23T13:53:00-07:002016-02-23T13:53:00-07:00Claytag:www.inmoth.ca,2016-02-23:/mountain-study.html<p>A study into a specific art style for a project I am working on</p><script src="scripts/processing.min.js"></script>
<p><center><canvas id="proc-canvas" class="processing" data-processing-sources="scripts/Mountain_study.pde"></canvas></center></p>
<p>This is a quick experiment I threw together this weekend to play with simple scenes and get an idea about how I want the art direction to go in a future project.</p>