<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/">
<channel>
  <title>Arnav Sharma</title>
  <link>https://www.arnnvv.com/</link>
  <atom:link href="https://www.arnnvv.com/rss.xml" rel="self" type="application/rss+xml" />
  <image>
    <url>https://www.arnnvv.com/og.png</url>
    <title>Arnav Sharma</title>
    <link>https://www.arnnvv.com/</link>
  </image>
  <description>A collection of thoughts, stories, and articles on systems, software, and learning.</description>
  <language>en</language>
  <lastBuildDate>Tue, 18 Aug 2026 09:57:45 GMT</lastBuildDate>
  <item>
    <title>Thinking is cheap, writing isn't</title>
    <link>https://www.arnnvv.com/my-writings/thinking-is-cheap-writing-isnt/</link>
    <guid isPermaLink="true">https://www.arnnvv.com/my-writings/thinking-is-cheap-writing-isnt/</guid>
    <pubDate>Mon, 02 Feb 2026 13:05:03 GMT</pubDate>
    <description>Writing is how you find out what you think. It’s the paper you reach for when the problem gets complex enough to need it.</description>
    <content:encoded><![CDATA[<p>The idea that started this came from somewhere small. I was thinking about mental math, how you can do simple calculations in your head without trouble, but the moment something gets complex you instinctively reach for a pen. Not because you’re bad at math. Because the problem outgrew the space you had for it.</p>
<p>I heard something similar on a radio show once. Couldn’t tell you which one. But it stuck.</p>
<p>That’s kind of the problem I’m writing about, actually. Things stick but don’t finish forming. I consume a lot, articles, books, podcasts, specs. The Signal double ratchet spec at 2am, Meditations in the afternoon, some random Hacker News thread in between. The input is constant. The output is mostly silence.</p>
<p>Not because I have nothing to say. I think I do. It’s more that the gap between having a thought and being able to write it down cleanly is wider than I expected. The thought exists. Getting it out is the part that requires something I don’t always have.</p>
<hr>
<p>I’m aware I care too much about what people think. It comes up even when I know it’s irrational. There’s a half-decent evolutionary argument for why, if the group rejected you as a caveman you were actually dead, but knowing the origin doesn’t make the feeling go away. It just makes you slightly more annoyed at yourself for having it.</p>
<p>That anxiety shows up in writing specifically. Every sentence feels like a position I’ll have to defend. So I think longer instead of writing sooner, waiting for the thought to feel airtight. But thinking alone doesn’t close the loop. It just keeps the thought in that comfortable vague state where it’s still potentially brilliant and hasn’t been tested by actually existing yet.</p>
<p>The mental math thing is the right analogy. You don’t expect to do long multiplication in your head. Nobody calls that a failure. But we expect real opinions to arrive fully formed, silently, before we’re allowed to say anything. As if writing is just transcription, you think it, then you write it down. That’s not how it works. Writing is how you find out what you think. It’s the paper you reach for when the problem gets complex enough to need it.</p>
<hr>
<p>Most of what I’ve figured out that actually feels solid on ORMs, on cryptography, on how I want to build things, came from trying to explain it to someone or write it down. The understanding didn’t precede the writing. The writing produced the understanding.</p>
<p>The stuff still sitting in my head half-formed? I’m not sure it’s actually there the way I think it is. It might just be familiarity mistaken for knowledge. I’ve read enough to recognize ideas when I see them. That’s not the same as having worked them through.</p>
<p>Aurelius wrote: <strong>soon you will have forgotten all things, and all things will have forgotten you.</strong> He didn’t mean it as doom. He meant it as permission. If the scoreboard resets anyway, the draft that embarrasses you today costs nothing. The thought you never wrote down costs everything because you never actually finished thinking it.</p>
<p>Everything else is still mental math on a problem that’s too big for my head.</p>]]></content:encoded>
    <media:content url="https://www.arnnvv.com/og.png" type="image/png" medium="image" />
  </item>
  <item>
    <title>The Ultimate Compass</title>
    <link>https://www.arnnvv.com/my-writings/the-ultimate-compass/</link>
    <guid isPermaLink="true">https://www.arnnvv.com/my-writings/the-ultimate-compass/</guid>
    <pubDate>Fri, 17 Oct 2025 20:49:50 GMT</pubDate>
    <description>The game was always temporary. The giving everything was always the point.</description>
    <content:encoded><![CDATA[<p>The greatest honor a Roman general could receive was a triumph, a victory procession through Rome, crowned with laurel, hailed as near-divine. But behind him stood a slave whose only job was to whisper: <strong>Respice post te. Hominem te esse memento.</strong> Look behind you. Remember you are only mortal. I’ve always liked this image. Not because it’s poetic (though it is), but because it’s useful. A whisper at the height of glory that cuts through everything and shows you what’s real.</p>
<h2 id="rome">Rome</h2>
<p>In his Meditations, Aurelius does something specific with mortality that most people miss. He doesn’t just say you’re going to die. He turns it into a question: what exactly are you afraid of losing?</p>
<p>That’s where the work is. Because most of what people are afraid of losing, reputation, money, status, the approval of people who won’t remember their name in fifty years, collapses under even gentle scrutiny. Aurelius wrote: <em>soon you will have forgotten all things, and all things will have forgotten 
you.</em> Your fortune won’t grieve you. To anchor your life to these things is to tie yourself to smoke.</p>
<h2 id="childs-play">Child’s play</h2>
<p>If nothing lasts, why do anything at all?</p>
<p>I remember watching a video on <a href="https://lakshminarayanlenasia.com/wp-content/uploads/2021/12/AdviataVedanta_AdiShankara.pdf" target="_blank" rel="noopener noreferrer">Atman and Brahman</a> that put it in a way that stuck with me. It said: think of a small child playing cricket. They know, on some level, that this game won’t matter. And yet they give everything. They appeal every decision. They argue over wides. For that moment they are fully in it. Then the game ends, they get sad or happy depending on how it went, pack their stuff, and go home.</p>
<p>That’s it. That’s the whole thing. <strong>The game was always temporary. The giving everything was always the point. A life practically meant to have no meaning gets its meaning exactly from playing it like it does.</strong> Nothing mattering cosmically doesn’t mean all choices are the same in the time you have. It just means you get to choose the game.</p>
<h2 id="artha">Artha</h2>
<p>Growing up in India, Dharma, Artha, Kama, Moksha weren’t concepts I discovered in a book. They were just in the air, at home, in school, in how people talked about what a life should look like. Chanakya especially stuck with me. Not because he was the most spiritual, but because he was the most honest about how the world actually works. His idea that without material strength even truth has no protection made sense in a way that pure renunciation never did. You still have to live a life. You can’t move to the mountains and take sanyasa every time you’re looking for meaning.</p>
<p>Bhishma spends a significant portion of the <a href="https://idsa.in/wp-content/uploads/2025/07/book-Col-Vivek-Chadha-Mahabharatas-Dharmic-Compass.pdf" target="_blank" rel="noopener noreferrer">Mahabharata</a> advising Yudhishthira on how to actually rule not just how to be good, but how to function. His point isn’t that wealth is the goal. It’s that dharma needs a foundation, and that foundation is material. You can’t protect what’s right if you have nothing to stand on. Chanakya says the same thing in the Arthashastra. The ancients weren’t telling you to escape the world. They were telling you to be strong enough to operate in it without losing yourself.</p>
<p>Aurelius ruled an empire. Seneca was one of the richest men in Rome. Virtue doesn’t mean poverty. It means power under control. The enemy was never ambition. The enemy was letting ambition own you instead of the other way around.</p>
<h2 id="rafa">Rafa</h2>
<p>Once you accept the scoreboard resets, something changes about how you play.</p>
<p>There’s a documentary where Nadal talks about winning. Victory feels good, he says, but it’s momentary. What stayed with him was the desire to keep fighting. The satisfaction of doing something hard. He took something from the suffering itself, not just the result.</p>
<p>Before the 2008 Wimbledon final, Federer’s surface, 5 back to back titles on for a 6th, everyone expected Federer to win, Nadal said something that’s stayed with me. He knew Roger was probably the better player on grass at a pure skill level. His thought going in was: <strong>I am willing to suffer more than him.</strong> Winning is an event. Competing is an identity. And the only way you can sustain that, the only way suffering becomes something you choose rather than something that breaks you, is if the scoreboard isn’t carrying the full weight of meaning.</p>
<p>There’s a version of this where you just tell yourself victory is coming, it’ll all be worth it eventually. That works until you realize winning isn’t guaranteed. Then it falls apart. The more durable version is simpler: the scoreboard resets anyway, so having a shot is the thing. Not whether you win or lose it.</p>
<p>I won’t pretend this is easy to live by. It’s easier to say than to implement. But it works when you need it most, when the outcome is out of your hands and the only thing left is whether you kept going.</p>
<hr>
<p>So what should fill that blank Aurelius left, what are you actually afraid of losing?</p>
<p>For him the answer was internal. Virtue. The ability to act with courage, with honesty, without flinching. Death only matters if it interrupts that. And if that’s what you’re protecting, the solution is immediate: practice it now, while you still can.</p>
<p><strong>You could leave life right now. Let that determine what you do and say and think.</strong></p>
<p>The whisper in the chariot wasn’t a curse. It was a compass. It pointed away from the things that don’t survive you and toward the one thing that does, who you were while you were here, what you were willing to go through, whether you played it seriously or spent your time protecting yourself from the possibility of losing.</p>
<p>The scoreboard resets. It always does. The child packs their bag and goes home. What mattered was how they played.</p>]]></content:encoded>
    <media:content url="https://www.arnnvv.com/og.png" type="image/png" medium="image" />
  </item>
  <item>
    <title>My Case against ORMs</title>
    <link>https://www.arnnvv.com/my-writings/my-case-against-orms/</link>
    <guid isPermaLink="true">https://www.arnnvv.com/my-writings/my-case-against-orms/</guid>
    <pubDate>Wed, 01 Oct 2025 09:59:38 GMT</pubDate>
    <description>In reality, it hides the actual thing you care about, your SQL and replaces it with a Rube Goldberg machine of struct tags, magic method chaining</description>
    <content:encoded><![CDATA[<p>Let me just say it straight: I don’t like using ORMs.</p>
<p>People act like using an ORM is some sort of higher-level abstraction that makes life easier. In reality, it hides the actual thing you care about, your SQL and replaces it with a Rube Goldberg machine of struct tags, magic method chaining, and runtime query generation. And then they call it “maintainable.” It’s a trap. A convenient-looking trap.</p>
<h2 id="the-one-model-to-rule-them-all-fallacy">The “One Model to Rule Them All” Fallacy</h2>
<p>On paper, having a single model seems like a good idea. But once you get into any real-world scenario, it becomes a mess. Suddenly, the field that makes sense for the DB (<code>sql.NullString</code> or a custom nullable type) becomes a pain in the API layer. And if you want to omit that field in some response? Tough luck. You’re stuck with a struct that’s trying to do five jobs at once. It’s the god function problem, but with data.</p>
<p>When you split your models, one for DB, one for API, suddenly your logic gets cleaner. You don’t worry about some random tag breaking your DB logic. You can shape your API payloads exactly how they should be, and your DB models can focus on what the database actually wants.</p>
<h2 id="what-about-sql-injection">What About SQL Injection?</h2>
<p>A common argument in favor of ORMs is that they protect you from SQL injection.</p>
<p>That’s only true if you’re writing raw SQL naively by string-concatenating user input directly into your queries. But nobody doing raw SQL seriously does that.</p>
<p>The actual solution is simple and safe: <strong>use prepared statements</strong>.</p>
<p>All modern database drivers support them. Whether you’re using Go, Node.js, Python, or Rust, parameterized queries are easy and idiomatic. You pass values as arguments, and the driver handles escaping and query planning safely.</p>
<p>No ORM magic required. No injection risk.</p>
<h2 id="orms-make-easy-things-easy-and-hard-things-impossible">ORMs Make Easy Things Easy and Hard Things Impossible</h2>
<p>Yes, ORMs can make the basics easier. <code>User.find(...)</code>, <code>.save()</code>, <code>.delete()</code> — fine. But try writing a query that actually touches real business logic: something with joins, aggregates, filters, maybe window functions. Now your nice ORM abstraction becomes a maze of nested includes, prefetches, or custom query builders that fight the underlying database every step of the way.</p>
<p>Ever debugged an n+1 query problem in an ORM? Good luck. Prefetching in ORMs is usually painful and opaque. With raw SQL, you just write the join.</p>
<h2 id="runtime-queries-performance-red-flag">Runtime Queries: Performance Red Flag</h2>
<p>Most ORMs build and compile your queries at <strong>runtime</strong>, based on struct tags and method chaining. That means every query is dynamically assembled when the code runs. You don’t get to see the final query easily. You don’t know what exactly is sent to the database unless you enable logging and reverse-engineer it.</p>
<p>Why?</p>
<p>You’re using a straightforward language like SQL, but then instead of writing that directly, you wrap it in a JavaScript-style query builder or some DSL. And now you have to trust that it’s doing the right thing.</p>
<p><strong>Someone might argue</strong>: <em>But manually writing SQL and mapping table fields to code wastes time, what about type inference, syncing column names, etc.?</em></p>
<p>Fair. That would be painful <em>if</em> you had to do it all by hand.</p>
<p>But you don’t. That’s where tools like <a href="https://sqlc.dev" target="_blank" rel="noopener noreferrer">sqlc</a> shine.</p>
<p>You write your SQL. It parses it and auto-generates type-safe Go code from it. No need to manually map types. It’s fast, ergonomic, and works with lightweight drivers. You get static analysis and full compiler support—with zero runtime reflection or dynamic DSLs.</p>
<h2 id="bottom-line">Bottom Line</h2>
<p>If you care about:</p>
<ul><li>Performance  </li><li>Debuggability  </li><li>Query clarity  </li><li>Explicitness  </li><li>Clean separation of concerns  </li></ul>
<p>then you shouldn’t be using an ORM.</p>
<p>Use a tool like <a href="https://sqlc.dev" target="_blank" rel="noopener noreferrer">sqlc</a> that gives you the best of both worlds: real SQL + generated types. It’s fast, safe, and transparent. And most importantly, it respects your database instead of pretending it’s a Java object store.</p>
<p>Inspired by <a href="https://blog.codinghorror.com/object-relational-mapping-is-the-vietnam-of-computer-science" target="_blank" rel="noopener noreferrer">Jeff Atwood</a></p>]]></content:encoded>
    <media:content url="https://www.arnnvv.com/og.png" type="image/png" medium="image" />
  </item>
  <item>
    <title>From framework to funnel, The genius of Vercel</title>
    <link>https://www.arnnvv.com/my-writings/from-framework-to-funnel-the-genius-of-vercel/</link>
    <guid isPermaLink="true">https://www.arnnvv.com/my-writings/from-framework-to-funnel-the-genius-of-vercel/</guid>
    <pubDate>Wed, 01 Oct 2025 09:44:30 GMT</pubDate>
    <description>Today, Next.js isn’t just a framework anymore. It’s a gateway to an entire platform experience</description>
    <content:encoded><![CDATA[<p><strong>Next.js used to be a framework</strong>. You know, like the good old frameworks that gave you a set of tools and patterns, and then got the hell out of your way. You could build stuff, host it anywhere, scale it how you liked. That’s what frameworks <em>used to be about</em>. Power in your hands. Deployment-agnostic. Infra-independent.</p>
<p>And then came Vercel.</p>
<p>At first, it looked like a natural evolution, Vercel backing Next.js brought with it resources, momentum, polished DX, and incredible iteration speed. What most people didn’t fully realize at the time was that Vercel wasn’t just <strong>supporting</strong> Next.js. They were <strong>transforming</strong> it.</p>
<p>Today, Next.js isn’t just a framework anymore.<br>It’s a gateway to an entire platform experience, arguably one of the smartest product-led growth plays we’ve seen in the devtools space.</p>
<h2 id="every-new-feature-is-an-infra-feature">Every New “Feature” is an Infra Feature</h2>
<p>And that’s where the magic really kicks in.</p>
<p>Look at the last few years of Next.js evolution: App Router, Middleware, ISR, Image Optimization, Edge Functions; every single one of these is more than just a code-level feature. These are <strong>infrastructure-powered capabilities</strong> wrapped in a developer-friendly API.</p>
<p>They look and feel like framework features, but underneath, they’re tightly coupled with execution models, caching layers, and distributed systems that Vercel already built for you.</p>
<p>Try to use them outside of Vercel, and it becomes clear: you’re not just dealing with functions and files anymore, you’re dealing with behaviors, caching semantics, invalidation logic, and routing models that were designed to shine <em>on their infra</em>. Self-hosting these features is possible, sure, but it comes with an operational tax. And that’s the brilliance: the deeper you go into Next.js, the more sense Vercel’s platform makes.</p>
<h2 id="youre-not-just-building-with-nextjs-youre-buying-into-a-system">You’re Not Just Building with Next.js, You’re Buying Into a System</h2>
<p>What Vercel pulled off is impressive.</p>
<p>They didn’t just build a hosting provider. They built <strong>an entire deployment-native framework</strong>, one where the best parts come alive only when used together with their infra stack.</p>
<p>Middleware? That’s not just a simple code hook, that’s an edge-native function runtime optimized for Vercel’s network.<br>ISR? That’s not just a static regeneration trick, that’s backed by intelligent caching, revalidation orchestration, and routing behavior baked into their platform.<br>Image optimization? That’s not just a utility, it’s directly tied to their CDN and storage layer.</p>
<p>You’re not just writing a Next.js app. You’re building for the Vercel runtime, whether you realize it or not. And that’s kind of genius, because it means the more you use the framework, the better the platform feels.</p>
<h2 id="and-yes-you-pay-for-that">And Yes, You Pay for That</h2>
<p>Of course, there’s a cost, but it’s not just a cost, it’s a <strong>business model</strong>.</p>
<p>On Vercel, you don’t pay for servers. You pay for outcomes:</p>
<ul><li>Edge function executions  </li><li>CDN bandwidth  </li><li>Image transformations  </li><li>Region-aware cold starts  </li><li>Caching, routing, invalidation, orchestration  </li></ul>
<p>You’re not renting a box. You’re paying for the execution of every micro-primitive that enables your app to scale instantly across the globe.</p>
<p>And as your app grows, the platform grows with you, and yes, the bill grows too. That’s the tradeoff: frictionless scaling in exchange for platform-native architecture. For startups it feels magical. For enterprises, it’s predictable. For Vercel, it’s recurring revenue, and it’s brilliant.</p>
<h2 id="can-you-leave-technically-yes">Can You Leave? Technically, Yes.</h2>
<p>But that’s the kicker, by the time you consider moving away, you’ve already adopted half a dozen features that weren’t just framework-level decisions.</p>
<p>They were <strong>infra-coupled</strong> by design.</p>
<p>Leaving means rebuilding those pieces yourself:</p>
<ul><li>Your own ISR cache and invalidation system  </li><li>Your own CDN-backed image optimizer  </li><li>Your own edge runtime logic and routing layer  </li><li>Your own regional cold-start mitigation  </li></ul>
<p>At that point, you’re not just migrating. You’re rewriting. And it’s not just effort — it’s architectural debt. Most teams choose to stay, and honestly? That’s what makes it such a masterclass in developer-led growth.</p>
<h2 id="verdict">Verdict</h2>
<p>Vercel didn’t just build a hosting platform. They built <strong>an ecosystem</strong>.</p>
<p>They blurred the line between framework and infrastructure so cleanly that most developers didn’t even notice it happening.<br>They took something as technical as deployment and turned it into a seamless, scalable extension of your codebase.<br>They gave Next.js a superpower, and in the process, made their own platform indispensable.</p>
<hr>
<p><strong>Next.js didn’t just evolve. It became the front door to Vercel’s infrastructure.</strong><br>And if we’re being honest, it might be the most effective vendor strategy the frontend world has ever seen.</p>
<p><strong>A framework disguised as a product disguised as a platform.</strong><br>That’s not a trap. That’s marketing brilliance.</p>]]></content:encoded>
    <media:content url="https://www.arnnvv.com/og.png" type="image/png" medium="image" />
  </item>
  <item>
    <title>The Engineering of Trust</title>
    <link>https://www.arnnvv.com/my-writings/the-engineering-of-trust/</link>
    <guid isPermaLink="true">https://www.arnnvv.com/my-writings/the-engineering-of-trust/</guid>
    <pubDate>Wed, 01 Oct 2025 09:25:09 GMT</pubDate>
    <description>We blindly entrust our most sensitive data to protocols most of us can’t explain.</description>
    <content:encoded><![CDATA[<p>I’ll admit it: I’m a cryptography snob. My password manager is a command center. I’d slap two-factor authentication on my coffee machine if I could. I guard private keys like nuclear launch codes. Yet I carried a professional shame: I trusted the little padlock icon. I preached Signal’s “end-to-end encryption” without understanding the mechanics behind it. I knew the what, not the how. For someone who lives by logic, “it’s magic” is never an answer.</p>
<p>We blindly entrust our most sensitive data to protocols most of us can’t explain. What is the actual engineering chain that guarantees secrecy when anyone could be listening? When you peek under the hood, the reality is stranger, smarter, and far more elegant than any marketing slogan.</p>
<p>This is a deep dive into that chain. We’ll dissect the engines powering modern secure messaging, from the math that creates a secret in plain sight to the engineering that closes every loophole.</p>
<h3 id="secret-in-public">Secret in Public</h3>
<p>Two parties, Alice and Bob, require private communication. Their only channel is insecure; an eavesdropper, Eve, sees everything. If Alice encrypts her message, how does she safely deliver the key? This is the canonical problem of cryptography: <strong>agree on a secret over a public channel</strong>.</p>
<p>The solution lies in <strong>one-way functions</strong>: operations that are easy to compute but computationally infeasible to reverse. A specific subset you may know are <strong>trapdoor functions</strong>, which are reversible only with a secret piece of information. The key exchanges we’ll dissect are even more pure, relying on one-way functions with no trapdoor.</p>
<p>The classic paint analogy illustrates this perfectly:</p>
<ol><li>Alice and Bob agree on a public color: <strong>Yellow</strong>.</li><li>Alice secretly picks <strong>Red</strong>, mixes it with Yellow to get <strong>Orange</strong>, and sends it publicly.</li><li>Bob secretly picks <strong>Blue</strong>, mixes it with Yellow to get <strong>Green</strong>, and sends it publicly.</li><li>Alice mixes Green + her secret Red → <strong>Brown</strong>. Bob mixes Orange + his secret Blue → <strong>Brown</strong>.</li></ol>
<p>Eve sees only the public mixes. She cannot deduce the secret components. They’ve established a shared secret <strong>in plain sight</strong>.</p>
<h3 id="the-first-engine-diffie-hellman">The First Engine: Diffie Hellman</h3>
<p>Diffie Hellman was the first algorithm to turn this paint-mixing idea into a machine of numbers.</p>
<ul><li><strong>The One-Way Function:</strong> Modular Exponentiation (<code>Result = base ^ exponent mod prime</code>). Reversing this is the <strong>Discrete Logarithm Problem (DLP)</strong> computationally intractable for large numbers.</li></ul>
<p>Let’s walk through the math with small numbers.</p>
<blockquote><ol><li><strong>Public Agreement:</strong> Alice and Bob agree on a prime <code>p = 23</code> and a base <code>g = 5</code>. Eve knows these.</li><li><strong>Secret Keys:</strong><ul><li>Alice secretly chooses <code>a = 4</code>.</li><li>Bob secretly chooses <code>b = 3</code>.</li></ul></li><li><strong>Public Exchange:</strong><ul><li>Alice calculates <code>5^4 mod 23</code>, which results in a remainder of <strong>4</strong>. She sends <code>A = 4</code> to Bob.</li><li>Bob calculates <code>5^3 mod 23</code>, which results in a remainder of <strong>10</strong>. He sends <code>B = 10</code> to Alice.</li></ul></li><li><strong>The Shared Secret:</strong><ul><li>Alice computes <code>10^4 mod 23</code>, which results in a remainder of <strong>18</strong>.</li><li>Bob computes <code>4^3 mod 23</code>, which also results in a remainder of <strong>18</strong>. In real systems, these numbers scale to hundreds of digits, ensuring security. But the computational cost is high.</li></ul></li></ol></blockquote>
<h3 id="the-modern-engine-the-dance-of-elliptic-curves">The Modern Engine: The Dance of Elliptic Curves</h3>
<p>The demand for performance led us to <strong>Elliptic Curve Diffie-Hellman (ECDH)</strong>. They offer equivalent security to Diffie-Hellman with drastically smaller keys, making them faster and more efficient.</p>
<p>The one-way function is <strong>Scalar Multiplication (k*P)</strong>. Geometrically, this is visualized as “adding” a point <code>P</code> to itself <code>k</code> times along the curve according to specific rules.</p>
<figure><div><img src="https://d23s79tivgl8me.cloudfront.net/user/55496/ch4_elliptic_curve_plots_69126.png" alt="Geometric visualization of point addition on an elliptic curve." loading="lazy"></div><figcaption><em>The geometric rule for “adding” points: a line through P and Q intersects the curve at a third point. The reflection of that point across the x-axis is R, the result of P + Q.</em></figcaption></figure>
<ol><li><em><em>To get 2</em> P:</em>* Draw a line perfectly tangent to the curve at point <code>P</code>. This line will hit the curve at one other spot. Reflect that spot across the horizontal x-axis. <em>That</em> new point is <code>2P</code>.</li><li><strong>To get 3*P:</strong> Draw a straight line from our <em>original</em> point <code>P</code> through our new point <code>2P</code>. That line will hit the curve somewhere else. Flip <em>that</em> point across the axis, and you have <code>3P</code>.</li></ol>
<p>Given a starting point <code>P</code> and a final point <code>Q</code>, it’s impossible to deduce <code>k</code>. That’s the <strong>Elliptic Curve Discrete Logarithm Problem (ECDLP)</strong>. But how does a computer actually “draw tangent lines” to compute <code>k*P</code>? Let’s get concrete. The most intuitive implementation is a classic algorithm called <strong>Double-and-Add</strong>. It translates the scalar <code>k</code> into a series of point doublings and point additions, directly analogous to its binary representation.</p>
<p>For a secret key <code>k = 22</code> (binary <code>10110</code>), the computer does the following:</p>
<ul><li>It scans the bits of <code>k</code> from left to right.</li><li>For <strong>every</strong> bit, it performs a <strong>Point Double</strong>.</li><li>Only when the bit is a <strong>‘1’</strong>, it also performs a <strong>Point Add</strong>.</li></ul>
<p>The calculation for <code>22*P</code> would unroll like this:</p>
<div class="prose-table-wrap"><table><thead><tr><th class="align-left">Bit</th><th class="align-left">Operation</th><th class="align-left">Result</th></tr></thead><tbody><tr><td class="align-left"><strong>1</strong></td><td class="align-left">Double, then <strong>Add</strong> P</td><td class="align-left"><code>P</code></td></tr><tr><td class="align-left"><strong>0</strong></td><td class="align-left">Double</td><td class="align-left"><code>2P</code></td></tr><tr><td class="align-left"><strong>1</strong></td><td class="align-left">Double, then <strong>Add</strong> P</td><td class="align-left"><code>5P</code></td></tr><tr><td class="align-left"><strong>1</strong></td><td class="align-left">Double, then <strong>Add</strong> P</td><td class="align-left"><code>11P</code></td></tr><tr><td class="align-left"><strong>0</strong></td><td class="align-left">Double</td><td class="align-left"><code>22P</code></td></tr></tbody></table></div>
<p>This works beautifully. But that simple <code>if bit == '1'</code> check the very heart of the algorithm’s logic is a catastrophic vulnerability. Mathematical elegance often introduces subtle, practical flaws.</p>
<h3 id="side-channel-attacks">Side-Channel Attacks</h3>
<p>The mathematics of ECDH are strong, but the hardware it runs on can leak secrets. This is where the physical world brutally intrudes on abstract math. An attacker doesn’t need to break the ECDLP; they just need to listen to your processor.</p>
<p>The Double-and-Add algorithm has a fatal flaw: its execution path depends on the secret data.</p>
<ul><li>If a bit of your key is <strong>0</strong>, the CPU performs <em>one</em> operation (Double).</li><li>If a bit of your key is <strong>1</strong>, the CPU performs <em>two</em> operations (Double and Add).</li></ul>
<p>This difference, no matter how small, is a signal. An attacker can measure this signal to extract your key, bit by bit.</p>
<ul><li><strong>Timing Attacks:</strong> A <code>Point Add</code> takes a few hundred extra nanoseconds. By precisely measuring the total time of the scalar multiplication over thousands of attempts, an attacker can statistically determine how many ’1’s are in your key. A more direct attack measures the time per loop: a short loop reveals a ‘0’, a long loop reveals a ‘1’. One microsecond can leak everything.</li><li><strong>Power Analysis:</strong> This is even more devastating. Different CPU instructions consume different amounts of power. By attaching an oscilloscope to the device’s power line, an attacker can get a “power trace” of the computation. They can literally <em>see</em> the difference: a small power blip for a Double, and a larger, distinct power blip for a Double followed by an Add. The pattern of blips on their screen is a direct readout of your secret key.</li></ul>
<h3 id="the-hardened-engine-montgomery-ladder">The Hardened Engine: Montgomery Ladder</h3>
<p>The solution is not stronger math, but smarter engineering: <strong>constant-time algorithms</strong>. The algorithm’s operations must be completely independent of the secret data’s value. The gold standard here is the <strong>Montgomery Ladder</strong>.</p>
<ul><li><strong>Mechanism:</strong> The genius of the Montgomery Ladder is its elimination of the data-dependent <code>if</code> branch. Instead of one running total, it cleverly maintains two points, <code>R0</code> and <code>R1</code>. In every single loop iteration, it performs <strong>exactly one point addition and one point doubling</strong>, regardless of whether the secret key bit is a 0 or a 1.</li></ul>
<p>At first glance, this looks like sleight of hand. How does doing a fixed dance of one add and one double per bit still give the right result, k*P? The answer is the ladder’s core invariant.</p>
<p><strong>The Secret of the Montgomery Ladder</strong>: <code>The Invariant</code></p>
<p>The algorithm preserves a single relationship across all iterations:</p>
<p><strong>R1 - R0 = P</strong></p>
<p>In every branch, the invariant survives. That invariant is what ties the operations back to correct scalar multiplication.</p>
<p>Let’s look at its logic:</p>
<pre><code>Initialize:
    R0 = Point at Infinity
    R1 = P
    // Invariant: R1 - R0 = P - O = P

For each bit in the secret key k:
  if bit == 0:
    R1 = R0 + R1  // One Add
    R0 = 2 * R0   // One Double
    // Check invariant:
    // (R0 + R1) - (2*R0) = R1 - R0 = P
  else: // bit == 1
    R0 = R0 + R1  // One Add
    R1 = 2 * R1   // One Double
    // Check invariant:
    // (2*R1) - (R0 + R1) = R1 - R0 = P
</code></pre>
<h3 id="how-the-invariant-builds-scalar-multiplication">How the Invariant Builds Scalar Multiplication</h3>
<p>Track <code>R0</code> as the bits of k are processed from left to right. After i bits, R0 equals <code>kᵢ*P</code>, where kᵢ is the integer represented by those i bits.</p>
<p>Example: k = 22 (binary <code>10110</code>). Walk through the ladder:</p>
<p>Start: k = (empty), R0 = O = 0*P, R1 = P</p>
<div class="prose-table-wrap"><table><thead><tr><th>Bit</th><th>Before (R0, R1)</th><th>Operation</th><th>After (R0, R1)</th><th>R0 Value</th><th>Bits Processed</th></tr></thead><tbody><tr><td>1</td><td>(O, P)</td><td>bit=1: R0=O+P, R1=2*P</td><td>(P, 2P)</td><td><code>P</code></td><td>1</td></tr><tr><td>0</td><td>(P, 2P)</td><td>bit=0: R1=P+2P, R0=2*P</td><td>(2P, 3P)</td><td><code>2P</code></td><td>10</td></tr><tr><td>1</td><td>(2P, 3P)</td><td>bit=1: R0=2P+3P, R1=2*3P</td><td>(5P, 6P)</td><td><code>5P</code></td><td>101</td></tr><tr><td>1</td><td>(5P, 6P)</td><td>bit=1: R0=5P+6P, R1=2*6P</td><td>(11P, 12P)</td><td><code>11P</code></td><td>1011</td></tr><tr><td>0</td><td>(11P, 12P)</td><td>bit=0: R1=11P+12P, R0=2*11P</td><td>(22P, 23P)</td><td><code>22P</code></td><td>10110</td></tr></tbody></table></div>
<p>Result: After processing all the bits of k, the register R0 holds the final result: <em>22P</em>.</p>
<p>The workload is <code>identical</code> in both branches. The only difference is which register receives the result, a detail that is invisible to a power or timing attacker. The “long loop” vs “short loop” signal is gone. The power trace becomes a monotonous, repeating pattern, sealing the leak.</p>
<h3 id="the-special-sauce-for-x25519">The Special Sauce for X25519</h3>
<p>On a generic curve, the point addition <code>R0+R1</code> and doubling <code>2*R0</code> are moderately expensive. Critically, to add two different points (x1, y1) and (x2, y2), you need all four coordinates.</p>
<p>However, on a Montgomery Curve, the specific operations used in the ladder (differential add and double) can be performed using <strong>only the x-coordinates</strong> of the points. You don’t need the y-coordinates at all during the loop. This makes the calculations drastically <em>faster</em> and <em>simpler</em>, requiring less code and <strong>fewer CPU cycles</strong>. The curve shape itself is purpose built to make its security algorithm, the Montgomery Ladder, run as fast as humanly possible.</p>
<p>This constant-time ladder is the core of X25519, but the protocol’s hardened design goes even further, making it an opinionated system engineered from the lessons of past failures.</p>
<ul><li><strong>No Input Validation</strong>: Unlike older curves that require slow, error-prone public-key validation, X25519’s math accepts any 32-byte input, neutralizing classical “Invalid Curve Attacks.” It computes a valid shared secret regardless of the input, but sloppy protocol use can still shoot you in the foot.</li><li><strong>Scalar Clamping</strong>: Before use, the secret scalar k is passed through a clamping function. This is a crucial, non-optional safety measure. It forces specific bits of the key into a known state (<code>k[0] &amp;= 248; k[31] &amp;= 127; k[31] |= 64</code>). This mitigates a variety of sophisticated attacks, including small subgroup attacks, and ensures the Montgomery Ladder operates on a correctly formatted scalar.</li></ul>
<p>X25519’s design philosophy is to make the secure path the only path.</p>
<h3 id="beyond-secrecy-the-problem-of-integrity">Beyond Secrecy: The Problem of Integrity</h3>
<p>The handshake is done. We have a key. But here’s the catch: the beautiful, heavy mathematics of X25519 are for building bridges, not for moving traffic. They are brilliant for establishing a shared secret but far too slow to encrypt every single message. Imagine running that entire elliptic curve dance for every text you send your phone’s battery would die by lunch.</p>
<p>So, we use a hybrid approach. The heavy math (asymmetric) is run once to derive a lightweight, lightning-fast <strong>symmetric key</strong>. This key is then used with a symmetric cipher like AES to do the actual work of encrypting data.</p>
<p>This is where a new, more insidious class of attacks emerges. We’ve solved the secrecy problem, but we’ve created an integrity problem. Early encryption methods locked the box but failed to put a seal on it.</p>
<p>Let’s look at a classic, now-deprecated mode called <strong>Cipher Block Chaining</strong> (CBC). The core problem it tries to solve is that if you encrypt the same block of data twice with the same key, you’ll get the exact same encrypted block. This leaks information. Imagine an image where large areas are the same color; in an unchained mode, those would create visible, repeating patterns in the ciphertext.</p>
<p>CBC’s solution is to chain the encryption of each block to the one before it. It does this using the <strong>XOR</strong> operation. To encrypt a block of plaintext, you first XOR it with the previous block of ciphertext.</p>
<p>Why XOR? Because it’s a perfect, reversible “scrambler.” XOR is a bitwise operation that acts like a toggle switch. If you XOR a value A with a key K, you get a scrambled result C. If you XOR C with the exact same key K again, you get your original value A back <code>(A ⊕ K ⊕ K = A)</code>.</p>
<p>In CBC, the previous ciphertext block acts as that single-use, per-block “key.” It scrambles the plaintext before it even enters the main encryption engine. This ensures that even if you have two identical plaintext blocks, <code>P1</code> and <code>P2</code>, they will be XORed with two different ciphertext blocks (<code>C0</code> and <code>C1</code>) before being encrypted. The inputs to the AES engine will be different, so the final outputs (<code>C1</code> and <code>C2</code>) will also be completely different. It’s a clever way to ensure no patterns are repeated, but as we’ll see, this very mechanism is also its greatest weakness.</p>
<p>This very mechanism creates a fatal flaw, it makes the ciphertext malleable. An attacker can tamper with the encrypted message and produce predictable changes in the decrypted text all without ever knowing the key.</p>
<p>Let’s stage the attack. You are sending an encrypted bank transfer instruction:
<code>{"from":"Alice","to":"Eve","amount":"100"}</code></p>
<p>Eve intercepts the ciphertext. She can’t read it, but she knows the format. She wants to change that “100” to “900”. Here is the precise, devastatingly simple mechanic she uses:</p>
<p><code>Plaintext_Block[i] = Decrypt(Key, Ciphertext_Block[i]) ⊕ Ciphertext_Block[i-1]</code></p>
<p>The attacker cannot touch the <code>Decrypt(Key, ...)</code> part, that’s protected by the secret key. But she has complete control over the previous ciphertext block <code>Ciphertext_Block[i-1]</code>.</p>
<p>She knows that whatever change she makes to <code>Ciphertext_Block[i-1]</code> will be XORed directly against the output of the decryption, right before it becomes the final plaintext.</p>
<p>So, she crafts a <strong>Tampering Mask</strong>, she then modifies the ciphertext block that comes just before the block containing the amount:
<code>Tampered_Ciphertext_Block[i-1] = Original_Ciphertext_Block[i-1] ⊕ Mask</code></p>
<p>When the receiver decrypts, this happens:</p>
<blockquote><p><code>Final_Plaintext = Decrypt(Key, Ciphertext_Block[i]) ⊕ Tampered_Ciphertext_Block[i-1]</code>
<code>Final_Plaintext = Decrypt(...) ⊕ (Original_Ciphertext_Block[i-1] ⊕ Mask)</code>
<code>Final_Plaintext = (Decrypt(...) ⊕ Original_Ciphertext_Block[i-1]) ⊕ Mask</code>
<code>Final_Plaintext = Original_Plaintext ⊕ Mask</code></p></blockquote>
<p>The result? The receiver’s machine decrypts the message perfectly, without any errors. But the plaintext it sees is <code>{"from":"Alice","to":"Eve","amount":"900"}</code>. Integrity has been silently destroyed.</p>
<p>This is why modern systems use <strong>Authenticated Encryption with Associated Data</strong> (AEAD), with AES-GCM being the gold standard. AEAD provides a non-negotiable two-for-one deal: secrecy and integrity.</p>
<p>Here’s how it neutralizes the attack. An AEAD cipher produces two outputs:</p>
<blockquote><ol><li><strong>The Ciphertext</strong>: The encrypted, unreadable data.</li><li><strong>An Authentication Tag</strong>: A small, fixed-size cryptographic checksum. Think of it as an unforgeable, tamper-evident seal.</li></ol></blockquote>
<p>If an attacker alters even a single bit of the ciphertext, the seal is broken. When the recipient tries to verify the tag against the tampered message, the check will fail, and the software will immediately discard the message. No more silent manipulations.</p>
<p>But how does this tag, this short string of bytes, guarantee the integrity of the entire message, from the first byte to the last?</p>
<p>It does this through cryptographic accumulation. The tag is not just a hash of the last block. It’s the final state of a process that has <strong>sequentially mixed in every single block of the ciphertext</strong>. </p>
<p>Imagine a mixing bowl:</p>
<blockquote><ol><li>The process starts with a secret value derived from the key, let’s call it the Authentication Key <code>H</code>.</li><li>The first block of ciphertext is thrown into the bowl and mixed with <code>H</code>. The result is a new, scrambled value.</li><li>The second block of ciphertext is thrown into the bowl, which still contains the result from previous step. It’s all mixed together again with <code>H</code>.</li><li>This continues for every block. The state of the mixing bowl at any point is a function of everything that came before it. A change to the first block creates a different result in the next one, which then snowballs, causing every subsequent state to be completely different.</li></ol></blockquote>
<p>By the end, the value in the mixing bowl is a holistic representation of the entire, ordered sequence of ciphertext blocks. This final value is then encrypted one last time to produce the tag.</p>
<figure><div><img src="https://ars.els-cdn.com/content/image/3-s2.0-B978012803843700003X-f03-04-9780128038437.jpg" alt="Block ciphers." loading="lazy"></div></figure>
<p>When the receiver gets the message, they perform the exact same mixing ritual on the ciphertext they received. If their final tag matches the one the sender sent, it is cryptographic proof that the message is authentic and has not been altered in any way.</p>
<p>With AEAD, the box is not only locked; it has a seal that is intrinsically tied to the exact contents of the box. Break the seal, and everyone knows. This is why modern secure messaging relies on AEAD: it protects both the secret and ensures it hasn’t been messed with along the way.</p>
<h3 id="forward-secrecy">Forward Secrecy</h3>
<p>Our system is secure. But what if a key is compromised years from now, could someone dig up old conversations and read them? That’s exactly the kind of problem the <strong>Double Ratchet Algorithm</strong>, by <strong>Signal</strong>, was built to solve. It keeps past messages safe and even lets the system recover after a compromise by rotating keys with every single message.</p>
<p>That’s a rabbit hole for another day I’ll cover it properly in a future post. But if you want to peek under the hood right now, the <a href="https://signal.org/docs/specifications/doubleratchet" target="_blank" rel="noopener noreferrer">signal spec</a> is the best place to start.</p>]]></content:encoded>
    <media:content url="https://www.arnnvv.com/og.png" type="image/png" medium="image" />
  </item>
  <item>
    <title>Global checkout in 10 minutes? It's easier than you think</title>
    <link>https://www.arnnvv.com/my-writings/global-checkout-in-10-minutes-its-easier-than-you-think/</link>
    <guid isPermaLink="true">https://www.arnnvv.com/my-writings/global-checkout-in-10-minutes-its-easier-than-you-think/</guid>
    <pubDate>Mon, 29 Sep 2025 16:34:02 GMT</pubDate>
    <description>The whole thing boils down to a pattern that feels less like a traditional payment integration and more like implementing “Sign in with Google.”</description>
    <content:encoded><![CDATA[<p>Last week I spent three hours debugging why our Stripe webhook signatures weren’t validating in staging. It turned out to be a classic: a miniscule difference in how the JSON body was being stringified between my local environment and the Vercel Edge runtime. This is my life now. It’s a recurring nightmare of subtle cryptographic and environment-specific bugs that has absolutely nothing to do with building features people actually pay for.</p>
<p>So when I head of <a href="https://dodopayments.com" target="_blank" rel="noopener noreferrer">Dodo Payments</a>, I was skeptical. <em>Another payments SDK</em>, I groaned. But I had a project to integrate, so I blocked off my afternoon.</p>
<p>I finished it before my second coffee.</p>
<p>The whole thing boils down to a pattern that feels less like a traditional payment integration and more like implementing “Sign in with Google.” And that’s the point. It’s basically OAuth for money.</p>
<hr>
<h2 id="the-dance-how-it-works">The Dance: How it Works</h2>
<p>If you’ve done any social auth, you already know this pattern:</p>
<p><strong>1. Your Server -&gt; Dodo:</strong> Your server calls its own endpoint to say, “Hey, I need a checkout link for this product.”</p>
<p><strong>2. Dodo -&gt; User:</strong> Dodo gives you back a secure, one-time URL. You redirect the user to it.</p>
<p><strong>3. User -&gt; Dodo:</strong> The user fills out their payment info on a page hosted by Dodo. Your app never sees a credit card number, so PCI compliance is not your problem.</p>
<p><strong>4. Dodo -&gt; Your Webhook:</strong> Dodo pings your server to say, “Hey, that user paid. Or they didn’t. Here’s the result.”</p>
<p>This model keeps the sensitive stuff off your servers and makes the integration shockingly simple.</p>
<hr>
<h2 id="1-configure-in-the-dashboard">1: Configure in the Dashboard</h2>
<p>First, get your API keys from the Dodo dashboard. While you’re there, create a “Product” and set your webhook URL (e.g., <code>https://yourapp.com/api/webhooks</code>). This is the “few clicks” part no code required.</p>
<h2 id="2-create-a-checkout-endpoint">2: Create a Checkout Endpoint</h2>
<p>Add a single API route to your server. The Dodo adapter exposes a Checkout function that you initialize with your API key. This five-line file becomes a complete, secure endpoint that securely communicates with Dodo’s API to generate a one-time checkout URL.</p>
<pre><code class="language-typescript">import { Checkout } from "@dodopayments/nextjs";
import { appConfig } from "#/lib/config";

export const POST = Checkout({
  bearerToken: appConfig.dodo.apiKey,
  returnUrl: appConfig.dodo.returnUrl,
  environment: appConfig.dodo.environment,
  type: "session",
});
</code></pre>
<h2 id="3-trigger-the-flow">3: Trigger the Flow</h2>
<p>From your frontend, make a request to that endpoint. You can pass custom metadata like your internal <code>orderId</code> or <code>userId</code>. This is the key to connecting the payment back to your application’s state. The adapter handles the redirect automatically.</p>
<p>Here’s the code:</p>
<pre><code class="language-typescript">export async function createCheckoutAction(formData: FormData) {
  const productId = formData.get("productId") as string;
  const orderId = `order_${crypto.randomUUID()}`;

  await db.createOrder({ id: orderId, status: 'pending' });

  const response = await fetch(`${process.env.APP_BASE_URL}/api/dodo/checkout`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      product_cart: [{ product_id: process.env.DODO_PRODUCT_ID, quantity: 1 }],
      metadata: {
        internalOrderId: orderId,
        internalProductId: productId,
      },
    }),
  });

  const { checkout_url } = await response.json();
  return { checkout_url };
}
</code></pre>
<p>The crucial part is the <code>metadata</code>. I create my own order record in my database before sending the user to Dodo, and I pass my internal orderId along. When Dodo calls my webhook later, it will include this metadata, so I know exactly which order to update.</p>
<h2 id="4-handle-the-webhook">4: Handle the Webhook</h2>
<p>This is where I usually lose my mind. Verifying cryptographic signatures, checking timestamps to prevent replay attacks… it’s a minefield.</p>
<p>I instinctively started writing a function to parse the <code>webhook-signature</code> header, hash the body with my secret key, and do a timing-safe comparison. I spent 20 minutes on it before realizing the adapter does this for me. Again. Sometimes reading the docs first is actually a good idea.</p>
<p>The Dodo adapter does all that for you. You just provide your business logic:</p>
<pre><code class="language-typescript">import { Webhooks } from "@dodopayments/nextjs";
import { appConfig } from "#/lib/config";
import { db } from "#/lib/db";

export const POST = async (req) =&gt; {
  const handler = Webhooks({
    webhookKey: appConfig.dodo.webhookSecret,

    onPaymentSucceeded: async (payload) =&gt; {
      const internalOrderId = payload.data.metadata?.internalOrderId;
      if (internalOrderId) {
        await db.query(
          "UPDATE orders SET status = 'succeeded' WHERE id = $1",
          [internalOrderId]
        );
        console.log(`Order ${internalOrderId} marked as successful.`);
      }
    },

    onPaymentFailed: async (payload) =&gt; {
      // ... update DB to 'failed'
    },
  });

  return handler(req);
};
</code></pre>
<p>Here’s a simple sketch of that flow:</p>
<figure><div><img src="https://i.ibb.co/wrrCNM8T/dodo.jpg" alt="Dodo Checkout Flow" loading="lazy"></div></figure>
<hr>
<p>The model is dead simple: your app never touches card numbers. You redirect the user out, Dodo runs the payment, and they call your webhook with the result. If this smells familiar, it should—it’s basically OAuth but for money. Think of “Log in with Google”: you hit your own server, it redirects to Google, user does the auth, Google calls back your redirect URI, and you finish up. Same pattern here, just applied to payments.</p>
<p>The best part is you can get from zero to working checkout in minutes, not days. Which, if you’ve ever had to do payment integration the “traditional” way, feels like cheating.</p>]]></content:encoded>
    <media:content url="https://www.arnnvv.com/og.png" type="image/png" medium="image" />
  </item>
</channel>
</rss>