<?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>DEV Community: Cory Dabrowski</title>
    <description>The latest articles on DEV Community by Cory Dabrowski (@chaps_grid).</description>
    <link>https://dev.to/chaps_grid</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3978676%2Fbd52fa35-a057-47b3-a438-8cd85290657f.png</url>
      <title>DEV Community: Cory Dabrowski</title>
      <link>https://dev.to/chaps_grid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chaps_grid"/>
    <language>en</language>
    <item>
      <title>Certifying something on-chain without revealing it: privacy attestation on Midnight</title>
      <dc:creator>Cory Dabrowski</dc:creator>
      <pubDate>Sat, 27 Jun 2026 01:03:22 +0000</pubDate>
      <link>https://dev.to/chaps_grid/certifying-something-on-chain-without-revealing-it-privacy-attestation-on-midnight-1j4c</link>
      <guid>https://dev.to/chaps_grid/certifying-something-on-chain-without-revealing-it-privacy-attestation-on-midnight-1j4c</guid>
      <description>&lt;p&gt;I built &lt;a href="https://github.com/CjDabrow/grid-audit-midnight" rel="noopener noreferrer"&gt;Grid Audit&lt;/a&gt;, a tool that reviews Midnight code and then lets you certify that review on-chain. The thought behind this was to help assist people with reviewing what code they have created. But then my original thought grew: how do you record on a public ledger that an audit happened, by an authorized auditor, over a specific report, without publishing the report and without exposing the auditor's key?&lt;/p&gt;

&lt;p&gt;That turns out to be a general Midnight pattern, and this post walks through the small contract that does it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You want a public, tamper-evident receipt that says "this exact thing was reviewed and signed off." But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The report itself is private. It should never touch the chain.&lt;/li&gt;
&lt;li&gt;Only the real auditor should be able to publish a receipt. No impersonation.&lt;/li&gt;
&lt;li&gt;Anyone should be able to verify a receipt later, without learning the report or the auditor's secret.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a transparent chain you would have to publish the report or trust an off-chain server. On Midnight you can do it with two primitives: witnesses and commitments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The contract&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is the whole registry. It is small, which is the point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;pragma&lt;/span&gt; &lt;span class="n"&gt;language_version&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.23&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CompactStandardLibrary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Public ledger state.&lt;/span&gt;
&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;ownerCommitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// H(auditorSecret), set once at deploy&lt;/span&gt;
&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;receipts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// receiptId -&amp;gt; H(reportFingerprint)&lt;/span&gt;
&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;published&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Private inputs (witnesses). Supplied by the prover, never written to the ledger in the clear.&lt;/span&gt;
&lt;span class="n"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;auditorSecret&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;reportFingerprint&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Bind the registry to the auditor's secret at deploy. Stores only the commitment, never the secret.&lt;/span&gt;
&lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ownerCommitment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;auditorSecret&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;publishReceipt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;receiptId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Access control: re-derive H(secret) in-circuit and require it to match the commitment.&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;auditorSecret&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ownerCommitment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"not the auditor"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;receipts&lt;/span&gt;&lt;span class="nf"&gt;.member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;receiptId&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="s"&gt;"receipt already exists"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Only the commitment to the private report fingerprint is stored.&lt;/span&gt;
  &lt;span class="n"&gt;receipts&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;receiptId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reportFingerprint&lt;/span&gt;&lt;span class="p"&gt;())));&lt;/span&gt;
  &lt;span class="n"&gt;published&lt;/span&gt;&lt;span class="nf"&gt;.increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pattern 1: in-circuit secret-knowledge auth (not &lt;code&gt;ownPublicKey()&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The access control here is the important bit. At deploy, the constructor stores &lt;code&gt;ownerCommitment = H(auditorSecret)&lt;/code&gt;. The secret stays in the auditor's wallet. To publish, &lt;code&gt;publishReceipt&lt;/code&gt; re-derives &lt;code&gt;H(auditorSecret())&lt;/code&gt; from the witness inside the circuit and asserts it equals the stored commitment.&lt;/p&gt;

&lt;p&gt;The proof convinces the chain that the caller knows the secret, without the secret ever being disclosed. An observer sees only the commitment and the proof, never the secret, so nobody can impersonate the auditor.&lt;/p&gt;

&lt;p&gt;This matters because the obvious habit from other chains, gating on the caller's public key, does not work on Midnight. &lt;code&gt;ownPublicKey()&lt;/code&gt; is witness-sourced, so it is not a trusted caller identity and can be spoofed. The correct pattern is exactly this: make the caller prove knowledge of a secret in-circuit against an on-chain commitment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pattern 2: a commitment to a private value&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The report never goes on-chain. The auditor computes a &lt;code&gt;reportFingerprint&lt;/code&gt; (a hash of the report) locally, and the circuit stores only &lt;code&gt;H(reportFingerprint)&lt;/code&gt; as the receipt value. Later, anyone holding the report can recompute the fingerprint, hash it, and check it against the stored commitment, proving the receipt matches that exact report. The chain learns that a report was certified, not what it said.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The one assumption that makes this safe&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;persistentHash&lt;/code&gt; only hides inputs that are hard to guess. This contract is safe because &lt;code&gt;auditorSecret&lt;/code&gt; and &lt;code&gt;reportFingerprint&lt;/code&gt; are high-entropy: a random 32-byte secret and a hash. If you tried this same pattern with a low-entropy input, say a four-digit code or a yes/no value, the published hash would be brute-forceable and the privacy would evaporate. For low-entropy values you need a commitment with a fresh secret salt instead of a bare hash. Here the inputs are already high-entropy, so a plain hash is fine. Knowing which case you are in is the whole game.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you end up with&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A public registry where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only the holder of the auditor secret can publish, proven in zero knowledge.&lt;/li&gt;
&lt;li&gt;Each receipt commits to a private report without revealing it.&lt;/li&gt;
&lt;li&gt;Anyone can verify a receipt against a report they hold.&lt;/li&gt;
&lt;li&gt;The auditor's secret and the report contents never touch the chain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Wrapping up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is a small contract, but it is a reusable shape: prove authorization by secret knowledge, and publish commitments instead of data. Any time you want an on-chain record of something private, this is the pattern to reach for.&lt;/p&gt;

&lt;p&gt;The full project is here: &lt;a href="https://github.com/CjDabrow/grid-audit-midnight" rel="noopener noreferrer"&gt;Grid Audit on GitHub&lt;/a&gt;. It pairs this registry with a reviewer that flags Midnight-specific privacy and security traps, so you can review your Compact code and then certify the result on-chain, privately.&lt;/p&gt;

</description>
      <category>midnightfordevs</category>
      <category>zeroknowledge</category>
      <category>privacy</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What actually stays private on Midnight! (A builder's mental model)</title>
      <dc:creator>Cory Dabrowski</dc:creator>
      <pubDate>Wed, 17 Jun 2026 04:12:55 +0000</pubDate>
      <link>https://dev.to/chaps_grid/what-actually-stays-private-on-midnight-a-builders-mental-model-59h1</link>
      <guid>https://dev.to/chaps_grid/what-actually-stays-private-on-midnight-a-builders-mental-model-59h1</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Coming from the XRPL, the challenge for me has been grasping what actually stays hidden on a privacy chain and how they acomplish that.  On a transparent ledger, you always know what is public, because everything is. On Midnight the answer is not obvious, and getting it wrong is exactly how supposedly private data leaks.&lt;/p&gt;

&lt;p&gt;Here is the mental model I use. Once it clicked, most of my privacy mistakes stopped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three places your data can live
&lt;/h2&gt;

&lt;p&gt;On Midnight, every value in a contract sits in one of three places, and the place decides who can see it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Witness data: private inputs that never leave the user's device.&lt;/li&gt;
&lt;li&gt;Ledger state: public, on chain, visible to everyone forever.&lt;/li&gt;
&lt;li&gt;Disclosed values: things you deliberately move from private to public with &lt;code&gt;disclose()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Get these three straight and most privacy bugs disappear.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Witnesses: the private inputs
&lt;/h2&gt;

&lt;p&gt;A witness is a value the contract receives from the user's local environment. It feeds the zero-knowledge proof, but it never goes on chain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import CompactStandardLibrary;

// A private input. The prover knows it. The chain never sees it.
witness getBalance(): Bytes&amp;lt;32&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key idea: a proof can depend on a witness without revealing it. A contract can prove "the balance is at least 100" while nobody learns the balance. And anything derived from a witness is also treated as private by the compiler, which matters in a minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Ledger: the public state
&lt;/h2&gt;

&lt;p&gt;Ledger fields are the opposite. They are stored on chain and readable by anyone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Public state. Everyone can read this, now and forever.
export ledger balance: Bytes&amp;lt;32&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a value reaches a ledger field, it is public. Full stop. There is no private ledger. So the only interesting question is how a private witness ever gets into a public ledger field. It does not, unless you say so.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. disclose(): the deliberate door
&lt;/h2&gt;

&lt;p&gt;Midnight will not let you move witness-derived data into public space by accident. If you try, the compiler stops you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit recordBalance(): [] {
  balance = getBalance();   // compiler error: undeclared disclosure
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have to open the door on purpose with &lt;code&gt;disclose()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit recordBalance(): [] {
  balance = disclose(getBalance());   // ok: you declared the disclosure
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;disclose()&lt;/code&gt; does not encrypt or protect anything. It is a declaration: I know this value came from private data, and I am choosing to make it public. That is the entire point. Disclosure is an explicit, auditable decision, not something that happens to you.&lt;/p&gt;

&lt;p&gt;This is the mental flip from transparent chains. There, everything is public unless you do extra work. Here, everything private stays private unless you deliberately disclose it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it gets people: the boundary is sticky
&lt;/h2&gt;

&lt;p&gt;The trap is that "derived from a witness" spreads. The compiler tracks it through arithmetic, type casts, function calls, even conditionals.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;witness getBalance(): Uint&amp;lt;64&amp;gt;;

export circuit balanceExceeds(n: Uint&amp;lt;64&amp;gt;): Boolean {
  return getBalance() &amp;gt; n;   // compiler error: the boolean still leaks the balance
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returning whether the balance exceeds &lt;code&gt;n&lt;/code&gt; is not safe just because you did not return the balance itself. The comparison result is derived from private data, so the compiler still makes you &lt;code&gt;disclose()&lt;/code&gt; it. That is the feature working. It forces you to notice that even a yes or no answer is a disclosure.&lt;/p&gt;

&lt;h2&gt;
  
  
  A subtler leak: disclosing is not the same as hiding
&lt;/h2&gt;

&lt;p&gt;Here is the part that bites even careful people. The compiler treats some standard library routines as safe to disclose. A commitment, &lt;code&gt;transientCommit&lt;/code&gt; or &lt;code&gt;persistentCommit&lt;/code&gt;, is treated as not containing witness data, so you can publish it without a &lt;code&gt;disclose()&lt;/code&gt; wrapper.&lt;/p&gt;

&lt;p&gt;But the compiler allowing it is not the same as it being secret. If you commit or hash a value that has only a few possible inputs, anyone can brute-force it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// looks safe, is not: a yes/no vote has only two possible hashes
recordedVote = disclose(persistentHash&amp;lt;Field&amp;gt;(localVote()));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An observer hashes 0 and 1 and instantly knows the vote. The fix is a commitment with fresh secret randomness:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;witness voteSalt(): Bytes&amp;lt;32&amp;gt;;   // fresh, secret, never reused

recordedVote = persistentCommit&amp;lt;Field&amp;gt;(localVote(), voteSalt());
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The salt is the real secret. Same value plus a fresh salt gives a different, unguessable output. The compiler will not verify the salt is actually fresh or secret. That part is on you.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist before you ship
&lt;/h2&gt;

&lt;p&gt;Before any circuit goes out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is it a witness, or derived from one? Then it is private, and the compiler will fight you if you leak it. Good.&lt;/li&gt;
&lt;li&gt;Does it land in a ledger field, or get returned from an exported circuit? Then it is public. Did you mean that?&lt;/li&gt;
&lt;li&gt;Is there a &lt;code&gt;disclose()&lt;/code&gt; in the path? Then stop and ask: is this value low-entropy or guessable? If so, use a commitment with a fresh salt, not a bare hash.&lt;/li&gt;
&lt;li&gt;Did I reuse a salt anywhere? Identical inputs make identical commitments, which links them. Fresh salt every time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the whole model. Three buckets, one deliberate door, and a hard look at anything going through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Privacy on Midnight is not magic, and it is not something you just assume happens.&lt;/p&gt;

&lt;p&gt;The way I look at it is simple.&lt;/p&gt;

&lt;p&gt;Midnight gives builders privacy by default, but there is still a door you choose to open when something needs to be revealed.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>blockchain</category>
      <category>privacy</category>
      <category>web3</category>
    </item>
    <item>
      <title>Private logic, public rails: Pairing Midnight's ZK privacy with XRPL settlement</title>
      <dc:creator>Cory Dabrowski</dc:creator>
      <pubDate>Thu, 11 Jun 2026 04:32:46 +0000</pubDate>
      <link>https://dev.to/chaps_grid/private-logic-public-rails-pairing-midnights-zk-privacy-with-xrpl-settlement-54p4</link>
      <guid>https://dev.to/chaps_grid/private-logic-public-rails-pairing-midnights-zk-privacy-with-xrpl-settlement-54p4</guid>
      <description>&lt;p&gt;Building on XRPL has taught me one thing fast: public rails are powerful, but not every part of the app should be public.&lt;/p&gt;

&lt;p&gt;I can put settlement, payments, tokens, escrows, and wallet flows on-chain all day. That part makes sense. But the second you are dealing with identity, eligibility, compliance checks, customer data, private business logic, or anything sensitive, you hit a wall. You do not want that sitting out in public forever.&lt;/p&gt;

&lt;p&gt;Public ledgers made trust programmable, but that transparency cuts both ways. On the XRP Ledger, every amount, address, and balance is out in the open. That is exactly what you want for settlement, and exactly what you cannot have for sensitive business logic.&lt;/p&gt;

&lt;p&gt;The argument of this post is simple:&lt;/p&gt;

&lt;p&gt;You should not have to choose between public settlement and private logic.&lt;/p&gt;

&lt;p&gt;Use Midnight for the private half.&lt;br&gt;
Use XRPL for the public half.&lt;br&gt;
That gives you both.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two halves of one problem
&lt;/h2&gt;

&lt;p&gt;Most on-chain apps are really dealing with two different problems at the same time.&lt;/p&gt;

&lt;p&gt;One side is settlement.&lt;/p&gt;

&lt;p&gt;That means moving value fast, cheap, final, and in a way people can audit.&lt;/p&gt;

&lt;p&gt;The other side is private logic.&lt;/p&gt;

&lt;p&gt;That means proving something is true without exposing all the data behind it.&lt;/p&gt;

&lt;p&gt;Most chains force you into one lane. Either everything is public, or you move to a privacy setup and lose some of the settlement rails, liquidity, and ecosystem you already have.&lt;/p&gt;

&lt;p&gt;I do not think it has to be that way.&lt;/p&gt;
&lt;h2&gt;
  
  
  What XRPL is great at
&lt;/h2&gt;

&lt;p&gt;XRPL is my home base, and the strength is obvious once you build on it.&lt;/p&gt;

&lt;p&gt;It is fast.&lt;br&gt;
The fees are tiny.&lt;br&gt;
Settlement is final.&lt;br&gt;
The native DEX is already there.&lt;br&gt;
Issued tokens are built into the ledger.&lt;br&gt;
Escrow, payment channels, trust lines, and wallet flows are all part of what makes XRPL useful for real apps.&lt;/p&gt;

&lt;p&gt;That is why I like building on it. You can move value without needing a bunch of extra layers just to do basic things.&lt;/p&gt;

&lt;p&gt;But XRPL does not give you privacy.&lt;/p&gt;

&lt;p&gt;Everything is public by design.&lt;/p&gt;

&lt;p&gt;For settlement, that is a feature.&lt;/p&gt;

&lt;p&gt;For customer data, business logic, compliance decisions, identity checks, or private financial data, that becomes a problem fast.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Midnight adds
&lt;/h2&gt;

&lt;p&gt;Midnight brings in the private side.&lt;/p&gt;

&lt;p&gt;Midnight is a zero-knowledge data-protection chain, built as a Cardano partner chain, where privacy is the default. Data only becomes public when you choose to disclose it.&lt;/p&gt;

&lt;p&gt;The important pieces are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero-knowledge proofs: prove something is true without showing the private data behind it.&lt;/li&gt;
&lt;li&gt;Private witness state vs. the public ledger: the sensitive inputs stay private.&lt;/li&gt;
&lt;li&gt;Selective disclosure and viewing keys: show only what needs to be shown, to only who needs to see it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The simple way to think about it:&lt;/p&gt;

&lt;p&gt;Run the private logic on Midnight.&lt;br&gt;
Publish only the proof.&lt;br&gt;
Then use XRPL to settle.&lt;/p&gt;
&lt;h2&gt;
  
  
  Private logic, public rails
&lt;/h2&gt;

&lt;p&gt;This is the pairing.&lt;/p&gt;

&lt;p&gt;Split the app by what each chain is good at.&lt;/p&gt;

&lt;p&gt;Confidential decision, eligibility, or private state goes to Midnight.&lt;/p&gt;

&lt;p&gt;Final value movement goes to XRPL.&lt;/p&gt;

&lt;p&gt;The flow looks like this:&lt;/p&gt;

&lt;p&gt;Private inputs go into Midnight.&lt;br&gt;
Midnight creates a ZK proof that the rules were followed.&lt;br&gt;
The app takes that proof or token.&lt;br&gt;
XRPL handles the payment or settlement publicly.&lt;/p&gt;

&lt;p&gt;The honest caveat:&lt;/p&gt;

&lt;p&gt;Right now, there is no native trust-minimized bridge between Midnight and XRPL. This is not a magic one-click bridge today. This is an application-layer pattern that the app coordinates itself.&lt;/p&gt;

&lt;p&gt;Midnight's hybrid dApp direction points toward this kind of cross-chain future, but for now, this is something builders can prototype and design around at the app layer.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the private half looks like
&lt;/h2&gt;

&lt;p&gt;Here is a simplified Midnight / Compact-style example to show the pattern. The goal is not to ship a full contract here, but to show how private inputs, public ledger state, and selective disclosure fit together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ledger eligibleRoot: MerkleTreeDigest;
export ledger usedTokens: Set&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;;

witness credentialSecret(): Bytes&amp;lt;32&amp;gt;;
witness membershipPath(): MerkleTreePath&amp;lt;10, Bytes&amp;lt;32&amp;gt;&amp;gt;;

export circuit proveEligibility(): Bytes&amp;lt;32&amp;gt; {
  const secret = credentialSecret();
  const path = membershipPath();

  const commitment = persistentHash&amp;lt;Vector&amp;lt;2, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;([
    pad(32, "elig:commit:"),
    secret
  ]);

  assert(path.leaf == commitment, "wrong credential");
  assert(merkleTreePathRoot&amp;lt;10, Bytes&amp;lt;32&amp;gt;&amp;gt;(path) == eligibleRoot, "not eligible");

  const token = persistentHash&amp;lt;Vector&amp;lt;2, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;([
    pad(32, "elig:token:"),
    secret
  ]);

  assert(!usedTokens.member(disclose(token)), "already used");

  usedTokens.insert(disclose(token));

  return disclose(token);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what matters.&lt;/p&gt;

&lt;p&gt;The witness values are private inputs. That means the credential secret and the membership path are not being posted publicly.&lt;/p&gt;

&lt;p&gt;The Merkle check proves the user is part of the eligible set, but it does not reveal which user they are.&lt;/p&gt;

&lt;p&gt;That is the important part.&lt;/p&gt;

&lt;p&gt;The app can prove, "this person is eligible," without exposing their identity, their credential, or the private data used to check them.&lt;/p&gt;

&lt;p&gt;The only thing disclosed is the one-time token.&lt;/p&gt;

&lt;p&gt;That token works like a nullifier. It proves this eligibility check has been used once, and it prevents the same private credential from being reused over and over.&lt;/p&gt;

&lt;p&gt;So the public chain gets the proof reference.&lt;/p&gt;

&lt;p&gt;It does not get the private data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the public half looks like
&lt;/h2&gt;

&lt;p&gt;Now XRPL does what XRPL does best.&lt;/p&gt;

&lt;p&gt;It settles.&lt;/p&gt;

&lt;p&gt;In a real app, the eligibility token should be hex-safe before it goes into the XRPL memo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eligibilityTokenHex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;eligibilityToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;TransactionType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Payment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classicAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RECIPIENT_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;xrpToDrops&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;Memos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Memo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;MemoType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;midnight:eligibility-token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;MemoData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;eligibilityTokenHex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prepared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;autofill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prepared&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submitAndWait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx_blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The XRPL transaction is public and auditable.&lt;/p&gt;

&lt;p&gt;Anyone can see the payment happened.&lt;/p&gt;

&lt;p&gt;Anyone can see the eligibility token attached in the memo.&lt;/p&gt;

&lt;p&gt;But they do not see the private logic behind it.&lt;/p&gt;

&lt;p&gt;They do not see the identity check.&lt;br&gt;
They do not see the full customer data.&lt;br&gt;
They do not see the private business rules.&lt;br&gt;
They only see the public settlement tied to a valid proof reference.&lt;/p&gt;

&lt;p&gt;That is the point.&lt;/p&gt;

&lt;p&gt;Public rails. Private logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete use case: private eligibility, public payment
&lt;/h2&gt;

&lt;p&gt;This is where it gets real for compliance and audits.&lt;/p&gt;

&lt;p&gt;Say an app needs to make sure someone is eligible before a payment happens.&lt;/p&gt;

&lt;p&gt;Maybe they need to be KYC'd.&lt;br&gt;
Maybe they need to not be sanctioned.&lt;br&gt;
Maybe they need to be accredited.&lt;br&gt;
Maybe they need to meet some internal business rule before they can claim, buy, settle, or receive funds.&lt;/p&gt;

&lt;p&gt;On XRPL alone, you either expose too much or you handle the private side off-chain and ask everyone to just trust the app.&lt;/p&gt;

&lt;p&gt;With Midnight added, the app can prove the user passed the check without leaking the actual data.&lt;/p&gt;

&lt;p&gt;Then XRPL handles the payment.&lt;/p&gt;

&lt;p&gt;If a regulator, auditor, or approved third party needs to review it, selective disclosure or a viewing key can give them access to exactly what they need.&lt;/p&gt;

&lt;p&gt;Not the whole world.&lt;/p&gt;

&lt;p&gt;Just the right party.&lt;/p&gt;

&lt;p&gt;That is the difference.&lt;/p&gt;

&lt;p&gt;This is not privacy to hide from compliance.&lt;/p&gt;

&lt;p&gt;This is privacy that can work with compliance.&lt;/p&gt;

&lt;p&gt;Compliant AND confidential.&lt;/p&gt;

&lt;p&gt;That is the lane I think matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a developer would build this flow
&lt;/h2&gt;

&lt;p&gt;At the app level, the flow would look something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an eligible-user set off-chain.&lt;/li&gt;
&lt;li&gt;Store the Merkle root publicly on Midnight.&lt;/li&gt;
&lt;li&gt;Keep each user's credential secret private.&lt;/li&gt;
&lt;li&gt;Let the user generate a Midnight proof that they are in the eligible set.&lt;/li&gt;
&lt;li&gt;Disclose only the one-time eligibility token.&lt;/li&gt;
&lt;li&gt;Attach that token to an XRPL payment memo.&lt;/li&gt;
&lt;li&gt;Let the app, auditor, or verifier connect the XRPL payment back to the Midnight proof reference.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key part is that the private data never needs to touch XRPL.&lt;/p&gt;

&lt;p&gt;XRPL only sees the settlement and the proof reference.&lt;/p&gt;

&lt;p&gt;Midnight handles the private check.&lt;/p&gt;

&lt;p&gt;That gives you a cleaner split:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Midnight proves the private rule was followed.&lt;/li&gt;
&lt;li&gt;XRPL proves the public payment happened.&lt;/li&gt;
&lt;li&gt;The app connects the two.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is also why the one-time token matters.&lt;/p&gt;

&lt;p&gt;Without it, someone could try to reuse the same eligibility proof again and again. With the token/nullifier pattern, the app can tell that the private credential was already used without learning who the user is.&lt;/p&gt;

&lt;p&gt;That is a useful pattern for claims, gated payments, allowlists, private compliance checks, and other flows where you need proof without exposing the full dataset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why both beats either
&lt;/h2&gt;

&lt;p&gt;XRPL alone gives you fast, liquid, low-cost settlement.&lt;/p&gt;

&lt;p&gt;But it does not give you privacy.&lt;/p&gt;

&lt;p&gt;A privacy chain alone gives you confidentiality.&lt;/p&gt;

&lt;p&gt;But then you may lose the XRPL settlement layer, liquidity, tooling, users, and ecosystem that already exist.&lt;/p&gt;

&lt;p&gt;Together, the two make more sense.&lt;/p&gt;

&lt;p&gt;Midnight handles the private proof.&lt;/p&gt;

&lt;p&gt;XRPL handles the public settlement.&lt;/p&gt;

&lt;p&gt;That gives builders a way to keep sensitive logic private without giving up auditability.&lt;/p&gt;

&lt;p&gt;And that distinction matters.&lt;/p&gt;

&lt;p&gt;Because real businesses do not want to dump sensitive data on-chain just to prove something happened.&lt;/p&gt;

&lt;p&gt;But they also do not want black-box systems where nobody can verify anything.&lt;/p&gt;

&lt;p&gt;The better model is selective proof.&lt;/p&gt;

&lt;p&gt;Show what needs to be shown.&lt;br&gt;
Hide what should stay private.&lt;br&gt;
Still settle in public.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it will take to make this real
&lt;/h2&gt;

&lt;p&gt;The hard part is coordination.&lt;/p&gt;

&lt;p&gt;If Midnight proves something privately and XRPL settles publicly, you need strong guarantees that those two actions stay tied together.&lt;/p&gt;

&lt;p&gt;That means better bridge patterns, oracle models, proof verification flows, or atomic-style guarantees so the proof and the settlement cannot be split apart or abused.&lt;/p&gt;

&lt;p&gt;That part is still early.&lt;/p&gt;

&lt;p&gt;But I think this is exactly where serious builders should be looking.&lt;/p&gt;

&lt;p&gt;Because the next wave of Web3 apps will not just be about putting everything on-chain.&lt;/p&gt;

&lt;p&gt;It will be about knowing what belongs on-chain, what should stay private, and how to prove the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;XRPL gives us strong public rails.&lt;/p&gt;

&lt;p&gt;Midnight gives us private logic.&lt;/p&gt;

&lt;p&gt;Together, they point to a better pattern for real applications.&lt;/p&gt;

&lt;p&gt;You do not have to trade away auditability to get confidentiality.&lt;/p&gt;

&lt;p&gt;And you do not have to expose sensitive data just to prove the rules were followed.&lt;/p&gt;

&lt;p&gt;Private logic.&lt;br&gt;
Public rails.&lt;br&gt;
That is the build path I want to see more people explore.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
