<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Nat Chin</title><description>A minimal hugo theme focus on content</description><link>https://natchineth.com/</link><language>en</language><copyright>Copyright 2026, Calvin Tran</copyright><lastBuildDate>Tue, 28 Apr 2026 15:49:43 -0400</lastBuildDate><generator>Hugo - gohugo.io</generator><docs>http://cyber.harvard.edu/rss/rss.html</docs><atom:link href="https://natchineth.com//atom.xml" rel="self" type="application/atom+xml"/><item><title>When Bitwise NOT breaks Differential Tests</title><link>https://natchineth.com/posts/nearly-slipped-away/</link><description>&lt;p>When you build your test suite to work for you, it &lt;em>really&lt;/em> works.&lt;/p>
&lt;p>In a recent engagement, I used differential testing to verify if two implementations of the same logic—one in Golang, one in Solidity—were actually identical. This was a perfect use-case for differential fuzzing - logic that looks the same, under manual review. But looks aren&amp;rsquo;t everything.&lt;/p>
&lt;p>To find them, I built a &amp;ldquo;fuzzing harness.&amp;rdquo; Imagine a monkey smashing a keyboard, taking that random input, and feeding it to the equivalent program in Solidity and Golang. If the Go version succeed on the input but the Solidity version hates it (or vice-versa), the fuzzer errors.&lt;/p>
&lt;h3 id="diggin-into-the-code">Diggin&amp;rsquo; into the code&lt;/h3>
&lt;p>The goal was for these two implementations to be identical.&lt;/p>
&lt;p>&lt;strong>Golang Original:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-golang" data-lang="golang">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// ensure MAP_ANONYMOUS is set and fd == -1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#00a8c8">if&lt;/span> &lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">flags&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">val&lt;/span>&lt;span style="color:#111">()&lt;/span>&lt;span style="color:#f92672">&amp;amp;&lt;/span>&lt;span style="color:#ae81ff">0x20&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#75af00">fd&lt;/span> &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#75af00">u64Mask&lt;/span>&lt;span style="color:#111">()&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">addr&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#75af00">u64Mask&lt;/span>&lt;span style="color:#111">()&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">errCode&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#75af00">toU64&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">0x4d&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#75715e">// no error
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#111">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Solidity Original:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-solidity" data-lang="solidity">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">switch&lt;/span> &lt;span style="color:#111">or&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#111">iszero&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#111">and&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#111">flags&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#ae81ff">0x20&lt;/span>&lt;span style="color:#111">)),&lt;/span> &lt;span style="color:#111">not&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#111">eq&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#111">fd&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">u64Mask&lt;/span>&lt;span style="color:#111">())))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">case&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">addr&lt;/span> &lt;span style="color:#f92672">:=&lt;/span> &lt;span style="color:#111">u64Mask&lt;/span>&lt;span style="color:#111">()&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">errCode&lt;/span> &lt;span style="color:#f92672">:=&lt;/span> &lt;span style="color:#111">toU64&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">0x4d&lt;/span>&lt;span style="color:#111">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="eurekaor-kind-of">Eureka&amp;hellip;.or kind of&lt;/h3>
&lt;p>At a high level, both codebases were intended to return 1 on their first lines, to enter the &lt;code>if&lt;/code>/&lt;code>switch&lt;/code> statement. Instead, the program produced two separate numeric outputs, which was miscalculated. The fuzzer had found a deviation in which one of the calculations uses a padded variable, causing a discrepancy.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Step&lt;/th>
&lt;th>Golang Logic&lt;/th>
&lt;th>Solidity Logic&lt;/th>
&lt;th>Match?&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1. Check Flags&lt;/td>
&lt;td>&lt;code>31&amp;amp;0x20 ==0 -&amp;gt; 1&lt;/code>&lt;/td>
&lt;td>&lt;code>isZero(and(31, 0x20)) -&amp;gt; 1&lt;/code>&lt;/td>
&lt;td>✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2. Equality Check **&lt;/td>
&lt;td>&lt;code>fd == u64Mask() = 1&lt;/code>&lt;/td>
&lt;td>&lt;code>eq(18446744073709551551, u64Mask()) = 0000000000000000000000000000000000000000000000000000000000000000&lt;/code>&lt;/td>
&lt;td>❓&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3. The NOT&lt;/td>
&lt;td>Result is already &lt;code>1&lt;/code>&lt;/td>
&lt;td>&lt;code>1111111111111111111111111111111111111111111111111111111111111111 -&amp;gt; 0xff.ff&lt;/code>&lt;/td>
&lt;td>❌&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>*Note: Step 2 in Golang logic combines step 2 and 3 in one. This is why the result is already 1.&lt;/p>
&lt;h3 id="what-happened">What happened?&lt;/h3>
&lt;p>In Golang, the result of the check is a literal &lt;code>1&lt;/code>. The logic is only a single bit - this is &lt;strong>expected behaviour.&lt;/strong>&lt;/p>
&lt;p>In Yul, the &lt;code>not()&lt;/code> operator is applied &lt;em>bitwise&lt;/em>. When the equality check &lt;code>eq(fd, u64Mask())&lt;/code> returned a 0, it was interpreted as &lt;code>00...00&lt;/code>. Applying the &lt;code>not()&lt;/code> operator on the 32 byte word of zeroes, flipped &lt;strong>every single bit.&lt;/strong>&lt;/p>
&lt;p>The result in Solidity wasn&amp;rsquo;t a &lt;code>1&lt;/code> - it was a &lt;code>1111111111111111111111111111111111111111111111111111111111111111&lt;/code>.&lt;/p>
&lt;p>Because the Solidity code used a &lt;code>switch&lt;/code> statement, which expected a result of &lt;code>1&lt;/code>, the control flow did not enter the branch. In Golang, the same path resulted in a &lt;code>1&lt;/code>, which ran the associated code. This resulted in the final result of both implementations to be mismatched.&lt;/p>
&lt;h4 id="evolution-of-the-fuzzer">Evolution of the Fuzzer&lt;/h4>
&lt;p>The fuzzer went through four distinct iterations:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>V0 (Sanity Check):&lt;/strong> - Simple success/failure check - if one version reverts with input, both must revert and vice versa.&lt;/li>
&lt;li>&lt;strong>V0.5 (Error Matching):&lt;/strong> - Tightened constraints. If both versions fail, the error codes must match.&lt;/li>
&lt;li>&lt;strong>V1 (Input Targeting):&lt;/strong> - Defined specific numeric ranges for inputs to ensure that the fuzzer could explore additional flows.&lt;/li>
&lt;li>&lt;strong>V2 (Coverage Bumper):&lt;/strong> - Analyzed branch coverage of unit tests, and realized that this if statement had no coverage. I manually seeded the fuzzer with inputs that should trigger this statement. &lt;strong>The fuzzer found the deviation in seconds&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h3 id="smart-dumb-monkey-smashing-keyboard">&lt;em>Smart&lt;/em> &lt;del>Dumb&lt;/del> Monkey-Smashing-Keyboard&lt;/h3>
&lt;p>I mentioned that fuzzing is like a monkey smashing a keyboard, but a &amp;ldquo;dumb&amp;rdquo; monkey uses input space inefficiently. If you&amp;rsquo;re testing liquidity pools or low-level masks, a random &lt;code>uint256&lt;/code> will almost always just trigger either the same happy paths already discovered, or generic &amp;ldquo;Balance too low&amp;rdquo; or more likely, a series of reverts that causes the fuzzer to get nowhere.&lt;/p>
&lt;p>A &amp;ldquo;Smart&amp;rdquo; Fuzzer (V2) uses directed input generation. By constraining the &amp;ldquo;monkey&amp;rdquo; to smash keys within specific numeric ranges (like values just above or below u64Mask), we maximize coveraged reached by the fuzzer.&lt;/p>
&lt;p>This is the intersection of security research and development: knowing where the code is likely to have issues and guiding the tools to look there. It is highly improbable the fuzzer would have found this specific bit-flip on its own without those manual adjustments to the harness. It is also highly improbable that this bug would be found via manual review.&lt;/p>
&lt;h3 id="lessons-for-the-road">Lessons for the Road&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>Logical vs. Bitwise:&lt;/strong> In Yul, always use &lt;code>iszero(x)&lt;/code> for negation. Avoid &lt;code>not()&lt;/code> unless you are actually manipulating bits.&lt;/li>
&lt;li>&lt;strong>Stop and think:&lt;/strong> Focus on the areas your unit tests don&amp;rsquo;t reach. Use coverage reports as a gauge for where to target your fuzzer.&lt;/li>
&lt;li>&lt;strong>Always Deterministic:&lt;/strong> Never ignore a failed test, especially if it&amp;rsquo;s only &lt;em>sometimes&lt;/em> fails. If any test fails once, you&amp;rsquo;re likely sitting on a bug. Understand it. Investigate it.&lt;/li>
&lt;/ol></description><author>Calvin Tran</author><guid>https://natchineth.com/posts/nearly-slipped-away/</guid><pubDate>Tue, 28 Apr 2026 15:49:43 -0400</pubDate></item><item><title>Curvance Invariants Unleashed</title><link>https://natchineth.com/posts/curvance-invariants-unleashed/</link><description>&lt;p>Initially posted on Trail of Bits Blog: &lt;a href="https://blog.trailofbits.com/2024/04/30/curvance-invariants-unleashed/">Curvance: Invariants Unleashed&lt;/a>&lt;/p></description><author>Calvin Tran</author><guid>https://natchineth.com/posts/curvance-invariants-unleashed/</guid><pubDate>Tue, 30 Apr 2024 15:46:49 -0400</pubDate></item><item><title>The Ultimate Secure Wallet</title><link>https://natchineth.com/posts/the-ultimate-secure-wallet/</link><description>&lt;p>Initially posted on &lt;a href="https://medium.com/stk-token/state-channels-the-ultimate-secure-wallet-9a17da38bd77">State Channels Smart Contract: The Ultimate Secure Wallet&lt;/a>&lt;/p>
&lt;hr>
&lt;p>Safety has always been a major concern in the cryptocurrency industry. Once in a while, we’ll see cryptocurrency exchanges get hacked for millions of dollars. The concern of security is among one of the top reasons why people are reluctant to get into the cryptocurrency space. At STK, the security and the safety of your assets is always our top priority. Instead of a traditional wallet, we made a choice to use the smart contract and payment channels for our app. Due to the security nature of these contracts, we have not pushed our contracts up to mainnet yet. They will be pushed on mainnet soon after auditing.&lt;/p>
&lt;h3 id="storing-crypto-in-a-wallet-vs-a-smart-contract">Storing Crypto in a Wallet vs a Smart Contract&lt;/h3>
&lt;p>A crypto wallet is always generated using a private/public key pair. In other words, when you sign up for a new address with something like MyEtherWallet, you will get a “safety package” — containing your private key that you should be keeping safe. With MetaMask, you would have the mnemonic with the 12 seed words. If you forget these, you lose access entirely to your account. If you accidentally make your private key accessible to someone else, this person has full control over your funds.&lt;/p>
&lt;p>A smart contract, on the other hand, is different. There is no explicit private/public key differentiation — the address of the contract is the contract’s public key, but the private key is never made available. Unlike a crypto wallet, a smart contract does what its assigned to do, through code. For instance, our STK-smart-contracts is set to interact with only STK tokens. If other ERC20 tokens are sent to the smart contract, they are lost. This contract does not accept ETH transfers — so, if a customer accidentally transfers Ether to the smart contract, our contract will explicitly refuse it and refund it back. The only thing a user would lose is the gas fees for execution of said operation. Our smart contract also specifies that only STACK and User’s address can settle a channel. The settle function controls the movement of STK Tokens through a payment channel. Due to the restriction of changing the state of the channel, once an IOU has been submitted to change the state of a channel, no one can control the refund or transfers of tokens except yourself. The cryptocurrency you own is completely within your control.&lt;/p>
&lt;h3 id="transactions-are-safer-with-state-channels">Transactions are safer with state channels&lt;/h3>
&lt;p>In the initialization of a state channel, there are a few addresses that we make use of:&lt;/p>
&lt;p>user address: the address of the wallet in which your funds are stored. When a channel is settled and the funds are returned back to the user, the tokens/ETH will be returned to this address.
signer address: the private and public key pair generated on your device for signing purposes*
recipient address: the address of the STACK’s wallet. When a channel is settled, the IOU amount will be transferred to STACK.
channel address: the address of your personal state channel. When you deposit funds, this is where your funds are stored.&lt;/p>
&lt;ul>
&lt;li>For you to sign transactions, the app automatically generates private and public key pairs. All transactions (IOU’s) are signed with this private key. These are only ever used to sign transactions that are sent to the smart contract. STACK never interacts with your signer’s private key. Notice your signer’s key never holds funds. Its only purpose is to sign off-chain transactions.&lt;/li>
&lt;/ul>
&lt;p>With the following setup, transactions are safer with state channels, because there is no single point of failure. No matter what tokens there are in our payment channel, the funds inside it are always safe. Even if STK’s backend or database fails or goes down, the user is in complete control of the funds within the state channel.&lt;/p>
&lt;h3 id="network-congestion">Network Congestion&lt;/h3>
&lt;p>Back in December of last year, when Cryptokitties was released officially, the sheer number of transactions on the Ethereum Network skyrocketed and caused complete congestion. A few people in the Reddit AMA asked about the effects of network congestion on our state channels.&lt;/p>
&lt;p>We sign the IOU’s off chain with the signer’s public and private key pair. These do not need to interact with the network. When you decide to settle a channel, these transactions will be pushed on-chain. During the settle period, you have the option to transfer any unused tokens back to your address. Your transaction amount will also be transferred to STACK. In other words, the end user is not affected by congestion on the network. This is ultimately safer for customers, because they are not affected by the volatility and the ever-changing block times on the network. To read more about how the IOU and signing off-chain transactions work, check this out.&lt;/p>
&lt;p>If you’re intrigued, feel free to check out our crypto beta demo located here: &lt;a href="https://www.youtube.com/watch?v=Yhmjks3eFIA">https://www.youtube.com/watch?v=Yhmjks3eFIA&lt;/a>, where we go through a live purchase at a local coffee shop.&lt;/p></description><author>Calvin Tran</author><guid>https://natchineth.com/posts/the-ultimate-secure-wallet/</guid><pubDate>Tue, 31 Jul 2018 15:40:47 -0400</pubDate></item><item><title>DeltaHacks Interview</title><link>https://natchineth.com/posts/deltahacks-interview/</link><description>&lt;p>Initially posted on &lt;a href="https://softwarehamilton.com/2018/01/11/interview-deltahacks-iv-natalie-chin/">Software Hamilton&lt;/a>&lt;/p></description><author>Calvin Tran</author><guid>https://natchineth.com/posts/deltahacks-interview/</guid><pubDate>Thu, 11 Jan 2018 15:23:03 -0400</pubDate></item></channel></rss>