<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:dc="http://purl.org/dc/elements/1.1/">
    <channel>
        <title>bhar · gav</title>
        <link>https://bhargav.wtf</link>
        <description><![CDATA[Posts from bhargav.wtf]]></description>
        <atom:link href="https://bhargav.wtf/rss.xml" rel="self"
                   type="application/rss+xml" />
        <lastBuildDate>Sun, 19 Oct 2025 00:00:00 UT</lastBuildDate>
        <item>
    <title>Quantum Random Number Generator</title>
    <link>https://bhargav.wtf/blog/qrng/index.html</link>
    <description><![CDATA[<blockquote>
<p>First time trying anything quantum computing w/ my friend Shuhul from Caltech :)</p>
</blockquote>
<h1 id="prompt">Prompt</h1>
<p><img src="/images/quantum/prompt.png" alt="Prompt" /></p>
<p><img src="/images/quantum/example.png" alt="Example Circuit" /></p>
<p>In this project for QiskitFest@UCLA, we implement a Quantum random number generator that uniformly samples <span class="math inline">0...N</span> in the fewest number of qubits and gates as possible. We include 4 sample circuits ranging from a naiive implementation to the most optimal design.</p>
<h1 id="preliminaries">Preliminaries:</h1>
<p>Quickly, this problem of sampling <span class="math inline">0..N</span> is trivial for the case <span class="math inline">N = 2^k</span>, since we can use a series of Hadamard transforms to evenly split the amplitudes across all <span class="math inline">2^k</span> computational basis states. Each Hadamard gate doubles the number of possible outcomes, creating a uniform superposition where every basis state has equal probability <span class="math inline">1/2^k</span>. This only becomes interesting when <span class="math inline">N</span> isn’t just a power of <span class="math inline">2</span>, for which we explore a series of circuit constructions. Naiively, we could just simulate extra states and use rejection sampling, but we wanted to design this circuit to operate purely on a quantum state.</p>
<h1 id="v0-naiive-control">V0: Naiive Control</h1>
<p><img src="/images/quantum/v0.png" alt="Ry Rotation Gates" />
<em><span class="math inline">R_y</span> Rotation Gates</em></p>
<p>In this circuit, we manually set the probabilities of each qubit using cascaded <span class="math inline">R_y</span> rotations, but this approach quickly becomes impractical. Each new qubit requires multiple controlled rotations whose angles depend on all previous qubits, causing the gate count and circuit depth to grow quadratically. These controlled rotations are also error-prone and hardware-expensive, and the precise rotation values are hard to compute or calibrate.</p>
<h1 id="v1-binary-expansion">V1: Binary Expansion</h1>
<p>Since for powers of 2, building a uniform sampler is straightforward using Hadamard gates, we instead view <span class="math inline">N</span> as a sum of powers of two, <span class="math inline">N = 2^{k_1} + 2^{k_2} + \cdots + 2^{k_m}</span>. By constructing uniform superpositions for each <span class="math inline">2^{k_i}</span> block and combining them with conditional rotations, we can approximate a uniform distribution over <span class="math inline">0...N-1</span> while keeping the circuit shallow and efficient.</p>
<h2 id="example">Example</h2>
<p>Here is an example of the breakdown for <span class="math inline">N = 7</span>:</p>
<p><img src="/images/quantum/v1_explanation.png" alt="Binary tree" /></p>
<p>And here is the corresponding circuit representation</p>
<p><img src="/images/quantum/v1.png" alt="V1 Circuit" /></p>
<p>We recursively sample over <span class="math inline">N = 7</span> states as a binary tree of conditional probabilities. At each branching point, the circuit applies a rotation that splits the amplitude proportionally to how many valid states remain in each subtree.</p>
<p>For instance, the first qubit has probability <span class="math inline">P(4/7)</span> of being <span class="math inline">|0\rangle</span> and <span class="math inline">P(3/7)</span> of being <span class="math inline">|1\rangle</span>. These are then recursively refined until all of the basis states are assigned equal amplitude.</p>
<h3 id="some-math">Some Math</h3>
<blockquote>
<p>For the first qubit, we want to split the probability mass between the left and right branches according to how many valid states lie under each.
<span class="math display">
P(0) = \frac{4}{7}, \quad P(1) = \frac{3}{7}.
</span>
An <span class="math inline">R_y(\theta)</span> gate on <span class="math inline">|0\rangle</span> prepares
<span class="math display">
R_y(\theta)|0\rangle = \cos\left(\frac{\theta}{2}\right)|0\rangle + \sin\left(\frac{\theta}{2}\right)|1\rangle,
</span>
giving probabilities <span class="math inline">\cos^2(\frac{\theta}{2})</span> and <span class="math inline">\sin^2(\frac{\theta}{2})</span>. Setting <span class="math inline">\cos^2(\frac{\theta}{2}) = \frac{4}{7}</span> yields
<span class="math display">
\boxed{\theta = 2\arccos\sqrt{\frac{4}{7}} = 2\arcsin\sqrt{\frac{3}{7}}.}
</span></p>
</blockquote>
<h1 id="v2-consolidating-hadamards">V2: Consolidating Hadamards</h1>
<p><img src="/images/quantum/v2.png" alt="V2 Image" /></p>
<p>Since the Hadamard gate is unitary, applying it twice yields the identity operation <span class="math inline">H^\dagger H = I</span>. Previously, we used a Hadamard gate with a control to emulate an anti-control, but since H is self-inverse, we can simplify the circuit by directly using an anti-control instead, reducing unnecessary gates.</p>
<h1 id="v3-complement-graph">V3: Complement Graph</h1>
<p><img src="/images/quantum/v3.png" alt="V3 Image" /></p>
<p>This approach first builds a fully balanced superposition over <span class="math inline">2^k</span> states using Hadamard gates, then applies conditional <span class="math inline">R_y</span> rotations to correct the amplitudes for <span class="math inline">N &lt; 2^k</span>. The Hadamard layer creates an even base distribution, while the correction step prunes invalid branches, creating an even more compact circuit.</p>
<h3 id="example-1">Example</h3>
<p>For <span class="math inline">N=9</span>, where the first split is <span class="math inline">N_0=8</span> and <span class="math inline">N_1=1</span>, the rotation angle is</p>
<p><span class="math display">\theta = 0.680 \text{ rad},</span></p>
<p>so relative to a Hadamard baseline (<span class="math inline">\pi/2</span>), the correction is</p>
<p><span class="math display">\Delta\theta = -0.891 \text{ rad}.</span></p>
<h1 id="v4-log-depth-hadamard-expansion">V4: Log-depth Hadamard Expansion</h1>
<p><img src="/images/quantum/v4.png" alt="V4 Image" /></p>
<p>To build a uniform quantum state over <span class="math inline">0 \ldots N-1</span>, we basically follow the binary expansion of <span class="math inline">N</span>. Each power of two, <span class="math inline">2^j</span>, is like a “block” of evenly distributed states, and you only need <span class="math inline">j</span> Hadamards to generate it, since each Hadamard doubles your reach. So if <span class="math inline">N=9=8+1</span>, you use three
Hadamards to cover the first eight states and then just patch in the last one. The key idea is that you don’t brute-force all <span class="math inline">N</span> outcomes – you
reuse the structure of powers of two to get there in about <span class="math inline">\log N</span> steps, with a few small corrections at the end to make everything line up
perfectly.</p>
<p>There might be a slightly more clever way of reducing the number of control gates, but this is the best we could do up to gates and number of qubits.</p>
<h1 id="exercise-for-fun">Exercise (for fun)</h1>
<p>Let <span class="math inline">k = \lceil \log_2 N \rceil</span> and <span class="math inline">D = 2^k - N</span>.</p>
<p>Estimate when the complement approach (cost <span class="math inline">O(D)</span>)
becomes less efficient than the Hadamard tree method (cost
<span class="math inline">O(\log N)</span>).</p>]]></description>
    <pubDate>Sun, 19 Oct 2025 00:00:00 UT</pubDate>
    <guid>https://bhargav.wtf/blog/qrng/index.html</guid>
    <dc:creator>Bhargav</dc:creator>
</item>
<item>
    <title>Private Money: Part 4</title>
    <link>https://bhargav.wtf/blog/zcash-4/index.html</link>
    <description><![CDATA[<p><strong>Series Index</strong><br />
- <a href="https://bhargav.wtf/blog/zcash-1/">Part 1</a>: State-of-the-art private money protocols<br />
- <a href="https://bhargav.wtf/blog/zcash-2/">Part 2</a>: Project Tachyon preliminaries<br />
- <a href="https://bhargav.wtf/blog/zcash-3/">Part 3</a>: Set non-inclusion accumulators</p>
<hr />
<h1 id="introduction">Introduction</h1>
<p>In the <a href="https://bhargav.wtf/blog/zcash-1/">first blog</a> post in this series, I discussed the potential vulnerabilities of the active Monero ring signature protocol and how this sort of heuristic security can lead to <em>flooding attacks</em> and <em>statistical tracing</em>. But since then, the Monero community proposed the “FCMPs+SA+L” to defend against such spam attacks. By ensuring that verification cost grows very slowly compared to spam size (using logarithmic-size proofs with recursive composition) and using full-set membership (through the initial FCMP proposal), FCMP++ promises to address the DoS-style spam vectors.</p>
<blockquote>
<p>In contrast to relying on ad-hoc sampling of decoys, FCMP++ uses bulletproofs to create provable non-inclusion guarantees – every spend comes with a succinct proof that it hasn’t been spent already without requiring global scans or probabilistic mixing.</p>
</blockquote>
<p>This is a <strong>substantial change</strong> from the active Monero protocol. FCMP++ abandons this heuristic model entirely. Instead of sampling decoys, every spend is proven against the entire accumulator of outputs. This proof is <em>succinct</em> and <em>compositional</em> because of the recursive structure of curve trees and bulletproof-style arguments. But now the trust assumptions change: instead of relying on statistical obfuscation, FCMP++ security is based on the algebraic properties of <a href="https://en.wikipedia.org/wiki/Elliptic-curve_cryptography">elliptic curves</a>.</p>
<h1 id="a-toy-example-of-the-fcmp-tree">A Toy Example of the FCMP++ Tree</h1>
<p>Suppose that Monero has only <span class="math inline">8</span> outputs (in reality this would be in the millions). Let them be <span class="math inline">o_1, o_2, \cdots, o_8</span>. At a high-level, the FCMP++ would look like this:</p>
<h2 id="commitments-for-each-output">Commitments for each output</h2>
<p>Let <span class="math inline">C_i</span> be the commitment for <span class="math inline">o_i</span>, which is used to hide the value of <span class="math inline">o_i</span> but has some algebraic properties.</p>
<h2 id="construct-a-merkle-curve-tree-of-commitments">Construct a Merkle (Curve) tree of commitments</h2>
<p>The commitments are paired and combined in a Merkle-tree</p>
<iframe class="quiver-embed" src="https://q.uiver.app/#q=WzAsMTUsWzQsMCwiUj1mKE5fezIsMX0sIE5fezIsMn0pIl0sWzIsMSwiTl97MiwxfT1mKE5fezEsMX0sIE5fezEsMn0pIl0sWzYsMSwiTl97MiwyfT1mKE5fezEsM30sIE5fezEsNH0pIl0sWzEsMiwiTl97MSwxfT1mKENfMSwgQ18yKSJdLFszLDIsIk5fezEsMn09ZihDXzMsIENfNCkiXSxbNSwyLCJOX3sxLDN9PWYoQ181LCBDXzYpIl0sWzcsMiwiTl97MSw0fT1mKENfNSwgQ182KSJdLFswLDMsIkNfMSJdLFsxLDMsIkNfMiJdLFsyLDMsIkNfMyJdLFszLDMsIkNfNCJdLFs0LDMsIkNfNSJdLFs1LDMsIkNfNiJdLFs2LDMsIkNfNyJdLFs3LDMsIkNfOCJdLFs3LDNdLFs4LDNdLFszLDFdLFs5LDRdLFsxMCw0XSxbMTEsNV0sWzEyLDVdLFsxMyw2XSxbMTQsNl0sWzEsMF0sWzIsMF0sWzQsMV0sWzUsMl0sWzYsMl1d&embed" width="100%" style="border-radius: 8px; border: none; pointer-events: none;">
</iframe>
<p><em>Where <span class="math inline">f</span> usually is a <a href="https://datatracker.ietf.org/doc/rfc9380/">hash-to-curve</a> function</em>.</p>
<h2 id="membership-proofs">Membership proofs</h2>
<p>To prove that <span class="math inline">o_3</span> has been spent for example, you generate a succinct membership proof that its commitment <span class="math inline">C_3</span> is included under the root <span class="math inline">R</span>, by providing the path up the tree <span class="math inline">C_3 \to N_{1,2} \to N_{2,1} \to R</span>. This convinces the verifier that <span class="math inline">C_3</span> is indeed part of the global accumulator without revealing which leaf you control.</p>
<p>But, membership itself is not sufficient: we have to also prove that <span class="math inline">o_3</span> has not been spent yet. In FCMP++, the aforementioned “+L” (Linkability) component of the proposal addresses this. With the membership proof, the spender publishes a unique linking tag <span class="math inline">L</span> derived from their secret key. Consensus nodes maintain a set of all seen tags such that any repeat indicates a double spend event. Zcash actively employs nullifiers in the same way that Monero proposes for their upgrade.</p>
<p>The distinguishing characteristic for FCMP++ is the underlying machinery. Membership in the curve tree is checked inside of a <strong>generalized bulletproof</strong> using inner-product arguments and the linkability tag is bound to the spend in a parallel proof.</p>
<p>Zcash uses <a href="https://github.com/zcash/halo2">Halo2</a> for this, which is a general-purpose proof system where gadgets can be reused inside a single circuit, making proofs modular and easier to audit.</p>
<p>Generalized Bulletproofs are not a universal circuit SNARK like in Halo2: <strong>you can’t just add a constraint to a global circuit</strong>. Instead, each property you want to prove (membership via a Merkle path, spend authorization with linkability, balance, or range) must be encoded as a specialized algebraic argument expressed through inner-product relations. These arguments are then delicately composed into a single aggregated proof, where Bulletproof’s logarithmic-size folding tricks excel at compressing many homogeneous instances (e.g. multiple range proofs) but don’t natively support heterogeneous ones. As a result, while “gadgets” exist in the sense of reusable argument templates, they are not modular plug-ins that can be audited independently: each must be hand-designed to fit the aggregation framework.</p>
<h1 id="more-on-merkle-esque-trees">More on Merkle-esque Trees</h1>
<p>Monero’s FCMP++ proposal is built on <em>curve trees</em>, which are <a href="https://research.protocol.ai/publications/curve-trees-practical-and-transparent-zero-knowledge-accumulators/campanelli2022d.pdf#:~:text=the%20random%20oracle%20model%20,level%20we%20use%20an%20appropriately">a shallow Merkle tree where the leaves and internal nodes are points over an elliptic curve</a>. Instead of hashing bitstrings, a curve tree uses commitments and alternating elliptic-curve cycles to build an accumulator. The prover then re-randomizes the commitments along the path and proves membership using a generalized Bulletproof and elliptic-curve divisors. This is very reminiscent of the incremental Merkle tree used in Zcash’s Orchard pool, where note commitments are appended to a fixed-depth tree and each spend proves membership in that tree.</p>
<p>Both schemes have to prevent double spends and do so through similar mechanisms. In Orchard, each note derives a nullifier, which are required to be unique via consensus rules. FCMP++ achieves the same thing with a linking tag: a ZK circuit that emits a tag derived from the spender’s secret key and includes a commitment <span class="math inline">R</span> to the randomness used to blind it. In both cases, a deterministic value tied to the note or output is recorded on‑chain, and the consensus layer rejects any repeat of that value, thereby preserving unspentness.</p>
<p>Project Tachyon, Zcash’s newest upgrade moves away from this sort of tree accumulator. Zcash currently uses a Merkle tree of note commitments and nullifiers which naturally fit in with Halo2, but the Tachyon upgrade plans to insert nullifiers into a new accumulator that supports efficient set-membership/non-membership proofs in a more succinct/cleaner hash-chain-style accumulator. This removes the necessity for <a href="https://seanbowe.com/blog/tachyon-scaling-zcash-oblivious-synchronization/#:~:text=services%20from%20learning%20sensitive%20information,This%20can%20be%20achieved">wallets to maintain Merkle-path witnesses and allows on-chain proofs to shrink even more</a> since now <strong>validators only need to check that a nullifier hasn’t appeared recently.</strong> Monero’s newest upgrade adopts an algebraic variant of a Merkle-tree approach that is currently being deprecated in favor of a leaner, more succinct accumulator in Tachyon <a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>.</p>
<h1 id="ecosystem-hurdles">Ecosystem Hurdles</h1>
<blockquote>
<p>Bulletproofs in Monero are experimental infrastructure with limited tooling in the broader ZK community, while PLONKish proof systems are well-understood and matured with complete compilers, DSLs, and even zkVMs. They are far less error-prone, easier to adopt, and therefore more auditable and secure in practice.</p>
</blockquote>
<p>Developer experience isn’t just a convenience factor but has direct security implications. Particularly for Bulletproofs which require <strong>bespoke inner-product arguments for every new property</strong>, and composing these arguments requires even more delicate care. With limited high-level tooling, the risk of subtle implementation flaws is higher.</p>
<p>Multiple critical exploits have been discovered in Monero security audits (documented <a href="http://suyash67.github.io/homepage/assets/pdfs/bulletproofs_plus_audit_report_v1.1.pdf">here</a> and <a href="https://blog.quarkslab.com/resources/2018-10-22-audit-monero-bulletproof/18-06-439-REP-monero-bulletproof-sec-assessment.pdf">here</a>), even in their more restricted implementations of Bulletproofs from a few years ago. To Monero’s credit, these vulnerabilities were identified through rigorous auditing processes. However, they demonstrate how challenging these cryptographic systems are to properly constrain—even in limited use cases. This naturally raises questions about the security implications of the generalized Bulletproofs that FCMP++ requires.</p>
<p>PLONKish systems have matured into an entire developer stack: there’s a full suite of DSLs, circuit libraries, recursive provers, and zkVMs that make them stable and production-grade. High-level languages like Circom, Noir, and Leo compile directly into PLONK circuits, with libraries like <code>gnark</code> and <code>jellyfish</code> providing ready-made gadgets. Recursive provers and zkVMs<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a> like <a href="http://succinct.xyz/">SP1</a> further expand the ecosystem.</p>
<p>PLONK secures billions of dollars in zkRollups such as Polygon zkEVM, zkSync, Scroll, and Aztec, with public gadgets subject to constant audits and scrutiny. Halo2, one of many PLONK-based protocols, benefits from this ecosystem: its modular circuits enable easy sharing of gadgets and optimizations. This breadth of reuse and review makes PLONKish systems established and less experimental in practice, while Bulletproofs, and by extension FCMP++, remain far more isolated within the broader developer ecosystem.</p>
<h1 id="final-thoughts">Final Thoughts</h1>
<p>With FCMP++, Monero is finally confronting some of the long-standing vulnerabilities in its anonymity model. By replacing ring signatures and heuristic decoy sampling with a global curve tree accumulator and full-chain membership proofs, Monero moves closer to the stronger, cryptographic security model that systems like Zcash pioneered. This is a significant shift both structurally for Monero but broadly for the definition of privacy for the industry: statistical obfuscation isn’t enough. Explicit, algebraic guarantees are necessary.</p>
<p>But Monero’s approach is complex: FCMP++ requires composing heterogeneous Bulletproofs with divisor arithmetic and custom gadgets, all within a monolithic proof system.</p>
<p>This is <strong>untested territory</strong>. Monero is building a one-off proving system for their uses that no other production-grade ecosystem shares in ZK. This is a bold but risky project: on one hand it addresses critical weaknesses in Monero’s current design but on the other it does so by venturing into complex and less established terrain. I’m curious to see how this pans out..</p>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>There is a derivation and implementation of the accumulator in Project Tachyon in <a href="https://bhargav.wtf/blog/zcash-3/">my previous blog</a>.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p>zkVMs lower the barrier to entry by letting developers write ordinary Rust (or any other RISCV language) programs that compile automatically into PLONK-style circuits, eliminating the need to hand-craft constraints. This expands the ecosystem and driving demand for PLONKish proof systems as the standard.<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>]]></description>
    <pubDate>Sun, 28 Sep 2025 00:00:00 UT</pubDate>
    <guid>https://bhargav.wtf/blog/zcash-4/index.html</guid>
    <dc:creator>Bhargav</dc:creator>
</item>
<item>
    <title>in the pool (tokyo summer)</title>
    <link>https://bhargav.wtf/blog/japan-summer/index.html</link>
    <description><![CDATA[<h1 id="some-ramblings">some ramblings</h1>
<p>On the flight over, the ocean looked like ink—flat from that height, heavy with what it refused to show. AirPods drowned out the cabin noise. My breathing was the only thing I could measure.</p>
<p>Months later, I was sitting in the Tokyo Metropolitan Library, finally writing. On the way, I passed a primary school. The gates were open. Children were playing baseball on the field. The air was thick enough to hold sound; it made their shouts feel close, even from the sidewalk. Their game had no clear beginning and no end, just like mine.</p>
<p>The library breathed a disciplined quiet; inside, my breathing felt too loud. Outside it was summer—damp Uniqlo Airisms, sunscreen, hot asphalt. Even small distances felt intentional—the distance between seats, the timing of footsteps. Bags were placed beneath chairs and not touched again. People arrived, badged in, and remained.</p>
<p>The room traded the effort of navigating the city for the discipline of staying. The quiet did not offer relief so much as structure. It set a pace and expected it to be kept.</p>
<p><img src="/images/life/writing.jpeg" alt="writing this irl" />
<em>here i am writing this. i’m not sure how i feel about it.</em></p>
<h1 id="the-promise">the promise</h1>
<p>When I told my friends I was going to Tokyo for the summer, they reacted as if I had already returned. They were excited for me, sometimes more than I was. I remember nodding and accepting their enthusiasm like a gift I don’t know where to put. I understood it later. My body didn’t wait.</p>
<p>Most people I know come to Japan the way they visit an exhibit or museum—wide-eyed, temporary, forgiven. There is a kind of freedom in that. You can be ignorant and call it charm. You can misread everything and leave before it has consequences.</p>
<p><img src="/images/life/airbnb.png" alt="airbnb image" />
<em>i was worried i was going to get another visit from the keisatsu</em></p>
<p>I stopped at the mouth of a Kabukicho back alley near the Godzilla statue—so narrow it felt like the city had drawn its shoulder in. Signs stacked upward in layers, bars and ramen shops tucked behind every corner. Touts <a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a> in gaudy chains drifted in and out like it was nothing. The air turned: urine, trash, stale smoke. Near Toyoko, kids hovered under LED light—gyaru makeup too bright for tired faces, cigarettes passed between small hands. I realized I’d been holding my breath.</p>
<p><img src="/images/life/uhoh.png" alt="uhoh" />
<em>uh oh..</em></p>
<p>I wasn’t here the way people come on vacation—wide-eyed, temporary, forgiven. I lived here. Which meant going in: through doors that didn’t care if I could read the signs, down hallways where the air changed and my instincts lagged. My cousin’s hotspot iPhone 4 sat half-dead in my pocket like a failing lifeline. If I didn’t come back out, it would be a missing person report in a language I could hardly speak, and then silence.</p>
<p>The first night I arrived, I couldn’t find my Airbnb. I walked the block twice, misread the entrances, slipped into the wrong building. There was a FamilyMart on the corner and a narrow back alley that smelled like damp concrete. I messaged the host. Again. And again, until I found the door.</p>
<p>And in the small room I had rented in a city where I was functionally illiterate, I did the most Gen Z thing possible: immediately checking the Wi-Fi. It flicked and failed. I stood hunched near the entrance, where the signal was barely alive, and called my concerned mother over WhatsApp.</p>
<p>And that was just the beginning.</p>
<p>Not the internship. Not the sightseeing. Not the friendships I would later make. This was the beginning: the comforts I usually carried—language, familiarity, the ability to disappear—were gone.</p>
<p><img src="/images/life/room.JPG" alt="my room" />
<em>i still can’t believe i brought a crushed belvita all the way from austin. it was multiple years old and unfortunately was my first meal in tokyo.</em></p>
<p>It was better than last summer’s place in San Francisco’s financial district by a lot. But it still wasn’t much.</p>
<p>A yellow air conditioner that never stopped working. Slippers waiting at the door. A fridge the color of cheap mint. A TV that I never used. And a closet so shallow it felt like the room was telling me, early, not to unpack.</p>
<div style="display: flex; justify-content: center; gap: 20px;">
    <img src="/images/life/austin_banksy.jpeg" alt="austin banksy" style="width: 50%; height: auto;" />
    <img src="/images/life/tokyo_banksy.jpeg" alt="tokyo banksy" style="width: 50%; height: auto;" />
</div>
<p><em>the same girl i’d seen on a wall in austin, near the bridge sun-faded, half-ignored, part of the outside world. here she was framed and clean—indoors, made tame.</em></p>
<p>And that kept happening this summer—familiar outlines showing up in new places. It wasn’t fate. It was just something repeating, and I kept noticing it.</p>
<div style="display: flex; justify-content: center; gap: 20px;">
    <iframe src="https://www.google.com/maps/embed?pb=!4v1757318713911!6m8!1m7!1sPYYs1dZV0egPAFlKf7_4gw!2m2!1d30.26283461921503!2d-97.74466928744275!3f128.99323098704417!4f-15.50496302294205!5f0.7820865974627469" width="600" height="450" style="border:0;" allowfullscreen="" loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>
</div>
<p><em>wow. it’s gone now.</em></p>
<p>Andy is building a humanoid company in Japan from scratch. Not “In Japan” as a vibe but as in suppliers, visas, language, parts that don’t arrive, company structure, culture, etc.</p>
<p>One afternoon, we were all in the meeting area discussing the number of degrees of freedom in the robot’s foot. Masato-san suggested four—start with a simple toy model and study it before enlarging it. Research-wise, it made a lot of sense. And my experience trying to train policies seemed to support it.</p>
<p>Andy listened and kindly said “this is engineering management 101”.</p>
<p>Four isn’t wrong, but it’s slow. I’ve always liked the “start simple” story because it feels clean, almost academic. Andy’s point was more practical: in hardware, “simple first” can turn into doing everything twice. Another round of parts that don’t arrive, another vendor loop, another build you still have to ship.</p>
<blockquote>
<h3 id="genchi-genbutsu-現地現物"><a href="https://en.wikipedia.org/wiki/Genchi_Genbutsu"><em>Genchi genbutsu</em> (現地現物)</a></h3>
<p>“Go to the real place. See the real thing.”<br />
A principle of the Toyota Production System: understanding should come from direct contact with the actual object and the conditions in which it operates, not from abstractions or proxies.</p>
</blockquote>
<p>I liked the phrase <em>genchi genbutsu</em> because it sounds stricter than how I usually live. Not morally—just practically. Less negotiable. It points at places where being wrong costs something and you don’t get to talk your way out of it.</p>
<p>One of the members who currently works on computer vision told me about his earlier years in manufacturing. He described handling molten metal in the Toyota assembly lines, managing timing and temperature. He didn’t present it as a lesson, but to me it made <em>genchi genbutsu</em> click: learning under conditions where errors show up immediately. Stuff like that changed what I take seriously. It shrinks the range of things you can pretend are fine.</p>
<p><img src="/images/life/gotokuji.png" alt="tokyo night" />
<em>one of the temples i visited during the summer, famous for its cat statues but there were certain views that couldn’t be forgotten like this.</em></p>
<p>I didn’t realize it right away. It showed up on the walk home, passing the same vending machines I’d started using like mile markers.</p>
<p>Tokyo had a way of quieting the background noise for me. When the city stopped asking me to perform, I could hear myself more clearly. I saw how quickly I reach for “nonstandardness.” Some of it is real curiosity. Some of it—if I’m being honest—is cover. A way to stay convincing to myself while calling it ambition.</p>
<p>What bothered me was that the desire hadn’t changed—only the reason underneath it. Sometimes I can’t tell whether I’m pulled by the work itself or by something more embarrassing: fear of being ordinary, hunger for story, imitation that looks like taste from the inside. I don’t know what mix of that I’m made of. I just know that when I’m unsure what matters, I get restless in a way that eventually forces me to move.</p>
<p><em>Genchi genbutsu</em> isn’t only about factories. It’s a rule against living through substitutes. I’m good at staying one step removed; if something stays debatable, I can keep it comfortable. The moments I remember from this summer weren’t like that. They had a kind of bluntness to them. Whatever I told myself afterward didn’t change what happened.</p>
<p>In San Francisco—at least in the crypto rooms I was in—a lot of things moved on vibes. Even the jargon had water in it—liquidity pools, depth—words that made it sound safer than it was. I could feel myself tracking the same signals everyone else was tracking: which VCs were circling, who was tweeting, what the narrative sounded like on Hacker News. It felt like momentum, even before anything had really been materially tested. That’s just what it feels like when attention moves faster than reality.</p>
<p>Japan isn’t just a new aesthetic, but I did learn a stricter definition of real. If I’m serious about creating things that matter, I have to go the place where the thing either functions or it doesn’t. <a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a></p>
<h1 id="liminal-spaces">liminal spaces</h1>
<p><img src="/images/life/walking.jpeg" alt="walking back" />
<em>it just rained, walking back from the imperial gardens</em></p>
<p>This summer has felt liminal in an odd way: not between cities but between forms of belonging.</p>
<p>Nothing shattered. Things only thinned—stretched until the effort of holding them became audible. I talked to people I love less often, not because they mattered less, but because each call now required a small negotiation with time zones and fatigue.</p>
<p><img src="/images/life/friends.png" alt="friends" />
<em>friends from the roppongi hacker house next to my airbnb</em></p>
<p>Meanwhile, Tokyo supplied companionship at arm’s length. Proximity did the work that distance was undoing elsewhere—buoyancy without intimacy. I started marking time by small things: the Takeshita side streets that stayed cool, which vending machines took my card, the same Taco Rico bags collecting by the door <a href="#fn3" class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a>, the train up to Bunkyo to the Uzumaki house—peanut chicken and bok choy, old Ethereum friends talking sequencer design and coordination like it was casual, and me mostly just grateful to be at a table with someone across from me.</p>
<p>I stopped narrating updates to people who wouldn’t be present for the next chapter; without an audience nearby, the urge to sound coherent receded.</p>
<p>In time I noticed a different divide, less about connection and more about whether the future was already scheduled. Staying in touch turned into logistics. Some threads endured on inertia—easy to resume because the next meeting was implied. Others survived only when someone chose to re-enter them, again and again, with no scheduled Google Calendar event to make it automatic <a href="#fn4" class="footnote-ref" id="fnref4" role="doc-noteref"><sup>4</sup></a>. Along the way, ideas I’d carried about identity, success, and ambition slipped loose—not dramatically, but by becoming unnecessary.</p>
<p>I want to do things without guarding every edge. The upside isn’t polish but the kind of <a href="https://fooledbyrandomness.com/ConvexityScience.pdf">asymmetry</a> that Nassim Taleb writes about, where volatility teaches instead of eroding. This summer loosened the gravity of an identity that I’ve worked within for a significant part of my life. Now what remains feels lighter, but also more deliberate.</p>
<p><img src="/images/life/background.jpg" alt="background" />
<em>a sketch from my notes app that i keep as my phone background. the left captures the instability of the present; the right is my reminder to zoom out.</em></p>
<p>I’d been in it so long I stopped noticing the current. In markets, a day can disappear into tiny shifts and still feel like work. With people, it was similar: staying where the temperature was familiar, where the next interaction was implied.</p>
<p>Tokyo didn’t fix anything. It just gave me enough quiet to hear my breathing again.</p>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>There are some incredible stories on <a href="https://www.reddit.com/r/JapanTravelTips/comments/171g19m/i_know_to_avoid_touts_in_kabukicho_but_what_red/">this reddit page</a>.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p>Earlier this term, I visited a local <a href="https://www.ihacitradeshow.com/">HVAC conference</a> and learned about the industry by talking with technicians, engineers, and private-equity investors. From the top down, the language was financial—EBITDA, rollups, multiples. From the bottom up, it was personal: who you trusted, who returned your calls, who you’d let into your house. Entire distributors ran on those relationships. The spreadsheets mattered, but the business held together because people knew each other.<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn3"><p>I kept having to Venmo someone for lunch.<a href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn4"><p>I regularly talk to an older online tech friend from Australia now :)<a href="#fnref4" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>]]></description>
    <pubDate>Fri, 08 Aug 2025 00:00:00 UT</pubDate>
    <guid>https://bhargav.wtf/blog/japan-summer/index.html</guid>
    <dc:creator>Bhargav</dc:creator>
</item>
<item>
    <title>Escardó’s Exhaustive Search: Part 2</title>
    <link>https://bhargav.wtf/blog/escardo-2/index.html</link>
    <description><![CDATA[<h1 id="pulling-the-rabbit-out-of-the-hat">pulling the rabbit out of the hat??</h1>
<p>At the end of the <a href="https://bhargav.wtf/blog/escardo-1/">previous blog</a>, I mentioned that the topological properties of the Cantor space make it searchable via a constructive algorithm. Surprisingly, its not too much of a lift but still feels kinda magical. Not only are we deciding a total predicate without any <em>a priori</em> information other than <strong>continuity and compactness</strong>. Nothing else is known ahead of time: which bit indices will be queried, the number of bits that are required, or any details about the frontier itself. All of this information is <strong>extracted at execution time</strong>.</p>
<p><img src="/images/escardo/rabbit.png" alt="Cylinder Set" />
<em>In a world of AI generated images, enjoy my hand-drawn rendition of a rabbit in a hat<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>. I know it is shit. Sorry.</em></p>
<h1 id="haskell-wizardry">haskell wizardry</h1>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ot">sme ::</span> <span class="dt">Pred</span> <span class="ot">-&gt;</span> <span class="dt">IO</span> [<span class="dt">Prefix</span>]</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>sme p <span class="ot">=</span> go IM.empty <span class="kw">where</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>  go asg <span class="ot">=</span> <span class="kw">case</span> evalP p asg <span class="kw">of</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>    <span class="dt">Right</span> <span class="dt">True</span> <span class="ot">-&gt;</span> <span class="fu">pure</span> [asg]</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    <span class="dt">Right</span> <span class="dt">False</span> <span class="ot">-&gt;</span> <span class="fu">pure</span> []</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>    <span class="dt">Left</span> i <span class="ot">-&gt;</span> <span class="kw">do</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>      f0 <span class="ot">&lt;-</span> go (IM.insert i <span class="dt">False</span> asg)</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>      f1 <span class="ot">&lt;-</span> go (IM.insert i <span class="dt">True</span> asg)</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>      <span class="fu">pure</span> (f0 <span class="op">++</span> f1)</span></code></pre></div>
<p>This performs a determinsitic DFS of the decision tree induced by the predicate <span class="math inline">p: (\mathbb{B}^\mathbb{N} \rightarrow \mathbb{B}) \rightarrow \mathbb{B}</span> via a partial oracle that answers onl ythe bits present int eh current prefix <span class="math inline">\sigma</span> and raises a <code>Need</code> exception when <span class="math inline">p</span> demands an unknown bit index <span class="math inline">i</span> (this is all covered in the previous blog, nothing new here).</p>
<p>In this case, at node <span class="math inline">\sigma</span> we now have three cases:
1. <code>evalP</code> returns <code>True</code> = <span class="math inline">p</span> is already a constant <span class="math inline">1</span> on <span class="math inline">[\sigma]</span> cylinder set so we can record <span class="math inline">\sigma</span> and return
2. <code>evalP</code> returns False, so <span class="math inline">[\sigma] \subseteq U^c</span> so the entire branch can be pruned
3. It raises <code>Need i</code> so we branch to <span class="math inline">\sigma \cup \{i \mapsto 0 \}</span> and <span class="math inline">\sigma \cup \{i  \mapsto 1 \}</span>.</p>
<p>The result of this procedure is <span class="math inline">\cal{F} = \text{SME}(p)</span>, which represents the set of minimal true prefixes (an antichain<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a> fo finite <span class="math inline">\sigma</span> with <span class="math inline">p \equiv 1</span> on <span class="math inline">[\sigma]</span>). Topologically, the preimage <span class="math inline">U = p^{-1}(1)</span> (which represents the set of satsifying assignments) can be expressed as the following disjoint union of cylinders</p>
<p><span class="math display">
    U = \bigcup_{\sigma \in \cal{F}} [\sigma]
</span></p>
<p><img src="/images/escardo/space.png" alt="Geometric Intuition" />
<em>Imagine each prefix <span class="math inline">\sigma</span> as carving out a subcube (cylinder set) of all points that agree with <span class="math inline">\sigma</span>. Then taking the union fills up the region where <span class="math inline">U = p^{-1} (1)</span>. By compactness, there is always a finite subcover but our algorithm refines this so that they are also disjoint.</em></p>
<h1 id="examples">examples</h1>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- fib-eventually (7 cylinders)</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>\x <span class="ot">-&gt;</span> <span class="fu">any</span> (\f <span class="ot">-&gt;</span> f <span class="op">&lt;</span> <span class="dv">30</span> <span class="op">&amp;&amp;</span> x f) (<span class="fu">takeWhile</span> (<span class="op">&lt;</span> <span class="dv">30</span>) fibonacci)</span></code></pre></div>
<p>This checks: “Does the infinite binary sequence x have <code>True</code> at any Fibonacci
position &lt; 30?”</p>
<p>The 7 cylinders represent the minimal decision tree:
- If <code>x[1] = True</code> = predicate is <code>True</code> (fib 1)
- Else if <code>x[2] = True</code> = predicate is <code>True</code> (fib 2)
- Else if <code>x[3] = True</code> = predicate is <code>True</code> (fib 3)
… and so on for positions 5, 8, 13, 21</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- prime-eventually (8 cylinders)</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>\x <span class="ot">-&gt;</span> <span class="fu">any</span> (\p <span class="ot">-&gt;</span> p <span class="op">&lt;</span> <span class="dv">20</span> <span class="op">&amp;&amp;</span> x p) (<span class="fu">takeWhile</span> (<span class="op">&lt;</span> <span class="dv">20</span>) primes)</span></code></pre></div>
<p>This computes primes on-demand from <code>primes = filter isPrime [2..]</code> (infinite list), then checks if x is <code>True</code> at any prime position <code>&lt; 20</code>. The 8 cylinders correspond to checking positions 2, 3, 5, 7, 11, 13, 17, 19 in
sequence.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- collatz-reaches-1 (942 cylinders!)</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>\x <span class="ot">-&gt;</span> <span class="kw">let</span> collatz n <span class="ot">=</span> <span class="kw">if</span> <span class="fu">even</span> n <span class="kw">then</span> n <span class="ot">`div`</span> <span class="dv">2</span> <span class="kw">else</span> <span class="dv">3</span><span class="op">*</span>n<span class="op">+</span><span class="dv">1</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>        orbit n <span class="ot">=</span> <span class="fu">takeWhile</span> (<span class="op">/=</span> <span class="dv">1</span>) (<span class="fu">iterate</span> collatz n)</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>    <span class="kw">in</span> <span class="fu">length</span> (orbit (<span class="fu">sum</span> [i <span class="op">|</span> i <span class="ot">&lt;-</span> [<span class="dv">0</span><span class="op">..</span><span class="dv">9</span>], x i] <span class="op">+</span> <span class="dv">1</span>)) <span class="op">&lt;</span> <span class="dv">50</span></span></code></pre></div>
<p>Again expanding this gives us:
1. Takes first 10 bits of x, sums the indices where <code>x[i] = True</code>, adds 1
2. Computes the Collatz orbit of that number (<span class="math inline">3n+1</span> conjecture)
3. Checks if the orbit reaches 1 in &lt; 50 steps</p>
<p>Certain predicates over <strong>infinite sequences can be decided by examining only finite prefixes</strong>, and algorithms like <code>sme</code> can discover these finite characterizations. I wonder if there is something interesting in representing this as finite automata. In the next blog, we’ll go into some implications of introducing Randomness.</p>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>I came across <a href="https://readingfeynman.org/tag/schrodinger-equation/">this blog</a> on Schrodinger’s equation when trying to come up with the intro, it seems pretty interesting.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p>Each finite prefix <span class="math inline">\sigma: F \rightarrow \mathbb{B}</span> (where <span class="math inline">F</span> is the local <a href="https://en.wikipedia.org/wiki/Modulus_of_continuity">modulus of continuity</a>[^3]) defines a cylinder set <span class="math inline">[\sigma] = \{x \in \mathbb{B}^\mathbb{N} | x|_F = \sigma\}</span>. If a new finite prefix <span class="math inline">\tau</span> extends <span class="math inline">\sigma</span>, then <span class="math inline">[\tau] \subseteq [\sigma]</span>. A small exercise is to prove that there si no way for two distinct prefixes two be returned. Consequently, <span class="math inline">\{ \sigma \}</span> defiens an antichain under this extension order. Algorithmically, this is enforced by <strong>minimality and early stopping</strong>.<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>]]></description>
    <pubDate>Sat, 26 Jul 2025 00:00:00 UT</pubDate>
    <guid>https://bhargav.wtf/blog/escardo-2/index.html</guid>
    <dc:creator>Bhargav</dc:creator>
</item>
<item>
    <title>Escardó’s Exhaustive Search: Part 1</title>
    <link>https://bhargav.wtf/blog/escardo-1/index.html</link>
    <description><![CDATA[<h1 id="introduction">Introduction</h1>
<p>I’ve recently read <a href="https://math.andrej.com/2007/09/28/seemingly-impossible-functional-programs/">this</a> old blog post by Andrej Bauer about Martin Escardó’s <a href="https://martinescardo.github.io/papers/exhaustive.pdf"><em>Infinite sets that admit fast exhaustive search</em></a>. At first, it seems pretty ridiculous! How is it possible to decide a problem that is embedded in an infinite topological space? However, using some nice tricks from functional programming and higher-level computability, we can achieve this and even explore some other unexpected consequences.</p>
<p>Any finite set is immediately exhaustible<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>, but the interesting thing is that certain infinite sequences with specific properties can also be exhaustible.</p>
<h2 id="notation">Notation</h2>
<p>The simple types are <span class="math inline">\sigma, \tau := o | \iota | \sigma \times \tau | \rightarrow \tau</span>. Let’s dig into this a bit more.
- <span class="math inline">o</span> the Booleans which in Haskell are <code>Bool = True | False</code>
- <span class="math inline">\iota</span> (natural numbers), written as <code>Int</code>.
- Product type <span class="math inline">\sigma \times \tau</span> is the Cartesian product, which is <code>IntMap a</code> <span class="math inline">: \tau \times</span> <code>(Maybe a)</code>
- Function type <span class="math inline">\sigma \rightarrow \tau</span>
- <span class="math inline">\iota \rightarrow o</span> = predicates on the naturals
- <span class="math inline">o \rightarrow o</span> = boolean functions <span class="math inline">\neg, \land, \lor</span>, etc.</p>
<p>Using these primitives, we can construct increasingly more complex types as:</p>
<p>Base case (level 0):
- <span class="math inline">o</span> = booleans
- <span class="math inline">\iota</span> = natural numbers</p>
<p>Level 1:
- <span class="math inline">o \times o</span> = pair of booleans <code>(True, False)</code>
- <span class="math inline">\iota \times \iota</span> = pair of naturals <code>(3, 7)</code>
- <span class="math inline">o \rightarrow o</span> = boolean functions <code>{NOT, AND True, OR False, id, ...}</code>
- <span class="math inline">\iota \rightarrow o</span> = predicates on naturals <code>{isEven, isPrime, λn.n&gt;5}</code>
- <span class="math inline">\iota \rightarrow \iota</span> = arithmetic functions, <code>{successor, λn.2*n, λn.n²}</code></p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="dt">Oracle</span> a <span class="ot">=</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> a        <span class="co">-- ι → a (where a could be o, ι, etc.)</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="dt">Pred</span> a <span class="ot">=</span> <span class="dt">Oracle</span> a <span class="ot">-&gt;</span> <span class="dt">Bool</span>  <span class="co">-- (ι → a) → o  </span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="dt">Prefix</span> <span class="ot">=</span> <span class="dt">IntMap</span> <span class="dt">Bool</span>        <span class="co">-- finite partial assignments  </span></span></code></pre></div>
<ul>
<li>The domain is a Cantor space <span class="math inline">\mathbb{B}^\mathbb{N}</span> (all boolean streams)</li>
<li>A predicate <code>p</code> is a higher-type functional <span class="math inline">p: (\mathbb{B}^\mathbb{N}) \rightarrow \mathbb{B}</span></li>
<li>a prefix <code>asg</code> is a finite partial map <span class="math inline">\alpha : S \rightarrow \mathbb{B}</span> which is used to define the <a href="https://en.wikipedia.org/wiki/Cylinder_set">cylinder sets</a> used to define the product topology on the Cantor space.
### Scott Domains
For each type <span class="math inline">\sigma</span>, there is a <strong>Scott domain</strong> <span class="math inline">D_\sigma</span> of partial functionals of the that same type <span class="math inline">\sigma</span> defined by lifting the type <span class="math inline">\sigma</span> to contain the <code>undefined</code> type with all of the properties you would expect (e.g. ordering, etc.). In Haskell, this looks like an <code>Oracle a</code> type with potential <code>Need</code> exceptions. This lets us capture the behavior of undefined/non-terminating computation, which we use to create <em>partial</em> oracles.</li>
</ul>
<h3 id="total-functionals-t_sigma-subseteq-d_sigma">Total functionals <span class="math inline">T_\sigma \subseteq D_\sigma</span></h3>
<p>A functional is <strong>total</strong> if it maps total inputs to total outputs (so it never produces a <code>Nothing</code> type from non-<code>Nothing</code>)</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ot">evalP ::</span> <span class="dt">Pred</span> a <span class="ot">-&gt;</span> <span class="dt">Prefix</span> a <span class="ot">-&gt;</span> <span class="dt">Either</span> <span class="dt">Int</span> <span class="dt">Bool</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>evalP p asg <span class="ot">=</span> <span class="kw">case</span> unsafePerformIO (try (evaluate (p (oracle <span class="fu">undefined</span> asg)))) <span class="kw">of</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Left</span> (<span class="dt">Need</span> i) <span class="ot">-&gt;</span> <span class="dt">Left</span> i    <span class="co">-- Partial: needs position i</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Right</span> b <span class="ot">-&gt;</span> <span class="dt">Right</span> b         <span class="co">-- Total on this prefix</span></span></code></pre></div>
<p>So then we can evaluate <span class="math inline">P(f^\perp)</span> where <span class="math inline">f^\perp</span> is a partial oracle depending on what the domain is.</p>
<h1 id="constructive-selection-functional-for-cantor-space">Constructive Selection Functional for Cantor Space</h1>
<p>Now consider the following code:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ot">escardo ::</span> <span class="dt">Pred</span> <span class="ot">-&gt;</span> <span class="dt">Maybe</span> <span class="dt">Oracle</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>escardo p <span class="ot">=</span> <span class="fu">fmap</span> extend (go IM.empty) <span class="kw">where</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>  extend asg i <span class="ot">=</span> IM.findWithDefault <span class="dt">False</span> i asg</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>  go asg <span class="ot">=</span> <span class="kw">case</span> evalP p asg <span class="kw">of</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>    <span class="dt">Right</span> <span class="dt">True</span>  <span class="ot">-&gt;</span> <span class="dt">Just</span> asg</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>    <span class="dt">Right</span> <span class="dt">False</span> <span class="ot">-&gt;</span> <span class="dt">Nothing</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>    <span class="dt">Left</span> i <span class="ot">-&gt;</span> <span class="kw">case</span> go (IM.insert i <span class="dt">False</span> asg) <span class="kw">of</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>      <span class="dt">Just</span> a  <span class="ot">-&gt;</span> <span class="dt">Just</span> a</span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a>      <span class="dt">Nothing</span> <span class="ot">-&gt;</span> go (IM.insert i <span class="dt">True</span> asg)</span></code></pre></div>
<p>We maintain a finite assignment <code>asg</code> that fixes the values of a few coordinates so far. <code>evalP</code> determines whether this cylinder already forces the truth value of <code>p</code>. If <code>evalP p asg</code> returns <code>Right True</code>, then <span class="math inline">p</span> is already true for every infinite bitstream extending <code>asg</code>. Then we can just stop and produce a total oracle by calling <code>extend</code>, which fills every unspecified bit (at this current point) with a default value (<code>False</code>).</p>
<p><strong><em>But what does this mean?</em></strong> Think of the Cantor space as the set of all infinite binary sequences</p>
<p><span class="math display">
    000000000... \\
    001010110... \\
    010101010... \\
    111111111... \\
</span></p>
<p>From Wikipedia,
&gt; Given a collection <span class="math inline">S</span> of sets, consider the Cartesian product <span class="math inline">X = \Pi_{Y \in S} Y</span> of all sets in the collection. The <strong>canonical projection</strong> corresponding to some <span class="math inline">Y \in S</span> is the function <span class="math inline">p_Y : X \rightarrow Y</span> that maps every element of the product to its <span class="math inline">Y</span> component. <strong>A cylinder set is a preimage of a canonical projection</strong> or finite intersection of such preimages. Explicitly, we can write it as:
<span class="math display">
\bigcap_{i = 1}^n p_{Y_i}^{-1} (A_i) = \{(x) \in X | p_{Y_1} \in A_1, \cdots, p_{Y_n} (x) \in A_n \}
</span></p>
<p>So for the Cantor space, a cylinder <span class="math inline">[\alpha]</span> consists of the set of all infinite strings that start with a finite prefix <span class="math inline">\alpha</span>. For example,
- <code>[01]</code> = all strings that start with “01”:
<span class="math display">
01000000... \\
01001010... \\
01010101... \\
01111111... \\
</span>
- <code>[101]</code> = all strings starting with “101”:
<span class="math display">
10100000... \\
10101010... \\
10111111... \\
</span></p>
<p>We can illustrate this as a tree:</p>
<p><img src="/images/escardo/cylinder_set.png" alt="Cylinder Set" />
<em>The cylinder set corresponds to the subtree circled in red in the case of the Cantor space.</em></p>
<p>So then, the search process starts at the root and recursively tries to decide <span class="math inline">p</span> on the current space (starting with the entire Cantor space). If we need bit <span class="math inline">i</span>, then split the current cylinder into two subcylinders. If a cylinder returns <code>Right False</code>, we abandon the subtree and if a cylinder returns <code>Right True</code>, we short-circuit and accept.</p>
<p>The incredible thing is that predicate <span class="math inline">p</span> in this case can be incredibly general. The simplest form to imagine are SAT-style boolean combinations, but they can be <strong>any continuous function decidable with finite information</strong>. For instance, all of the following are compatible with this framework and are decidable efficiently:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- Arithmetic predicate</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="ot">sumFirst10 ::</span> <span class="dt">Pred</span> <span class="dt">Int</span>  </span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>sumFirst10 oracle <span class="ot">=</span> <span class="fu">sum</span> [oracle i <span class="op">|</span> i <span class="ot">&lt;-</span> [<span class="dv">0</span><span class="op">..</span><span class="dv">9</span>]] <span class="op">&gt;</span> <span class="dv">50</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- Pattern matching</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a><span class="ot">hasPattern ::</span> <span class="dt">Pred</span> <span class="dt">Bool</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>hasPattern oracle <span class="ot">=</span> <span class="fu">any</span> (\i <span class="ot">-&gt;</span> oracle i <span class="op">&amp;&amp;</span> oracle (i<span class="op">+</span><span class="dv">1</span>) <span class="op">&amp;&amp;</span> <span class="fu">not</span> (oracle (i<span class="op">+</span><span class="dv">2</span>))) [<span class="dv">0</span><span class="op">..</span><span class="dv">97</span>]</span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="co">-- Convergence predicate  </span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a><span class="ot">converges ::</span> <span class="dt">Pred</span> <span class="dt">Double</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a>converges oracle <span class="ot">=</span> <span class="fu">abs</span> (oracle <span class="dv">100</span> <span class="op">-</span> oracle <span class="dv">99</span>) <span class="op">&lt;</span> <span class="fl">0.001</span></span></code></pre></div>
<p>and these aren’t easily expressible using an SAT-style alphabet. The key constraint is <em>continuity</em>, not logical structure.</p>
<h1 id="for-the-sake-of-mathematical-rigor">for the sake of mathematical rigor</h1>
<p>Recall that formally, the predicate is defined as <span class="math inline">p: \mathbb{B}^\mathbb{N} \rightarrow \mathbb{B}</span> a continuous map on the Cantor space, which itself is a countable product space. The basic open sets in this space are the aforementioned cylinders <span class="math inline">[\alpha]</span>, which are infinite bitstreams that extend a finite assignment <span class="math inline">\alpha : S \rightarrow \mathbb{B}</span>. By the (Kleene-Kreisel) continuity of <span class="math inline">p</span>, there exists some cylinder <span class="math inline">[\alpha] \ni x</span> for each <span class="math inline">x</span> on which <span class="math inline">p</span> is already constant and hence a finite amount of information about the input fixes the output. Because the Cantor space is compact and totally disconnected, the preimages <span class="math inline">p^{-1} (1)</span> and <span class="math inline">p^{-1} (0)</span> are <em>clopen</em>, so each can be written as a finite union of cylinders (this is a pretty standard trick). Refining these finitely many cylinders to a common depth yields a uniform modulus<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a> (via Heine-Cantor) <span class="math inline">N</span> such that for each <span class="math inline">x</span>, <span class="math inline">p(x)</span> is determined by some finite set of at most <span class="math inline">N</span> bits.</p>
<p>The <code>evalP</code> function implements this. Given a finite assignment <span class="math inline">alpha</span> (the <code>IntMap</code>), we run <span class="math inline">p</span> against the partial oracle that answers exactly the bits in <span class="math inline">\alpha</span> and throws <code>Need</code> when an unassigned bit is requested. This is exactly the “dialogue” that Escardó describes in his paper: <strong>continuous higher-type functionals consuming only finite information.</strong>.</p>
<p>So really, this is just an incredibly complex guided depth-first search over the binary decision tree of finite assignments that branches only when <span class="math inline">p</span> asks for a new bit with some short-circuiting logic. Termination relies on compactness via the uniform modulus <span class="math inline">N</span>. <code>Left i</code> can appear only when <code>i</code> is new (once a bit is assigned, accessing it later doesn’t throw a <code>Need</code>). Thus, no successful branch can be longer than <span class="math inline">N</span>, so after at most <span class="math inline">N</span> distinct queries continuity forces a <code>Right</code> answer. If <code>p</code> is everywhere false, the algorithm will explore all finitely many branches up to depth <span class="math inline">N</span> and fail and otherwise terminate early. Thus, we constructively determine that the Cantor space is searchable<a href="#fn3" class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a>. Another nice property of this is that the order that we sample branches doesn’t affect correctness, only runtime (so there are many engineering optimizations to be done…maybe Gray codes?).</p>
<blockquote>
<p>The TL;DR is that:
- Continuity gives you cylinder-faithfulness
- Compactness gives you a uniform finite modulus
- Dialogue gives you an on-demand DFS that decides the predicate over finite information</p>
</blockquote>
<h1 id="why-just-the-cantor-space">why just the cantor space?</h1>
<p><img src="/images/escardo/padic.png" alt="p-adic number" />
<em>Good ol’ Wikipedia image. Unfortunately took me a few months to fully wrap my head around them.</em></p>
<p>The <span class="math inline">p</span>-adic integers<a href="#fn4" class="footnote-ref" id="fnref4" role="doc-noteref"><sup>4</sup></a> <span class="math inline">\mathbb{Z}_p</span> can similarly be seen as an infinite stream of digits in the base <span class="math inline">p</span> much like the Cantor space (as shown above).</p>
<p><span class="math display">
    x = a_0 + a_1p + a_2 p^2 + \cdots, a_i \in \{ 0, 1, \cdots, p - 1 \}
</span></p>
<p>Really, the only difference is that now the alphabet size is now <span class="math inline">p</span> instead of restricted to <span class="math inline">2</span>. The Cantor space is kind of an artificial playground because conceptually it is pretty simple to understand. However, the <span class="math inline">p</span>-adics are widely applicable across number theory (<a href="https://en.wikipedia.org/wiki/Hensel%27s_lemma">Hensel’s lemma always surprises me</a>) and more. We can easily compute the answer for the question “does there exist a <span class="math inline">p</span>-adic number <span class="math inline">\geq k</span> that satisfies <span class="math inline">C</span> condition?” and construct a witness for it. At some point, i’ll implement a root finding constructive algorithm…maybe pure Haskell WolframAlpha??</p>
<p>There is also an extension of this that introduces <a href="https://softwareengineering.stackexchange.com/questions/202908/how-do-functional-languages-handle-random-numbers/202915#202915">Randomness</a> and in turn has some interesting measure-theoretic and algorithmic properties..</p>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>A set <span class="math inline">K</span> is <em>exhaustible</em> if for any decidable predicate <span class="math inline">p</span>, there is a deterministic algorithm to determine whether all elements of <span class="math inline">K</span> satisfy <span class="math inline">p</span>. Formally, we can write that for a functional type <span class="math inline">(C \rightarrow \mathbb{B}) \rightarrow \mathbb{B}</span> with <span class="math inline">K \subseteq C</span>. The input is a predicate <span class="math inline">p: C \rightarrow \mathbb{B}</span> and the output is <span class="math inline">p(x)</span> holds for all <span class="math inline">x \in K</span>.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p>From Wikipedia: a modulus of continuity is a function <span class="math inline">\omega: [0, \infty] \rightarrow [0, \infty]</span> used to measure the uniform continuity of functions. So we can write <span class="math inline">|f(x) - f(y)| \leq \omega(|x - y|)</span><a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn3"><p>A set <span class="math inline">K</span> is <em>searchable</em> if there is a computable functional <span class="math inline">\epsilon_K : (D \rightarrow B) \rightarrow D</span> such that for every <span class="math inline">p</span> defined on <span class="math inline">K</span>, <span class="math inline">\epsilon_k (p) \in K</span> and $p(x) = $ <code>True</code> for some <span class="math inline">x \in K</span> implies that $p(_K (p)) = $ <code>True</code>. Thus, searchability <span class="math inline">\implies</span> exhaustibility<a href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn4"><p>Distance is measured as <span class="math inline">|x|</span> over the normal number line but in the <span class="math inline">p</span>-adic form, <span class="math inline">|x|_p = p^{-k}</span> and <span class="math inline">x = p^k \cdot \frac{a}{b}</span>. <span class="math inline">p</span>-adics also have cylinder sets (which are now residue classes <span class="math inline">\mod p^k</span>) and form the sasme tree-like structure where depth 1 represents mod 3 classes, depth 2 is mod 9 classes, etc. for <span class="math inline">p = 3</span>.<a href="#fnref4" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>]]></description>
    <pubDate>Fri, 25 Jul 2025 00:00:00 UT</pubDate>
    <guid>https://bhargav.wtf/blog/escardo-1/index.html</guid>
    <dc:creator>Bhargav</dc:creator>
</item>
<item>
    <title>Private Money: Part 3</title>
    <link>https://bhargav.wtf/blog/zcash-3/index.html</link>
    <description><![CDATA[<h1 id="background">Background</h1>
<div style="background-color: #f8f9fa; padding: 1rem; border-radius: 8px; margin: 1rem 0;">
  <div style="display: flex; justify-content: center;">
    <img src="/images/zcash/double_spending.png" alt="Double Spending" style="width: 50%; height: auto;" />
  </div>
</div>
<p><strong>Double-spending</strong> is when someone tries to use the same funds more than once. It’s a fundamental problem in finance that appears in various guises. In traditional banking, this looks like <strong>check kiting</strong>—manipulating the float time between accounts to cover overdrafts. A notorious example is <a href="https://apnews.com/article/bank-fraud-classic-cars-keybank-elkhart-d0c9a4a2a66fb88a832a613a8560c49c">Najeeb Khan’s $180M fraud</a>, where he exploited bank timing windows to fund a lavish lifestyle at the expense of clients.</p>
<p>In crypto, Ethereum Classic suffered <a href="https://www.coinbase.com/blog/coinbases-perspective-on-the-recent-ethereum-classic-etc-double-spend">multiple 51% attacks in 2020</a> where attackers rewrote transaction history and double-spent over $9M. This showed that public blockchains without secure consensus can be vulnerable too.</p>
<p>Zcash presents a more complex challenge: its shielded transactions reveal nothing about sender, receiver, or value. To prevent double-spending while preserving privacy, Zcash enforces two cryptographic constraints:
1. <strong>Private inclusion proof</strong>: The note<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a> must be in the note-commitment Merkle tree.
2. <strong>Public non-inclusion proof</strong>: The note’s <strong>nullifier</strong> must not be in the nullifier set.</p>
<h2 id="why-zcash-uses-a-public-non-inclusion-check">Why Zcash uses a public non-inclusion check</h2>
<div style="background-color: #f8f9fa; padding: 1rem; border-radius: 8px; margin: 1rem 0;">
  <div style="display: flex; justify-content: center;">
    <img src="/images/zcash/trees.png" alt="Image of Note-Commitment Tree and Noninclusion Set" style="width: 50%; height: auto;" />
  </div>
</div>
<p>When a new shielded note is created, only its <strong>commitment</strong> is revealed. This is appended to the global note-commitment Merkle tree, while the note’s actual value and recipient stay private. Because the tree is append-only and doesn’t track spending, a second tag—the <strong>nullifier</strong>—ensures each note is spent at most once.</p>
<p>A <strong>nullifier</strong> is a deterministic, unlinkable fingerprint of a note. It’s computed using a <a href="https://crypto.stanford.edu/pbc/notes/crypto/prf.html">pseudorandom function</a> (PRF) keyed by the note’s secret. Only the owner can derive the nullifier, and each note has exactly one<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a>.</p>
<blockquote>
<p><em>“A transaction is not valid if it would have added a nullifier to the nullifier set that already exists in the set.”</em></p>
</blockquote>
<p>The nullifier is publicly revealed and checked against the nullifier set, which is updated each block along with the Merkle tree. This allows the network to reject double-spends while learning nothing about the note itself.</p>
<h2 id="private-membership-proof">Private membership proof</h2>
<p>Zcash uses <a href="https://z.cash/learn/what-are-zk-snarks/">zk-SNARKs</a> to enforce both constraints—commitment inclusion and nullifier consistency—without revealing which note is spent. Each proof is ~1–2 kB, verifiable in milliseconds, and works even for light clients<a href="#fn3" class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a>.</p>
<h3 id="the-private-link-created-inside-the-zk-snark">1. The private link created inside the zk-SNARK</h3>
<p><strong>Merkle inclusion</strong>:<br />
The prover supplies the note commitment <span class="math inline">cm</span><a href="#fn4" class="footnote-ref" id="fnref4" role="doc-noteref"><sup>4</sup></a> and a Merkle authentication path <span class="math inline">\pi</span> as private witness data. The circuit enforces:
<span class="math display">
\text{MerkleRoot}(cm, \pi) = \rho_t
</span>
where <span class="math inline">\rho_t</span> is the <strong>anchor</strong>, a public input representing the Merkle root at block height <span class="math inline">t</span> <a href="#fn5" class="footnote-ref" id="fnref5" role="doc-noteref"><sup>5</sup></a>. This confirms that <span class="math inline">cm</span> appears somewhere in the historical note-commitment tree.</p>
<p><strong>Nullifier computation</strong>:<br />
In the same circuit, the prover recomputes the nullifier using:
<span class="math display">
nf = \text{PRF}_{nk}(\rho_t, \psi, cm)
</span>
where <span class="math inline">(nk, \psi)</span> are secrets derived from the note. The output nullifier <span class="math inline">nf</span> is made public and bound to the same anchor and commitment.</p>
<p>This internal linkage ensures that the SNARK proves consistency between the revealed nullifier and the private note it was derived from—without revealing anything about the note itself.</p>
<h3 id="how-the-on-chain-check-works">2. How the on-chain check works</h3>
<p>Each transaction includes the anchor <span class="math inline">\rho_t</span> and the nullifier <span class="math inline">nf</span> in the public input of the proof. Once the SNARK verifies, every full node checks:
- That the nullifier <span class="math inline">nf</span> is <strong>not already in</strong> the nullifier set <span class="math inline">N(t)</span>.
- If the check passes, the block updates:
<span class="math display">
  N(t+1) = N(t) \cup \{nf\}
  </span></p>
<p>This ensures that every nullifier appears at most once—enforcing one-time spendability.</p>
<h3 id="why-the-two-links-suffice">3. Why the two links suffice</h3>
<ul>
<li><strong>Existence</strong>: The Merkle equation certifies that a real commitment <span class="math inline">cm</span> already sits in the tree whose root is <span class="math inline">\rho_t</span>.</li>
<li><strong>Uniqueness</strong>: The PRF binds a single nullifier <span class="math inline">nf</span> to that <span class="math inline">cm</span>, and consensus allows each <span class="math inline">nf</span> to appear only once.</li>
</ul>
<p>Hence, any valid transaction must:
- Reference some existing note (via inclusion of <span class="math inline">cm</span>),
- And cannot reuse that note (since its <span class="math inline">nf</span> would already be in <span class="math inline">N</span>).</p>
<p>The inclusion proof and the non-inclusion check are <strong>mathematically fixed</strong> through the shared variables <span class="math inline">(cm, \rho_t, nf)</span>—inside the SNARK and on-chain.</p>
<h1 id="limitations-of-merkle-trees">Limitations of Merkle Trees</h1>
<p>Incremental Merkle trees<a href="#fn6" class="footnote-ref" id="fnref6" role="doc-noteref"><sup>6</sup></a> are the classic way Zcash records shielded notes<a href="#fn7" class="footnote-ref" id="fnref7" role="doc-noteref"><sup>7</sup></a>. They have a fixed depth <span class="math inline">d</span>, so the ledger can accept at most <span class="math inline">2^d</span> commitments before a migration is needed. Every new note becomes a fresh leaf, and the tree’s collision-resistant hashing lets a prover later show inclusion with a <span class="math inline">d</span>-hash path. That path is constant-size and efficient, but the tree’s <em>state grows forever</em><a href="#fn8" class="footnote-ref" id="fnref8" role="doc-noteref"><sup>8</sup></a>.</p>
<h3 id="sharding-helps-but-doesnt-solve-the-problem">Sharding helps, but doesn’t solve the problem</h3>
<p>A natural next step is to <strong>shard</strong> the Merkle tree. Instead of one monolith, the ledger maintains many sub-trees (e.g., <span class="math inline">2^{32}</span>-leaf trees). When a shard fills, a new one is opened, and a small <strong>root-of-roots tree</strong> tracks the current shard roots. A note’s inclusion path now includes:
- A short <em>intra-shard</em> Merkle path
- One additional hash to reach the root-of-roots</p>
<p>This design keeps proof sizes small and <strong>reduces wallet update overhead</strong>: clients only need to track changes in shards that contain their notes <a href="#fn9" class="footnote-ref" id="fnref9" role="doc-noteref"><sup>9</sup></a>.</p>
<p>But even this is a <strong>temporary fix</strong>. The root-of-roots is itself incremental and will eventually fill. Historical paths must still be updated as long as the shard is active. State pruning remains difficult because <strong>every note ever minted must remain accessible</strong>. As a result, ledger size and wallet sync bandwidth <strong>grow linearly with protocol lifetime</strong>.</p>
<h3 id="why-a-set-non-inclusion-accumulator-changes-everything">Why a set non-inclusion accumulator changes everything</h3>
<p>A <strong>set non-inclusion accumulator</strong> offers a simpler approach to tracking spent notes.</p>
<p>While incremental Merkle trees work perfectly well for inclusion proofs, they cannot efficiently support non-inclusion proofs. The accumulator solves this by folding all notes into a <strong>constant-size accumulator value</strong> <span class="math inline">A_t</span> that can handle non-inclusion proofs (and can be easily extended to support inclusion proofs as well). Every insertion is a <strong>succinct polynomial-commitment update</strong>, and old accumulator states can be discarded—because an IVC (incremental verifiable computation) chain certifies correctness across updates.</p>
<p>The key advantage is simplicity: while Merkle trees require separate handling for commitments and nullifiers, the accumulator provides a single cryptographic primitive that handles non-inclusion proofs. This means we can use the same system for the nullifier set, and extend it to handle note commitments if needed.</p>
<p>The magic lies in the accumulator’s recursive structure: each update witnesses that a <em>vector</em> of notes was inserted without including a particular element <span class="math inline">x</span>. The non-membership claim is upheld step-by-step, proving that each polynomial inserted lacked <span class="math inline">x</span> as a root—meaning <span class="math inline">x</span> was not present. This transforms the problem into one of recursive algebra, not storage.</p>
<p>At the end, proving <strong>non-inclusion</strong> (“this nullifier was never inserted up to <span class="math inline">A_t</span>”) requires only checking that a single polynomial (the one folded into the accumulator) <strong>does</strong> have <span class="math inline">x</span> as a root.</p>
<ul>
<li>The accumulator’s size <strong>never grows</strong>, no matter how many notes are inserted</li>
<li>There’s <strong>no depth cap</strong> or leaf index to exhaust</li>
<li>Each IVC step is just a few hashes and group ops—efficient even onchain</li>
<li>You don’t need to track historical state prior to the last <span class="math inline">k</span> epochs—as long as all proofs spanning that range have been generated, the earlier accumulator data can be safely discarded</li>
</ul>
<p>In short: <strong>shielded anonymity can grow indefinitely</strong>, with no migrations or tree maintenance. This is the foundation for <strong>Project Tachyon’s accumulator design</strong>—a simpler system that handles non-inclusion proofs efficiently.</p>
<hr />
<h1 id="implementation">Implementation</h1>
<p>The accumulator starts in a trivial state <span class="math inline">A_0 = \langle(1,0,0,\ldots), G\rangle</span> and is updated every time we insert a <strong>vector of field elements</strong>
<span class="math inline">\mathbf a_i = (a_{i,1},\dots,a_{i,k}) \subset \mathbb F</span>.
After many updates we want to show, for <em>every</em> step in a chosen range <span class="math inline">[j,\,m)</span>, that the inserted vector <strong>never contained</strong> some element <span class="math inline">v</span>.
Instead of storing all past vectors, we fold them into a <strong>single curve-point accumulator</strong> whose size never grows.</p>
<p>The magic is that while we track more and more nullifiers internally—building a bigger polynomial each time—we only store a <strong>single point</strong> on-chain. This point acts as a cryptographic summary of all nullifiers we’ve seen, like a Merkle root but without the tree. This means the blockchain state stays tiny, even as we process millions of transactions.</p>
<h3 id="insertion-folding-one-vector-of-roots">1. Insertion: folding one vector of roots</h3>
<p>For the current step we first build its <em>vanishing polynomial</em></p>
<p><span class="math display">
a_i(X)=\prod_{r\in\mathbf a_i}(X-r),
</span></p>
<p>then make a Pedersen commitment <span class="math inline">P_i</span> to the coefficient vector.
One Fiat–Shamir challenge <span class="math inline">h=H(A_i,P_i)</span> lets us hop to the next state</p>
<p><span class="math display">
A_{i+1}=[h]\,A_i + P_i .
</span></p>
<div class="sourceCode" id="cb1"><pre class="sourceCode rust"><code class="sourceCode rust"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">pub</span> <span class="kw">fn</span> insert(roots<span class="op">:</span> <span class="op">&amp;</span>[Fr]<span class="op">,</span> a_prev<span class="op">:</span> G1Affine<span class="op">,</span> r<span class="op">:</span> Fr) <span class="op">-&gt;</span> <span class="dt">Result</span><span class="op">&lt;</span>State<span class="op">&gt;</span> <span class="op">{</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> poly   <span class="op">=</span> poly_from_roots(roots)<span class="op">;</span>           <span class="co">//  a_i(X)</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> p_i    <span class="op">=</span> commit(<span class="op">&amp;</span>poly<span class="op">.</span>coeffs<span class="op">,</span> r)<span class="op">?;</span>         <span class="co">//  P_i</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> h      <span class="op">=</span> hash_points_to_fr(<span class="op">&amp;</span>a_prev<span class="op">,</span> <span class="op">&amp;</span>p_i)<span class="op">;</span> <span class="co">//  H(A_i,P_i)</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> next   <span class="op">=</span> a_prev <span class="op">*</span> h <span class="op">+</span> p_i<span class="op">;</span>                 <span class="co">//  A_{i+1}</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>    <span class="cn">Ok</span>(State <span class="op">{</span> Accumulator<span class="op">:</span> next<span class="op">.</span>into_affine()<span class="op">,</span> Commitment<span class="op">:</span> p_i <span class="op">}</span>)</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p><em>Key point:</em> no matter how many vectors we add, <code>Accumulator</code> stays a <em>single</em> point.</p>
<h3 id="proving-that-a-fresh-value-v-was-not-among-todays-roots">2. Proving that a fresh value <span class="math inline">v</span> was <strong>not</strong> among today’s roots</h3>
<p>The verifier will accept only if the polynomial we committed <strong>doesn’t vanish</strong> at <span class="math inline">v</span>.</p>
<ol type="1">
<li><p>Evaluate once: <span class="math inline">\alpha = a_i(v)</span>.
If <span class="math inline">\alpha=0</span> the proof must abort (v was a root).</p></li>
<li><p>Shift the commitment so the <strong>shifted polynomial <em>does</em> vanish at <span class="math inline">v</span></strong>:
<span class="math inline">P&#39;_i = P_i - [\alpha]G_0</span></p></li>
<li><p>Use a second challenge <span class="math inline">h&#39; = H(S_i, P&#39;_i)</span> to hop the non-membership accumulator.</p></li>
</ol>
<div class="sourceCode" id="cb2"><pre class="sourceCode rust"><code class="sourceCode rust"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">pub</span> <span class="kw">fn</span> check_non_membership(</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>        roots<span class="op">:</span> <span class="op">&amp;</span>[Fr]<span class="op">,</span> v<span class="op">:</span> Fr<span class="op">,</span> r<span class="op">:</span> Fr<span class="op">,</span> s_prev<span class="op">:</span> G1Affine) <span class="op">-&gt;</span> <span class="dt">Result</span><span class="op">&lt;</span>State<span class="op">&gt;</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> poly   <span class="op">=</span> poly_from_roots(roots)<span class="op">;</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> alpha  <span class="op">=</span> evaluate_poly(<span class="op">&amp;</span>poly<span class="op">.</span>coeffs<span class="op">,</span> v)<span class="op">;</span>     <span class="co">// α = a_i(v)</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>    <span class="pp">assert!</span>(<span class="op">!</span>alpha<span class="op">.</span>is_zero())<span class="op">;</span>                       <span class="co">// must be non-root</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> p_i    <span class="op">=</span> commit(<span class="op">&amp;</span>poly<span class="op">.</span>coeffs<span class="op">,</span> r)<span class="op">?;</span>           <span class="co">// P_i</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> p_ip   <span class="op">=</span> p_i <span class="op">-</span> POINTS[<span class="dv">0</span>] <span class="op">*</span> alpha<span class="op">;</span>            <span class="co">// P&#39;_i</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> h_p    <span class="op">=</span> hash_points_to_fr(<span class="op">&amp;</span>s_prev<span class="op">,</span> <span class="op">&amp;</span>p_ip)<span class="op">;</span>  <span class="co">// h′ = H(S_i,P′_i)</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> next   <span class="op">=</span> s_prev <span class="op">*</span> h_p <span class="op">+</span> p_ip<span class="op">;</span>                <span class="co">// S_{i+1}</span></span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a>    <span class="cn">Ok</span>(State <span class="op">{</span> Accumulator<span class="op">:</span> next<span class="op">.</span>into_affine()<span class="op">,</span> Commitment<span class="op">:</span> p_i <span class="op">}</span>)</span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>Because <span class="math inline">a_i(v)\neq0</span>, the <em>shifted</em> commitment now hides a polynomial whose <strong>only</strong> root at <span class="math inline">v</span> is the one we artificially created—exactly the witness we need for non-membership.</p>
<h3 id="non-membership-across-a-range-with-ivc">3. Non-membership across a <strong>range</strong> with IVC</h3>
<p>We can create a recursive (IVC) proof with base case is <span class="math inline">(A_j,S_j)</span> and repeat the above step for every <span class="math inline">i \ge j</span> to make a ranged claim.</p>
<ul>
<li>In the circuit we start from snapshot <span class="math inline">(A_j,S_j)</span>.</li>
<li>At each iteration we witness <span class="math inline">(P_i, \alpha_i)</span> and apply the <code>check_non_membership</code> hop.</li>
<li>The accumulator moves from <span class="math inline">(A_i,S_i)</span> to <span class="math inline">(A_{i+1},S_{i+1})</span>.</li>
<li>Only the <strong>current</strong> state crosses the IVC boundary; earlier data are discarded.</li>
</ul>
<p>After <span class="math inline">m-j</span> hops the verifier sees <span class="math inline">(A_m,S_m)</span>.
If <code>S_m</code> opens <em>to zero at <span class="math inline">v</span></em>, then—by induction—every intermediate polynomial was proven to evaluate <em>non-zero</em> at <span class="math inline">v</span>. Hence <span class="math inline">v</span> never appeared in any root vector <span class="math inline">\mathbf a_j</span>.</p>
<h3 id="why-this-matters-for-zcash-style-nullifier-checks">4. Why this matters for Zcash-style nullifier checks</h3>
<ul>
<li><strong>O(1) state:</strong> <code>Accumulator</code> is one point <span class="math inline">\rightarrow</span> no Merkle depth cap.</li>
<li><strong>Forget history:</strong> once the IVC proof exists, we can delete every old vector.</li>
<li><strong>Cheap per block:</strong> each hop is a couple of hashes and group operations</li>
<li><strong>Scalable non-membership:</strong> perfect for showing a nullifier <em>never</em> appeared, without keeping the nullifier set on-chain.</li>
</ul>
<p>In practice, replacing Zcash’s append-only Merkle trees with this accumulator would remove anchor leakage and tree maintenance, while still proving that a nullifier is unique.</p>
<h1 id="conclusion">Conclusion</h1>
<p>A vector-commitment accumulator gives Zcash a simpler approach to tracking spent notes: <strong>constant-size state</strong> that efficiently handles non-inclusion proofs (and can be extended to support inclusion proofs). This eliminates depth caps and tree migrations, while still providing the same security properties as Merkle trees.</p>
<p>Real-world deployment still has open questions—metadata privacy, lightweight witness updates, etc. Check out my implementations of this accumulator below.</p>
<table>
<colgroup>
<col style="width: 5%" />
<col style="width: 24%" />
<col style="width: 69%" />
</colgroup>
<thead>
<tr>
<th>Prototype</th>
<th>Idea</th>
<th>Code</th>
</tr>
</thead>
<tbody>
<tr>
<td>PCS-only</td>
<td>accumulator + polynomial commitment</td>
<td><a href="https://github.com/0xWOLAND/set-noninclusion">https://github.com/0xWOLAND/set-noninclusion</a></td>
</tr>
<tr>
<td>Folded proofs</td>
<td>same accumulator, block-by-block recursion with the zkVM SP1</td>
<td><a href="https://github.com/0xWOLAND/sp1-noninclusion">https://github.com/0xWOLAND/sp1-noninclusion</a> built with <a href="https://www.succinct.xyz/">SP1</a></td>
</tr>
</tbody>
</table>
<p>For the full design sketch, see <a href="https://hackmd.io/@dJO3Nbl4RTirkR2uDM6eOA/BJOnrTEj1x">Sean Bowe’s HackMD</a>.</p>
<hr />
<h1 id="appendix">Appendix</h1>
<p>The vector-commitment accumulator guarantees <strong>existence</strong> (the value really was inserted) and <strong>uniqueness</strong> (no two different vectors can open to the same commitment at the same index) for two independent reasons:</p>
<h4 id="existence-in-the-ivc-non-membership-chain">Existence in the IVC non-membership chain</h4>
<p>For non-inclusion, each IVC step asserts <span class="math inline">p_t(z)\neq0</span>. Because the polynomial <span class="math inline">p_t</span> encodes <em>exactly</em> the vector inserted at step <span class="math inline">t</span>, the statement “<span class="math inline">z</span> was not in <span class="math inline">{\bf v}_t</span>” is true <strong>iff</strong> the evaluation is non-zero. The final recursive proof therefore certifies that <em>for every step in the range</em>, <span class="math inline">z</span> was not a coordinate of any inserted vector. That is an <em>existential</em> statement about the entire history, achieved with only <span class="math inline">O(1)</span> verifier work.</p>
<h4 id="uniqueness-of-a-nullifier-style-opening">Uniqueness of a nullifier-style opening</h4>
<p>If we swap the vector commitment for one that derives a nullifier‐like output (e.g. include the index <span class="math inline">j</span> and value <span class="math inline">v_j</span> in a PRF), the uniqueness follows the same logic: the PRF output is a function of a <strong>single</strong> valid opening, and the binding of the commitment ensures no second, distinct opening can produce the same output without violating discrete-log or collision-resistance.</p>
<p>In short, the algebraic binding of the commitment bases <strong>anchors existence</strong>, while their linear independence <strong>enforces uniqueness</strong>—properties that survive each IVC update and give the accumulator the same double-spend resistance Merkle nullifiers provide, but with constant-size state and proofs.</p>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>A note <span class="math inline">n</span> is a cryptographic representation of a value <span class="math inline">v</span> that can be spent by the holder of the corresponding shielded spending key<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p><a href="https://zcash.github.io/orchard/design/nullifiers.html?highlight=nullifier#nullifiers">In Orchard</a>, the nullifier is computed as a PRF fo the note’s two randomizers <span class="math inline">\rho, \phi</span>, the owner’s nullifier-deriving key <span class="math inline">nk</span>, and the commitment <span class="math inline">cm</span>.<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn3"><p>A <a href="https://ethereum.org/en/developers/docs/nodes-and-clients/light-clients/">light client</a> verifies state transitions using succinct proofs instead of downloading the full chain. This allows secure operation on low-resource devices.<a href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn4"><p>Because <span class="math inline">cm</span> is a <a href="https://zcash.github.io/halo2/design/gadgets/sinsemilla.html"><strong>binding Sinsemilla commitment</strong></a>, two different openings <span class="math inline">(\text{note},r)</span> and <span class="math inline">(\text{note}&#39;,r&#39;)</span> cannot map to the same curve point without either (i) finding a collision in the hash-to-curve function or (ii) solving a discrete-log problem—both assumed infeasible. If you tweak <em>any</em> field of the note or choose a new blinding scalar <span class="math inline">r&#39;</span>, you inevitably get a <em>different</em> point and hence a different <span class="math inline">cm</span>. Since the Merkle‐path inside the zk-SNARK ties the spend to the exact <span class="math inline">cm</span> that already sits in the tree, you can’t “swap in” an alternative commitment; you would first have to mint that new <span class="math inline">cm</span> in a separate transaction. And because the nullifier is a PRF that explicitly includes <span class="math inline">cm</span>, changing the commitment would also change the nullifier, so the on-chain double-spend check still holds.<a href="#fnref4" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn5"><p>Each block in Zcash has a note-commitment tree of height. The genesis block is height 0, and each subsequent block increments this height by 1.<a href="#fnref5" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn6"><p>An incremental Merkle tree is a binary tree that supports efficient, append-only updates: each new element is added as the next available leaf, and only the hashes along its path to the root are recomputed. This allows the Merkle root to evolve over time without rebuilding the whole tree, enabling short inclusion proofs that stay constant in size.<a href="#fnref6" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn7"><p>Zcash uses incremental Merkle trees to maintain a commitment tree of all shielded notes. As each note is created, its commitment is appended to the next empty leaf. Internal nodes are updated on-the-fly, and the Merkle root evolves incrementally. Inclusion proofs are short (one hash per level), and the current root is used as a public anchor in each transaction. This enables privacy-preserving spending proofs without revealing which note is spent.<a href="#fnref7" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn8"><p>While the tree state grows, wallet witness updates can be made efficient: a trusted third party can provide just <span class="math inline">O(\log n)</span> advice that lets clients update their witnesses through <span class="math inline">n</span> insertions without downloading every new leaf. This is exactly how <a href="https://github.com/Electric-Coin-Company/zashi">Zashi</a> currently handles witness updates by having a server provide compact update hints that let wallets stay in sync with minimal bandwidth.<a href="#fnref8" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn9"><p>Sharding solves UX problems around note detection and spendability. See <a href="https://forum.zcashcommunity.com/t/improving-ux-with-detection-keys/46372/14">this forum post</a> for details on the tradeoffs and design.<a href="#fnref9" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>]]></description>
    <pubDate>Wed, 04 Jun 2025 00:00:00 UT</pubDate>
    <guid>https://bhargav.wtf/blog/zcash-3/index.html</guid>
    <dc:creator>Bhargav</dc:creator>
</item>
<item>
    <title>Private Money: Part 2</title>
    <link>https://bhargav.wtf/blog/zcash-2/index.html</link>
    <description><![CDATA[<p><strong>Warning:</strong> Mathematics. This is a fairly technical post! I assume a solid understanding of high-school math and a willingness to bear with me :)</p>
<p>This blog reviews the prerequisite mathematics for understanding the set-noninclusion accumulator in <strong>Project Tachyon</strong> — if you’re already familiar with basic abstract algebra, feel free to skip to the next post.</p>
<h1 id="some-mathematics-prerequisites">Some Mathematics Prerequisites</h1>
<h2 id="musical-motivation">Musical Motivation</h2>
<p>In the 18th century, composer <strong>Johann Sebastian Bach</strong> wrote music that continues to be admired for its elegance and structure. Despite lacking formal mathematical training, Bach often composed with a precision that feels inherently mathematical. One striking example is his <em>Crab Canon</em> from <em>The Musical Offering</em>:</p>
<div style="display: flex; justify-content: center;">
  <iframe src="https://www.youtube.com/embed/xUHQ2ybTejU" frameborder="0" allowfullscreen style="width: 100%; aspect-ratio: 16/9; max-width: 560px;"></iframe>
</div>
<p><em>Bach’s Crab Canon from the Musical Offering is a fascinating example of mathematical music. When played forward and backward simultaneously, it creates a perfect palindrome — a musical Möbius strip where the end connects seamlessly to the beginning. This topological structure, where a one-sided surface is created by twisting and joining a strip, mirrors how the canon’s melody can be read in both directions while maintaining musical coherence.</em></p>
<p>The connection between musical expression and hidden structure has fascinated people for a long time. One of the key ideas here is the <a href="https://en.wikipedia.org/wiki/Circle_of_fifths"><strong>Circle of Fifths</strong></a> — a diagram that lays out musical notes so that closely related keys sit next to each other in a loop. It’s a handy way to make sense of harmony, and its circular shape hints at something deeper going on beneath the sound: a kind of pattern that music follows, even when we’re not consciously aware of it.</p>
<p><img src="https://upload.wikimedia.org/score/r/n/rn1zaakvsmp2icu895k7e1obrhuy0lw/rn1zaakv.png" alt="Circle of fifths clockwise within one octave" />
<em>Circle of fifths clockwise within one octave. Source: <a href="https://en.wikipedia.org/wiki/Circle_of_fifths">Wikipedia</a>.</em></p>
<div style="display: flex; justify-content: center;">
  <audio controls>
    <source src="https://upload.wikimedia.org/score/r/n/rn1zaakvsmp2icu895k7e1obrhuy0lw/rn1zaakv.mp3" type="audio/mpeg">
    Your browser does not support the audio element.
  </audio>
</div>
<p>Building on this, modern visualizations let us see musical motion as something geometric. In the animation below, a major seventh progression moves across the surface of an umbilic torus — a looping, twisted shape that shows how harmony can circle around while still shifting forward. Like Bach’s Canon, it turns music into more than just a line of notes — it becomes a kind of movement through space, shaped by patterns and rules we can start to recognize, even if we can’t name them yet.</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/The_circle_of_fifths_on_umbilic_torus_surface.gif/500px-The_circle_of_fifths_on_umbilic_torus_surface.gif" alt="Major 7th progression on umbilic torus surface" />
<em>This animation shows a major seventh progression traced on an umbilic torus surface — a higher-dimensional visualization of the circle of fifths. The smooth rotation through harmonic space represents tonal motion as continuous geometry, revealing deep symmetries between pitch, interval, and curvature.</em></p>
<p>These patterns aren’t just beautiful — there’s clearly something deeper going on under the surface. To really make sense of it, we’ll need a new kind of language — one that helps us talk about how music moves, transforms, and loops back on itself. That’s where we’re headed next: group theory.</p>
<h2 id="from-loops-to-logic">From Loops to Logic</h2>
<p>A <strong>group</strong> is a set equipped with a rule for combining elements — an operation — that behaves predictably: there’s a way to combine any two elements (closure), the way elements are grouped doesn’t matter (associativity), there’s an identity element that does nothing (identity), and every element has an inverse that undoes it (inverses).</p>
<p>A simple example of a group is a finite cyclic group <span class="math inline">\mathbb{Z} / n \mathbb{Z}</span>, the integers modulo <span class="math inline">n</span> which consists of the set</p>
<p><span class="math display">
    {0, 1, 2, \ldots, n - 1}
</span></p>
<p>with the action being addition modulo <span class="math inline">n</span>. For example, in <span class="math inline">\mathbb{Z} / 12 \mathbb{Z}</span>, <span class="math inline">12 \cong 0</span> so <span class="math inline">7 + 6 = 1 \mod 12</span>. Check out <a href="https://en.wikipedia.org/wiki/Group_(mathematics)#:~:text=Definition%20and%20illustration">Wikipedia</a> for the mathematical definition of a group.</p>
<p>It turns out that cyclic groups are also very useful for cryptography. Suppose that you start with <span class="math inline">0</span> in <span class="math inline">\mathbb{Z} / 12 \mathbb{Z}</span> and keep adding 5. Then you get the sequence:</p>
<p><span class="math display">
    0, 5, 10, 3, 8, 1, \ldots
</span></p>
<p>Eventually, you reach every number in the set. Now, if I asked you to tell how many times you have to add 5 to get the number 8, would you be able to tell me?</p>
<p>This is the <a href="https://en.wikipedia.org/wiki/Discrete_logarithm">Discrete Logarithm Problem</a>. Given a generator (like 5) and a result (like 8), the challenge is to figure out how many times the generator was applied. In this example, the answer is 4, since <span class="math inline">5 \times 4 = 20 \cong 8 \mod 12</span>.</p>
<p>In cryptography, we often write cyclic groups using <strong>multiplicative notation</strong>, where repeated application of a generator <span class="math inline">g</span> is written as:</p>
<p><span class="math display">
g^0, \; g^1, \; g^2, \; \dots, \; g^{n - 1}
</span></p>
<p>This gives all elements of the group if <span class="math inline">g</span> is a generator.</p>
<p>The <strong>discrete logarithm problem</strong> asks:
Given <span class="math inline">g</span> and <span class="math inline">h = g^x</span>, find <span class="math inline">x</span>.</p>
<p>For small numbers, this is easy to solve by trial. But in large cyclic groups (especially those built from primes with hundreds of digits) the problem becomes extremely hard. For small numbers, this is easy to solve by trial. But in large cyclic groups (like those used in Ethereum’s BLS signatures, where the modulus is a 381-bit prime) the problem becomes practically impossible to reverse, and that’s exactly what makes it secure against <a href="https://www.cs.umd.edu/~amchilds/teaching/w08/l02.pdf">classical computers</a>.</p>
<h1 id="pedersen-vector-commitment-scheme">Pedersen Vector Commitment Scheme</h1>
<p>Finally, we have the language to talk about tools that rely on group structure to ensure both <strong>hiding</strong> and <strong>binding</strong>: the two essential properties of a cryptographic commitment. One of the simplest and most elegant examples is the <strong>Pedersen commitment</strong>.</p>
<h2 id="hiding-and-binding">Hiding and Binding</h2>
<p>Before going further, it’s worth pausing to explain what we mean by <em>hiding</em> and <em>binding</em>.</p>
<ul>
<li><p><strong>Hiding</strong> means the commitment doesn’t reveal any information about the underlying message. Even if someone sees the commitment, they can’t figure out what value was committed — because it’s masked using randomness.</p></li>
<li><p><strong>Binding</strong> means that once you’ve committed to a value, you can’t later change your mind. That is, you can’t open the same commitment to a different value. This ensures the commitment is fixed and can’t be altered after the fact.</p></li>
</ul>
<p>In short: hiding protects privacy; binding ensures integrity.</p>
<h2 id="the-pedersen-commitment">The Pedersen Commitment</h2>
<p>Let <span class="math inline">G</span> be a cyclic group of prime order <span class="math inline">q</span>, with generators <span class="math inline">g</span> and <span class="math inline">h</span> such that no one knows the discrete logarithm between them. To commit to a value <span class="math inline">m \in \mathbb{Z} / q \mathbb{Z}</span>, choose a random blinding factor <span class="math inline">r \in \mathbb{Z} / q \mathbb{Z}</span> and compute:</p>
<p><span class="math display">
\text{Com}(m, r) = g^m h^r
</span></p>
<p>This is a commitment to <span class="math inline">m</span> that is:</p>
<ul>
<li><strong>Perfectly hiding</strong>: because <span class="math inline">r</span> is chosen at random, the output reveals nothing about <span class="math inline">m</span></li>
<li><strong>Computationally binding</strong>: under the discrete log assumption, it’s infeasible to find two different pairs <span class="math inline">(m, r)</span> and <span class="math inline">(m&#39;, r&#39;)</span> that yield the same commitment</li>
</ul>
<p>Additionally, Pedersen commitments are <strong>homomorphic</strong>:</p>
<p><span class="math display">
\text{Com}(m_1, r_1) \cdot \text{Com}(m_2, r_2) = \text{Com}(m_1 + m_2, r_1 + r_2)
</span></p>
<p>This means commitments can be added without opening them, a property useful in many protocols.</p>
<p>Now here’s how we implement this in Rust:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode rust"><code class="sourceCode rust"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">pub</span> <span class="kw">fn</span> commit(m<span class="op">:</span> Scalar<span class="op">,</span> r<span class="op">:</span> Scalar<span class="op">,</span> g<span class="op">:</span> GroupAffine<span class="op">,</span> h<span class="op">:</span> GroupAffine) <span class="op">-&gt;</span> GroupAffine <span class="op">{</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>    (g <span class="op">*</span> m <span class="op">+</span> h <span class="op">*</span> r)<span class="op">.</span>into_affine()</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<h2 id="from-commitments-to-vector-commitments">From Commitments to Vector Commitments</h2>
<p>To commit to a whole vector <span class="math inline">\mathbf{m} = (m_1, m_2, \dots, m_n)</span>, we extend the idea by using <span class="math inline">n</span> independent generators <span class="math inline">g_1, g_2, \dots, g_n \in G</span>, and a single blinding base <span class="math inline">h</span>. The commitment is:</p>
<p><span class="math display">
\text{Com}(\mathbf{m}, r) = g_1^{m_1} \cdot g_2^{m_2} \cdots g_n^{m_n} \cdot h^r
</span></p>
<p>The vector commitment version in Rust takes an array of generators:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode rust"><code class="sourceCode rust"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">pub</span> <span class="kw">fn</span> open(v<span class="op">:</span> <span class="op">&amp;</span>[Scalar]<span class="op">,</span> r<span class="op">:</span> Scalar<span class="op">,</span> j<span class="op">:</span> <span class="dt">usize</span>) <span class="op">-&gt;</span> <span class="dt">Result</span><span class="op">&lt;</span>(Scalar<span class="op">,</span> Scalar<span class="op">,</span> GroupAffine)<span class="op">&gt;</span> <span class="op">{</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> j <span class="op">&gt;=</span> v<span class="op">.</span>len() <span class="op">{</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>        <span class="cf">return</span> <span class="cn">Err</span>(<span class="pp">anyhow!</span>(<span class="st">&quot;Index out of bounds&quot;</span>))<span class="op">;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> blind <span class="op">=</span> POINTS[<span class="dv">0</span>] <span class="op">*</span> r<span class="op">;</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> witness <span class="op">=</span> POINTS[<span class="dv">1</span><span class="op">..</span>]<span class="op">.</span>iter()<span class="op">.</span>enumerate()</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>        <span class="op">.</span>filter(<span class="op">|</span>(i<span class="op">,</span> _)<span class="op">|</span> <span class="op">*</span>i <span class="op">!=</span> j)</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>        <span class="op">.</span>map(<span class="op">|</span>(i<span class="op">,</span> p)<span class="op">|</span> <span class="op">*</span>p <span class="op">*</span> v[i])</span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>        <span class="op">.</span><span class="pp">sum::</span><span class="op">&lt;</span>GroupProjective<span class="op">&gt;</span>() <span class="op">+</span> blind<span class="op">;</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>    </span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a>    <span class="cn">Ok</span>((v[j]<span class="op">,</span> r<span class="op">,</span> witness<span class="op">.</span>into_affine()))</span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>This compactly binds the entire vector <span class="math inline">\mathbf{m}</span> into a single group element. It maintains the same properties:</p>
<ul>
<li><strong>Hiding</strong>, because the random <span class="math inline">r</span> masks the entire vector</li>
<li><strong>Binding</strong>, assuming the generators <span class="math inline">g_1, \dots, g_n</span> are independent and the discrete log relationships between them are unknown</li>
</ul>
<p>And here’s how to verify the commitment:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode rust"><code class="sourceCode rust"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">pub</span> <span class="kw">fn</span> check(c<span class="op">:</span> GroupAffine<span class="op">,</span> v_j<span class="op">:</span> Scalar<span class="op">,</span> witness<span class="op">:</span> GroupAffine<span class="op">,</span> h_j<span class="op">:</span> GroupAffine) <span class="op">-&gt;</span> <span class="dt">bool</span> <span class="op">{</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>    c <span class="op">==</span> witness <span class="op">+</span> h_j <span class="op">*</span> v_j</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<h2 id="algebraic-properties">Algebraic Properties</h2>
<p>What makes Pedersen (vector) commitments especially powerful is their algebraic structure:</p>
<ul>
<li><p><strong>Linearity</strong>: Commitments respect linear combinations:</p>
<p><span class="math display">
\text{Com}(\mathbf{m}, r) \cdot \text{Com}(\mathbf{m}&#39;, r&#39;) = \text{Com}(\mathbf{m} + \mathbf{m}&#39;, r + r&#39;)
</span></p>
<p>where vector addition is component-wise.</p></li>
<li><p><strong>Scalability</strong>: You can aggregate commitments across multiple vectors:</p>
<p><span class="math display">
\prod_{i=1}^k \text{Com}(\mathbf{m}^{(i)}, r_i) = \text{Com}\left(\sum_{i=1}^k \mathbf{m}^{(i)}, \sum_{i=1}^k r_i\right)
</span></p></li>
<li><p><strong>Inner-product compatibility</strong>: Because exponentiation distributes over sums, Pedersen commitments can be used inside inner-product arguments (like in <a href="https://crypto.stanford.edu/bulletproofs/">Bulletproofs</a>), where both prover and verifier can manipulate commitments algebraically without knowing the underlying messages.</p></li>
<li><p><strong>Non-interactive opening proofs</strong>: Given <span class="math inline">\mathbf{m}</span> and <span class="math inline">r</span>, it’s trivial to open the commitment and prove correctness. Zero-knowledge variants can be layered on top if needed. We will see this in the next blog.</p></li>
</ul>
<p>These algebraic properties make Pedersen commitments a favorite building block in privacy-preserving protocols, SNARK-friendly constructions, and succinct proofs of integrity over large datasets. We will see how we can build Sean Bowe’s <a href="https://hackmd.io/@dJO3Nbl4RTirkR2uDM6eOA/BJOnrTEj1x">set-noninclusion accumulator</a> from this in the next blog post.</p>]]></description>
    <pubDate>Wed, 21 May 2025 00:00:00 UT</pubDate>
    <guid>https://bhargav.wtf/blog/zcash-2/index.html</guid>
    <dc:creator>Bhargav</dc:creator>
</item>
<item>
    <title>Private Money: Part 1</title>
    <link>https://bhargav.wtf/blog/zcash-1/index.html</link>
    <description><![CDATA[<h1 id="on-private-money">On Private Money</h1>
<h2 id="moneros-limitations">Monero’s Limitations</h2>
<p>One of the most fascinating developments in cryptocurrency has been the class of privacy-focused cryptoassets. Monero currently dominates this category in terms of usage (market cap of $6.73B and 24h volume of $110.49M) but it is not without its limitations.</p>
<p>Monero’s ring signature system provides each transaction input with a ring of decoy outputs plus their real one. In theory, an observer shouldn’t tell which output in the ring was actually spent. But in practice, <strong>set intersection attacks</strong> and other heuristics have exposed limitations in their privacy model.</p>
<p>In a set intersection attack, an adversary analyzes multiple transactions whose rings share common outputs in order to distinguish real spends by intersecting these sets. For example, suppose that Alice owns an old Monero output <span class="math inline">A</span>. She spends it in a transaction on the main Monero chain forming a ring</p>
<p><span class="math display">
    \text{Tx1} = \{A, B, C, D, E\}
</span></p>
<p>Because of Monero’s ring signature, an observer can’t tell which of the five is the real input. Now suppose Alice also spends the same output on a Monero fork:</p>
<p><span class="math display">
    \text{Tx1&#39;} = \{F, G, A, H, I\}
</span></p>
<p>Each ring has 5 outputs, but <span class="math inline">A</span> is the only one common in both, which isn’t good. In Monero, a user’s true spend should be computationally hidden among many decoys, but the <em>re-use</em> of the same output across multiple rings breaks this illusion. According to a recent review by Cypher Stack, this remains a practical <a href="https://moneroresearch.info/index.php?action=resource_RESOURCEVIEW_CORE&amp;id=235#">attack vector</a>.</p>
<p>Reusing decoys in Monero has some serious ripple effects. If the real spend for a single output is ever revealed—through an exchange, user mistake, or clever analysis—it doesn’t just affect that one transaction. It lets anyone rule out that output as a decoy in any other ring it appeared in. That, in turn, helps narrow down the real spends in those rings, and the process can repeat across the network. In Monero’s early days, this was especially bad—over 65% of inputs didn’t even use decoys, so they were trivially exposed. And even after mixins were added, researchers found that another ~22% of inputs could still be traced just by overlapping the right rings and <a href="https://eprint.iacr.org/2017/338.pdf#:~:text=%1Brst%20heuristic%20,any%20ground%20truth%20on%20RingCTs">eliminating possibilities</a>.</p>
<p>What’s more troubling is that a powerful attacker could add their own known decoys to the ring. By flooding the network with transactions that reuse known or controlled outputs as decoys, they can “poison” the anonymity set. Once a few real spends are known, these poisoned rings make it much easier to strip away decoys and trace other transactions. It creates a chain reaction where even users who took care to protect their privacy can end up exposed if their transactions enter rings with adversaries.</p>
<p>This set-intersection effect accurately explains why <strong>churning</strong>—the practice of sending funds back to yourself to “clean” them—is especially dangerous in Monero, and why any privacy leak (such as using a KYC exchange or revealing a single address) can compromise not just a single transaction but the entire anonymity set. When a user spends funds that were previously used in a public or traceable context, it contaminates any ring that output appears in. Even indirect interactions with centralized services, or careless use of wallets that expose scanning patterns, can cascade into de-anonymizing dozens or hundreds of other users.</p>
<p>What’s especially troubling is that normal users—people just trying to move their money or use an exchange—can unknowingly weaken the privacy of the entire network. Every time someone reveals a spend, even unintentionally, it shrinks the effective anonymity set for everyone else. Over time, these small leaks accumulate, and the network becomes easier to analyze. In Monero, privacy erosion isn’t always caused by attackers—it can emerge organically from the way the system works.</p>
<!-- Insert Diagram Here -->
<p>Modern Monero has strengthened its defenses against intersection attacks, though primarily through patches rather than rigorous cryptographic guarantees. Techniques like enforcing minimum ring sizes and refining decoy selection help mitigate “closed set” intersection attacks, where outputs only appear with each other and can thus be linked. Although these measures have made analysis harder than in Monero’s early days—when researchers could deanonymize up to 90% of transactions for just <a href="https://eprint.iacr.org/2019/455.pdf">~$1,000</a>—a well-resourced adversary with enough data can <em>still</em> perform large-scale intersection attacks.</p>
<p><img src="/images/zcash/monero.png" alt="Monero attack resistance matrix" />
<em>Figure: Monero’s resistance to various analysis techniques, adapted from Goodell, B. (2024), “History and state of Monero security analysis”. As the table shows, Monero remains vulnerable.</em></p>
<h2 id="zcashs-mathematical-guarantees">Zcash’s Mathematical Guarantees</h2>
<p>Unlike Monero, Zcash doesn’t rely on probabilistic arguments with decoys. Instead, it achieves <strong>ledger indistinguishability</strong> using zero-knowledge proofs. Every shielded transaction in Zcash is cryptographically indistinguishable from random noise. And now, it is being accelerated in Zcash Engineer Sean Bowe’s <a href="https://seanbowe.com/blog/tachyon-scaling-zcash-oblivious-synchronization/">Project Tachyon</a>. Succinctly, here is how Tachyon addresses the limitations of Monero:</p>
<table>
<colgroup>
<col style="width: 17%" />
<col style="width: 39%" />
<col style="width: 43%" />
</colgroup>
<thead>
<tr>
<th style="text-align: left;">Problem</th>
<th style="text-align: left;">How Monero Suffers</th>
<th style="text-align: left;">How Tachyon Fixes It</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">Node Resource Use</td>
<td style="text-align: left;">Every decoy must be tracked; ring signatures and Bulletproofs increase block size</td>
<td style="text-align: left;">Zcash blocks with aggregated zk-SNARKs remain small and fast to verify</td>
</tr>
<tr>
<td style="text-align: left;">Decoy Analysis</td>
<td style="text-align: left;">Set intersection and timing leaks remain partially effective</td>
<td style="text-align: left;">No decoys exist in Zcash; every transaction is indistinguishable</td>
</tr>
<tr>
<td style="text-align: left;">User Privacy Trade-offs</td>
<td style="text-align: left;">Remote nodes or light wallets may see which outputs you scan</td>
<td style="text-align: left;">Oblivious syncing services can advance wallet state without learning anything about your notes</td>
</tr>
<tr>
<td style="text-align: left;">Transaction Throughput</td>
<td style="text-align: left;">Monero hits practical limits during spam (e.g. March 2024)</td>
<td style="text-align: left;">PCD allows scaling to high throughput with small block size and low validator burden</td>
</tr>
</tbody>
</table>
<p>In Project Tachyon, Bowe rethinks how wallets sync and interact with blockchain state in a secure manner. Tachyon introduces a model where wallets maintain a proof of their own synchronization (via <a href="https://dspace.mit.edu/handle/1721.1/61151">proof-carrying data</a>), allowing validators to prune almost all historic data. By shifting secret distribution off-chain and removing reliance on encrypted note payloads in the ledger, it enables lean, stateless wallets and small, efficient blocks.</p>
<p>Where Monero’s privacy degrades as the network grows—due to increasing chain bloat, repeated decoy reuse, and compounding intersection attacks—Tachyon pushes Zcash in the opposite direction: greater usage yields greater efficiency/security. In effect, Monero’s scale is a liability, while Zcash’s scale becomes a strength. Tachyon turns what is traditionally a privacy trade-off into a privacy advantage.</p>
<p>For the full technical breakdown, check out Sean’s blog on <a href="https://seanbowe.com/blog/tachyon-scaling-zcash-oblivious-synchronization/">Project Tachyon</a>. In the next couple of blogs, we’ll walk through a toy implementation of the accumulator from Project Tachyon from zero!</p>]]></description>
    <pubDate>Tue, 20 May 2025 00:00:00 UT</pubDate>
    <guid>https://bhargav.wtf/blog/zcash-1/index.html</guid>
    <dc:creator>Bhargav</dc:creator>
</item>

    </channel>
</rss>
