Devnet snapshot · signs nothing, holds no keys · this project is not secure, not audited, not production-ready (P-5)

W-CONF

What confidential Multi-Purpose Tokens (MPTs) on the XRP Ledger hide, and what they do not. A research project, shown on one snapshot of the Devnet.

Confidential balances, in brief

The XRP Ledger is public: anyone can read every account and every transaction on it. Multi-Purpose Tokens (MPTs, XLS-33) are one of its kinds of token. Confidential Transfers (XLS-96) let an MPT holder keep their balance encrypted on that public ledger: hidden from the public, not from the token’s issuer.

Each confidential balance is stored as an ElGamal ciphertext: a number only the right key can read. A holder registers their own encryption key when they opt in.

The ledger must still check that nobody spends more than they hold. So every transaction that spends from a confidential balance carries zero-knowledge proofs, which every validator checks without learning any hidden amount.

XLS-96 is a final standard. When this page was written, on 2026-10-21, its amendment, ConfidentialTransfer, was enabled on Devnet and not on Mainnet or Testnet.

Sources: E-1, E-349; the README’s terms; TASK ZERO §3

The life of a confidential position

Holder A
Public balance
Convert: amount public
Inbox
ConfidentialBalanceInbox
MergeInbox
Spending balance
ConfidentialBalanceSpending
Send to holder B: amount hidden; A and B public
Note: the Send carries
the amount encrypted for the sender, the receiver, the issuer and any auditor, and proofs every validator checks
Holder B
Inbox
ConfidentialBalanceInbox
MergeInbox
Spending balance
ConfidentialBalanceSpending
ConvertBack: amount public
Public balance
publichidden from the public

Green is hidden from the public, not from the issuer: the issuer can read every green box, and so can an auditor if the token names one.

Holder A converts, merges and sends to holder B, who merges and converts back. Only the Send hides its amount.

Convert (ConfidentialMPTConvert) moves tokens from the holder’s public balance into their confidential inbox, and the amount is published. The first Convert also registers the holder’s key, which can never be changed. Send (ConfidentialMPTSend) moves an amount from one confidential balance to another; the amount is hidden, the sender and receiver are not, and it too arrives in the receiver’s inbox. ConvertBack (ConfidentialMPTConvertBack) moves tokens back to a public balance, and the amount is published, on every ConvertBack separately.

Tokens in the inbox cannot be sent yet. A separate transaction, MergeInbox, moves the inbox into the spending balance, so a holder who converts must merge before their first Send.

Only Send hides an amount. The amount is encrypted separately for the sender, the receiver, the issuer and any auditor. Send’s proofs show that every copy is the same number, that the sender holds the key to their balance, and that neither the amount nor the sender’s remaining balance is negative.

Why are both ends public? The ledger shows that no tokens are created inside the confidential pool by keeping its total in plain view. The token records the total held in confidential form, and every Convert and ConvertBack changes it by a published amount.

Sources: E-9, E-33, F-24, F-27, P-4, P-11; TASK ZERO §3; WHAT-CONFIDENTIAL-MEANS

What is hidden, and what is public

Hidden from the public

  • Each holder’s balance, except in the zero cases under “Public, always”.
  • The amount of each Send.

Both are hidden from the public, not from the issuer.

Public, always

  • Who sends to whom, and when, so how often anyone transacts.
  • Every amount converted in or out, each one separately.
  • The token’s total supply, and the total held in confidential form.
  • That a balance is exactly zero, in three cases: when it is first set up, when a MergeInbox empties the inbox, and after an issuer clawback, which destroys the balance. A balance spent down to zero looks like any other.

The public numbers also bound the hidden ones. If a holder converts in 500, receives nothing and converts back 400, anyone can work out that the other 100 is either still held or has left by confidential sends. No key is needed.

Sources: P-4, P-9, P-11, F-24, F-1, E-211, R-1, R-7

Who can read a balance, and who can spend it

Anyone
Who sends to whom, and when
yes
Amounts converted in or out
yes
Total held in confidential form
yes
A holder's balance
only whether it is a recognisable zero
A Send's amount
no
Can spend a balance
no
A holder
Who sends to whom, and when
yes
Amounts converted in or out
yes
Total held in confidential form
yes
A holder's balance
their own; others only as a recognisable zero
A Send's amount
their own only
Can spend a balance
their own, with both their keys
The issuer
Who sends to whom, and when
yes
Amounts converted in or out
yes
Total held in confidential form
yes
A holder's balance
every one
A Send's amount
every one
Can spend a balance
no; it can destroy one if the token allows clawback, and may freeze one
An auditor, if the token names one
Who sends to whom, and when
yes
Amounts converted in or out
yes
Total held in confidential form
yes
A holder's balance
every one
A Send's amount
every one
Can spend a balance
no
Who can read what, and who can spend. A dashed cell is a partial yes. A recognisable zero is a new balance, an inbox just merged, or a position clawed back.

Sources: P-9, E-58, E-11, E-33, E-118, E-119

The example: a confidential receivables facility

The other pages do not show this facility. They read four Devnet test accounts that hold one demo token, made by this project while it was being built: Holder A, an active position; Holder B, whose key is derived from its master seed; Holder C, the account the project’s ed25519 signing ran on; and Holder D, an early test account. “What anyone sees” and “What the issuer sees” show how those accounts look to an observer and to the issuer. The facility below ran separately, and its record is in the repository: the facility walkthrough.

A bank pays a large buyer’s suppliers early, against their invoices. How much a supplier draws says a lot about its cash position, so these numbers are commercially sensitive.

This project built the core of that facility on XLS-96 and ran it on Devnet on 2026-08-25, in 28 validated facility transactions besides setting up the token:

The bank
issues the token; reads every balance
funds it: amount public
The Dedicated Account
an ordinary XRPL account that holds the float, the money the facility advances to suppliers
one Convert: the whole float, amount public
The float, now confidential, in the Dedicated Account
drawdowns: Sends to suppliers
repayments: Sends back
Amounts hidden; who and when public.
Supplier A
limit, draws and what it owes are hidden
Supplier B
limit, draws and what it owes are hidden
Supplier C
limit, draws and what it owes are hidden
Each supplier enrols with a small public payment and its own Convert: both amounts public.
At the end: the float left in the Dedicated Account
retired in tranches: each ConvertBack's amount public
A public balance again
publichidden from the publicthe issuer can read it
The facility as it ran on Devnet. The bank issues the token and so reads every balance; the amounts that move between the Dedicated Account and the suppliers are hidden, and the amounts at the edges are not.

The run shows that these transactions are well formed and that rippled accepts them. It read the ledger through a public Devnet node, not one on the same machine as the design requires, so it says nothing about that requirement.

Anyone can see

  • The size of the float.
  • Which suppliers are enrolled, and both public amounts of each enrolment.
  • Who drew and repaid, on which days, and how often.
  • The size of every retirement tranche.

Hidden from the public

  • Each supplier’s limit.
  • The size of each drawdown and repayment.
  • What each supplier still owes.

The bank, as issuer, sees all of it.

In short, it hides how much each supplier draws and owes, not who borrows, when, or the size of the float.

Sources: E-146, B-18, E-151, F-24, F-26, R-1; the four accounts: E-208, E-118, E-176, E-101

How the project runs on a holder's machine

This site only shows. The project itself runs on one machine: a Rust program, wconf-signer, holds the keys and signs, and everything else talks to it over a local socket that no web page can open.

Viewing
The local web page
web/app, in the browser
HTTP
The read-only API
web/api, on loopback only
socket: Status only
wconf-signer, the same program as below
answers Status, which carries no authority
Transacting
The facility driver
facility/, plays the bank and every supplier
socket: Pair opens a grant, then Prepare asks for one transaction
wconf-signer, one per party
holds the ElGamal key and the account key
builds the proofs, shows the transaction in its own window, signs, and returns the signed bytes to the driver. It never submits.
Submitting
The facility driver
with the signed bytes
submits, with fail_hard on its movements
Devnet
fail_hard asks that a failing transaction be discarded rather than applied
The API and the signer read the ledger. A web page cannot open the signer’s socket, so a script injected into one can reach Status at most.
The project as it runs on one machine. In the demonstration one driver plays every party and holds every seed; that is a property of the demonstration, not of the design.

Sources: the README’s “How it fits together”; E-146, B-15

Limits

Sources: P-10, E-9, E-119, E-341, E-12, E-21, E-223, E-290, E-292, P-5

Each id under a panel, such as E-58, is a row in the project’s claims register, CLAIMS.md. Where this page and a row disagree, the row wins.