DEV Community

Cover image for Private logic, public rails: Pairing Midnight's ZK privacy with XRPL settlement
Cory Dabrowski
Cory Dabrowski

Posted on

Private logic, public rails: Pairing Midnight's ZK privacy with XRPL settlement

Building on XRPL has taught me one thing fast: public rails are powerful, but not every part of the app should be public.

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.

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.

The argument of this post is simple:

You should not have to choose between public settlement and private logic.

Use Midnight for the private half.
Use XRPL for the public half.
That gives you both.

Two halves of one problem

Most on-chain apps are really dealing with two different problems at the same time.

One side is settlement.

That means moving value fast, cheap, final, and in a way people can audit.

The other side is private logic.

That means proving something is true without exposing all the data behind it.

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.

I do not think it has to be that way.

What XRPL is great at

XRPL is my home base, and the strength is obvious once you build on it.

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

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

But XRPL does not give you privacy.

Everything is public by design.

For settlement, that is a feature.

For customer data, business logic, compliance decisions, identity checks, or private financial data, that becomes a problem fast.

What Midnight adds

Midnight brings in the private side.

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.

The important pieces are:

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

The simple way to think about it:

Run the private logic on Midnight.
Publish only the proof.
Then use XRPL to settle.

Private logic, public rails

This is the pairing.

Split the app by what each chain is good at.

Confidential decision, eligibility, or private state goes to Midnight.

Final value movement goes to XRPL.

The flow looks like this:

Private inputs go into Midnight.
Midnight creates a ZK proof that the rules were followed.
The app takes that proof or token.
XRPL handles the payment or settlement publicly.

The honest caveat:

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.

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.

What the private half looks like

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.

export ledger eligibleRoot: MerkleTreeDigest;
export ledger usedTokens: Set<Bytes<32>>;

witness credentialSecret(): Bytes<32>;
witness membershipPath(): MerkleTreePath<10, Bytes<32>>;

export circuit proveEligibility(): Bytes<32> {
  const secret = credentialSecret();
  const path = membershipPath();

  const commitment = persistentHash<Vector<2, Bytes<32>>>([
    pad(32, "elig:commit:"),
    secret
  ]);

  assert(path.leaf == commitment, "wrong credential");
  assert(merkleTreePathRoot<10, Bytes<32>>(path) == eligibleRoot, "not eligible");

  const token = persistentHash<Vector<2, Bytes<32>>>([
    pad(32, "elig:token:"),
    secret
  ]);

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

  usedTokens.insert(disclose(token));

  return disclose(token);
}
Enter fullscreen mode Exit fullscreen mode

Here is what matters.

The witness values are private inputs. That means the credential secret and the membership path are not being posted publicly.

The Merkle check proves the user is part of the eligible set, but it does not reveal which user they are.

That is the important part.

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

The only thing disclosed is the one-time token.

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.

So the public chain gets the proof reference.

It does not get the private data.

What the public half looks like

Now XRPL does what XRPL does best.

It settles.

In a real app, the eligibility token should be hex-safe before it goes into the XRPL memo.

const eligibilityTokenHex = eligibilityToken.toUpperCase();

const payment = {
  TransactionType: "Payment",
  Account: payer.classicAddress,
  Destination: RECIPIENT_ADDRESS,
  Amount: xrpToDrops("100"),
  Memos: [
    {
      Memo: {
        MemoType: Buffer.from("midnight:eligibility-token")
          .toString("hex")
          .toUpperCase(),
        MemoData: eligibilityTokenHex,
      },
    },
  ],
};

const prepared = await client.autofill(payment);
const signed = payer.sign(prepared);
const result = await client.submitAndWait(signed.tx_blob);
Enter fullscreen mode Exit fullscreen mode

The XRPL transaction is public and auditable.

Anyone can see the payment happened.

Anyone can see the eligibility token attached in the memo.

But they do not see the private logic behind it.

They do not see the identity check.
They do not see the full customer data.
They do not see the private business rules.
They only see the public settlement tied to a valid proof reference.

That is the point.

Public rails. Private logic.

A concrete use case: private eligibility, public payment

This is where it gets real for compliance and audits.

Say an app needs to make sure someone is eligible before a payment happens.

Maybe they need to be KYC'd.
Maybe they need to not be sanctioned.
Maybe they need to be accredited.
Maybe they need to meet some internal business rule before they can claim, buy, settle, or receive funds.

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

With Midnight added, the app can prove the user passed the check without leaking the actual data.

Then XRPL handles the payment.

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.

Not the whole world.

Just the right party.

That is the difference.

This is not privacy to hide from compliance.

This is privacy that can work with compliance.

Compliant AND confidential.

That is the lane I think matters.

How a developer would build this flow

At the app level, the flow would look something like this:

  1. Create an eligible-user set off-chain.
  2. Store the Merkle root publicly on Midnight.
  3. Keep each user's credential secret private.
  4. Let the user generate a Midnight proof that they are in the eligible set.
  5. Disclose only the one-time eligibility token.
  6. Attach that token to an XRPL payment memo.
  7. Let the app, auditor, or verifier connect the XRPL payment back to the Midnight proof reference.

The key part is that the private data never needs to touch XRPL.

XRPL only sees the settlement and the proof reference.

Midnight handles the private check.

That gives you a cleaner split:

  • Midnight proves the private rule was followed.
  • XRPL proves the public payment happened.
  • The app connects the two.

This is also why the one-time token matters.

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.

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.

Why both beats either

XRPL alone gives you fast, liquid, low-cost settlement.

But it does not give you privacy.

A privacy chain alone gives you confidentiality.

But then you may lose the XRPL settlement layer, liquidity, tooling, users, and ecosystem that already exist.

Together, the two make more sense.

Midnight handles the private proof.

XRPL handles the public settlement.

That gives builders a way to keep sensitive logic private without giving up auditability.

And that distinction matters.

Because real businesses do not want to dump sensitive data on-chain just to prove something happened.

But they also do not want black-box systems where nobody can verify anything.

The better model is selective proof.

Show what needs to be shown.
Hide what should stay private.
Still settle in public.

What it will take to make this real

The hard part is coordination.

If Midnight proves something privately and XRPL settles publicly, you need strong guarantees that those two actions stay tied together.

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.

That part is still early.

But I think this is exactly where serious builders should be looking.

Because the next wave of Web3 apps will not just be about putting everything on-chain.

It will be about knowing what belongs on-chain, what should stay private, and how to prove the difference.

Takeaway

XRPL gives us strong public rails.

Midnight gives us private logic.

Together, they point to a better pattern for real applications.

You do not have to trade away auditability to get confidentiality.

And you do not have to expose sensitive data just to prove the rules were followed.

Private logic.
Public rails.
That is the build path I want to see more people explore.

Top comments (0)