ZPrivDEX — building a shielded AMM on Zcash

Hi all,

I want to start somewhere specific: a thread from February that I think identified an important open problem but didn’t get a follow-up.

When the Maya Protocol retroactive grant was being discussed, @ emersonian raised what he called “traceable Orchard.” The framing was precise — when someone deposits into an Orchard address and the system immediately sweeps those funds into a transparent vault before processing the swap, the match between deposit amount and later withdrawal is close enough in amount and timing that the privacy gain is much smaller than it appears. @ itz, Maya’s application owner, confirmed this directly: “You’re right, support right now is as you mention ‘traceable orchard’. Right now, it has to go through our transparent vault. We currently do GG20 and would have to add FROST to fully support Orchard.”

@ZolyDevMaya added something important alongside that: even in a future version where FROST replaced the transparent vault and Maya’s ZEC custody was genuinely shielded, the non-ZEC leg of any cross-chain swap still settles on Bitcoin, Ethereum, or whichever transparent chain is on the other side — and swap metadata stays visible on Mayachain. That’s a fundamental ceiling for cross-chain DEXs. It’s not a criticism of Maya’s work, it’s just what cross-chain architecture looks like when only one side has privacy.

I want to be clear: the Zcash community was right to support that grant. Maya’s engineering was serious and their honesty about the limitations was admirable. @ daira supported it, @ emersonian said he’d have doubled the amount, and the community broadly agreed. The retroactive grant was well-deserved for work that was already shipped and running on mainnet.

But the question that came out of that thread — what would genuinely shielded pool custody actually require, and has anyone built it? — still doesn’t have an answer. That’s what this proposal is about.


The problem, stated plainly

If you hold ZEC and want to swap it for another asset today, every path available puts your funds through a transparent address at some point:

Maya Protocol, which has done more than anyone to bring shielded ZEC to cross-chain trading, confirmed as recently as March 2026 that all inbound swap addresses are transparent, even when routing from a shielded source to a shielded destination. That will improve over time, but it is the current state.

Wrapped ZEC on Ethereum-based DEXs means bridging out of the Zcash network entirely, abandoning every privacy guarantee Zcash provides, and introducing bridge risk on top of it.

Centralized exchanges have been narrowing support for shielded deposits specifically, and some jurisdictions block shielded withdrawals entirely.

None of this is a failure of the projects involved — it reflects a genuine technical gap. The threshold signature systems that allow multiple validators to jointly control large amounts of crypto (GG20, MPC schemes generally) work cleanly with transparent addresses. They do not support Orchard addresses, because Orchard spend authorization uses re-randomized Schnorr signatures (RedDSA), and generic MPC systems don’t implement the re-randomization that Orchard requires for unlinkability.

The Zcash Foundation solved this. Their re-randomized FROST implementation (ZIP-312, frost-rerandomized over RedPallas) is specifically designed to produce spend authorization signatures compatible with Orchard, while maintaining the unlinkability property that makes Orchard private. They declared it production-ready in 2024 and have been encouraging wallet integration. Zenith’s 2025 grant is building wallet multi-sig on top of it.

Nobody has built DeFi pool custody on top of it. That’s the gap.

What ZPrivDEX builds

A constant-product AMM — the same x * y = k formula behind most decentralized exchanges — where two things are structurally different from every existing Zcash DEX integration:

Pool custody lives in Orchard unified addresses. The spending key for the pool is never held by any single party. Instead it’s distributed across a 5-of-9 threshold using ZcashFoundation/frost with the frost-rerandomized ciphersuite over RedPallas. Signing ceremonies use the frostd and frost-client tooling that ZF has already released. Partially Created Zcash Transactions (PCZTs) handle the multi-round signing flow, using the pczt crate already in librustzcash. A compromised coordinator can delay operations but cannot steal funds without 5 valid threshold shares.

Trade validity is enforced by a Halo2 zero-knowledge proof. The circuit proves that x_old * y_old = x_new * y_new — the AMM invariant — without revealing what x_old, y_old, x_new, or y_new actually are. The circuit uses halo2_proofs 0.3.0 and the Pallas curve: the same crate and same curve as Zcash’s Orchard protocol, which means it composes naturally with Orchard’s note commitment and nullifier constraints in the full implementation. The public input is a commitment to the pool state. The verifier — the FROST signer group — confirms the trade was fair before co-signing the output transaction, without seeing the balances.

This combination means pool funds never touch a transparent address. No transparent vault, no sweep-and-restore pattern. Just Orchard notes, FROST custody, and ZK math.

A third component — decentralized lightwalletd — runs in parallel. ZCG has an open RFP for this. A shielded DEX that depends on a centralized sync server is fragile in ways that matter for a privacy tool. This piece gets built as a standalone public-good library regardless of how the rest of ZPrivDEX is received. It’s infrastructure the whole ecosystem needs and the RFP has been sitting open.


Where @ZolyDevMaya’s point actually helps this proposal

The structural ceiling they identified — that cross-chain swaps are inherently limited because the non-Zcash leg settles transparently — doesn’t apply to a same-chain AMM. ZPrivDEX trades only between Zcash-native assets: ZEC, and ZSA tokens once NU7 activates (ZIP-226/227). There is no cross-chain leg to worry about. The entire swap lifecycle — deposit, trade proof, output — stays within the Zcash shielded pool. It’s a narrower scope than Maya’s cross-chain vision, but for the privacy properties, that narrowness is actually the point.


Technical proof of concept

I have a working implementation of the pool invariant constraint system in Halo2. Around 350 lines of Rust, halo2_proofs 0.3.0 over Pallas, 11 tests covering both valid pool transitions (completeness) and attack scenarios including pool drain attempts, inflated outputs, and tampered public inputs (soundness). The full proof generation demo runs end-to-end in under 30 seconds on a laptop.

The code is almost ready to publish. I am deliberately not posting a half-finished repository in a community thread — that does more harm than good for credibility. It will be linked in the GitHub application, and I’ll update this post with the link when it’s up. If you’re technically inclined and want to see the circuit code before then to evaluate whether the constraint system design makes sense, message me directly. I’d rather show it to technical reviewers informally first.

The FROST custody system and lightwalletd layer are designed but not yet coded. The circuit prototype exists to validate the hardest claim in the proposal: that you can enforce AMM fairness in Halo2 without revealing pool balances. That’s the part that fails quietly if the design is wrong.


Three things I’m genuinely uncertain about and want community input on

Signer set decentralization. Cryptographically a 5-of-9 threshold is sound. Practically, if 6 of 9 signers are the same organization, the security model collapses to something much weaker. For launch I can publish packaged signer daemon binaries and clear setup documentation to make it easy for community members to run nodes, and I can commit to publishing the full signer set composition publicly so anyone can audit who is running what. But building real trust in a distributed signer group takes time that can’t be shortcut. I’m curious how others in this community think about “good enough for launch” on signer distribution, and whether there are precedents from other Zcash infrastructure projects worth looking at.

Proof generation time at full circuit scale. The prototype circuit is small. The production circuit — with Poseidon pool state commitments and Orchard note validity constraints added — will be considerably larger. I’ve designed a fallback: split the pool invariant proof and the spend authorization proof, link them by commitment hash, generate in parallel. That solves proving time at the cost of more complexity in the verifier. I won’t have real benchmarks until M3. If the committee has asked other ZK-based proposals hard questions about this, I’d like to know what those questions were so I can address them proactively.

NU7 dependency for the ZSA work. Everything through M7 — FROST custody, the ZK-AMM, the audit, lightwalletd — runs on current Zcash. M8, which adds ZSA pool pairs and a ZEC-collateralized ZUSD stablecoin, depends on NU7. If NU7 slips past its target, M8 reschedules by mutual agreement with ZCG rather than being rushed against an unstable protocol surface. That’s my plan, but I want to name it explicitly rather than quietly hoping the timeline holds.


Budget summary

$203,000 over 13 months, 8 milestones. Roughly 68% is engineering compensation for two people at below-market rates for this type of cryptography work. The largest external cost is a security audit ($35,000, targeting Least Authority who audited Sapling — the right firm for FROST and Halo2 in the same engagement). The rest is infrastructure and a contingency buffer.

I know this is a large ask for a team without a prior ZCG grant. I’ve tried to account for that by making every milestone deliverable independently verifiable by someone outside the project, and by building the circuit prototype before asking rather than after. The full breakdown is in the GitHub application when it goes up.


What I want from this thread

Real criticism, not encouragement. Specifically: if the FROST/Orchard composition has a problem I haven’t seen, if the circuit approach has a flaw, if there’s existing work that already does what I’m describing, I want to know now. The formal application goes in after this thread has had time to breathe and I’ve incorporated whatever comes out of it.

One more thing. I want to be careful that nothing in this post reads as diminishing Maya’s work. It doesn’t. @ itz and @ ZolyDevMaya were transparent about their architecture in a way that a lot of projects aren’t, and the community recognized that. The “traceable Orchard” framing @ emersonian introduced is useful vocabulary, not a criticism. ZPrivDEX addresses a different and narrower problem: what does fully shielded AMM custody look like on a single chain. Maya solved cross-chain liquidity access with the tools available. These are adjacent problems, not competing ones.

Thanks for reading.

2 Likes

Interesting … It’d be good to have a whitepaper though.

A use case I wish more were bullish on. :bullseye: :brain:

2 Likes

We do have one would you like to take a look at it , i would share it down here

I sent the following message privately to @startersplugs and they requested that I post it publicly so here it is:


What I want from this thread

Real criticism, not encouragement.

Well, okay. I read the whole thing and it sounds like you’ve already thought it through pretty well. My criticism is: more privacy is not always better!

A good way to go about it is, write down all the information that you think should be revealed to whom, in your ideal system. “To whom” can go into a few categories:

  1. The end user (who can, of course, choose to reveal any information they have to anyone else).
  2. The public.
  3. A specific third party chosen by the end user (for example, if you use a lightwalletd you are choosing which one to use — or even to run your own — and revealing certain information to that lightwalletd).
  4. A specific third party not chosen by the end user, except inasmuch as they choose to use the system at all (for example a hard-coded “master view key” that could read all encrypted data of all users who choose to use the system at all, such as was previously proposed and shot down for Zcash ZSAs, and such as was just deployed by Tempo).

You might have different answers than I do about what information should be revealed to whom, but the exercise of thinking about it and writing it down is probably helpful even when your answers differ from other people’s!

So, to be specific, my criticism of your proposal is that in my humble opinion, some of this information should be revealed to the public. Even if you could successfully conceal it, I don’t think that would be better. Some examples off the top of my head: number of swaps, total volume swapped, which coins are being swapped for which other coins, total value at any given time custodied by intermediaries or held by smart contracts or otherwise outside of one end-user’s control. I think it is better if all that information is public, which I think is roughly what the current architectures like Maya’s achieve.

The reason I think that information should be public is 1. that I think it imposes little risk on end-users (whose privacy is basically safe if they store their value shielded at rest, regardless of the publication of this information, and whose privacy is basically lost if they try to gain privacy from value in flight, regardless of the publication of this information). and 2. that I think its valuable for the safety of users and the evolution of society for such information to be public, so that users and builders can monitor the operation of the system and how much usage it has overall and how much usage different parts of it have, and how much value is at risk of being lost or stolen due to flaws or betrayal.

So, to me, it’s not that I want that information to be concealed from the public, and the difficulty of shielded address integration, or the lack of sufficiently advanced technology, or whatever, has made that difficult. It’s that I think that information should be public, and your proposal to hide it from the public would, in my humble opinion, result in a worse trade-off.

There’s an additional more specific criticism I offer, that your proposal shifts some information from class 2—the public—to class 4—specific third parties that the user can’t select. I think this is often worse! People often assume this is better, because it is an improvement in “privacy” if information is revealed only to a few people instead of to the public, but I suspect it is often worse, because it gives those few people added power compared to the public and compared to the user, and those people then become targets of temptation, extortion, hacking, legal compulsion, etc. So just as a very general pattern (that I wouldn’t necessarily defend in every instance), I prefer for information to be class 1—held by the user—when it can be sensitive/valuable to them personally and unimportant for the public good, and for it to be class 2—the public—when that trade-off is better, but I usually prefer 1 or 2 over 3 or 4!

Okay, that’s the criticism you requested. I prefer to send criticism privately, but if you are interested in pursuing this topic and you want this message to be public, I’ll post it publicly if you like, or you can quote it with or without attribution to me if you like. :slight_smile:

Thanks for contributing to our great mission to improve the world for everyone! <3

Regards,

Zooko

4 Likes

Thank you for taking the time to read this carefully and give us a suggestion. I want to respond to it properly rather than just acknowledging it and moving on, because you’ve identified something real and would help the product.
What you’ve written is more useful than most of the feedback I’ve received from a lot of crypto enthusiasts i spoke about this project and it deserves to be visible rather than sitting in a private thread. The committee should see this conversation, and frankly the community should too.

Now to the substance

Where you’re right and I’m changing the design

The four-category framework is sharper than how I’ve been thinking about this. I’ve been treating “privacy” as a single goal and optimizing for more of it everywhere. That’s the wrong frame. The right question — which you’ve stated precisely — is: who should have access to which information, and is that better or worse than the alternative?

On aggregate pool statistics: you’re correct. Total liquidity in a pool, cumulative volume, number of swaps, which asset pairs are being traded, total value custodied — this is class 2 information and it should stay class 2. It serves a genuine public good. Users evaluating whether a protocol works need it. Researchers monitoring systemic risk need it. Builders deciding whether to integrate need it. And hiding it doesn’t protect any individual user from anything meaningful — someone whose trade is private because of a ZK proof is still private whether or not the total pool volume is visible on-chain.

This is a change to the design. Pool-level statistics — TVL, volume, swap counts per pair, fee accumulation — will be committed to public on-chain state, not hidden behind the pool commitment. The ZK proof protects what it should protect: the individual trade — who traded, how much, in which direction. That’s the sensitive information. The aggregate is not.

On the class 4 problem: this one I had to sit with. You’re describing a pattern where information moves from class 2 (public, anyone can see it) to class 4 (visible only to a small group the user didn’t specifically choose), and arguing this is often worse because it concentrates information in parties who then become targets — for extortion, legal compulsion, hacking, or simple temptation. I think you’re right that I haven’t fully reckoned with the pool coordinator in this design. The coordinator sees which trade PCZTs are in-flight before they’re signed. That is class 4 information, not class 1 and not class 2, and it creates exactly the kind of privileged position you’re warning about.

This is a change to the design. The coordinator should see an encrypted trade intent until the moment it assembles the PCZT for signing — learning the transaction content at ceremony time, not before. This doesn’t eliminate the coordinator’s privileged window entirely, but it shrinks it significantly and removes the standing information advantage that makes front-running attractive. The FROST signers themselves become the check on coordinator behavior: they can refuse to sign PCZTs from a coordinator showing anomalous patterns.

Once again
Thank you @zooko !

1 Like

Appreciate this — it’s exactly the kind that sharpens a design.

The line makes sense when you think about what each category actually leaks. Aggregate stats — TVL, volume, swap counts — tell you nothing about any specific person. Individual trade data tells you exactly who moved what and when, which in a sufficiently liquid pool is enough to reconstruct someone’s position and strategy. That asymmetry is why the ZK proof belongs at the trade level, not the pool level. Hiding aggregate stats doesn’t protect users, it just makes the protocol opaque for no gain.

The coordinator fix is the same reasoning applied to the class 4 problem. A coordinator that knows your trade before it’s signed is a standing target. Encrypting the intent until PCZT assembly doesn’t eliminate that window but it removes the informational advantage that makes front-running worth attempting.

Both of these came out of this thread. The final product is going to be better.

Most people building privacy infrastructure focus on the cryptography and treat the use case as obvious. It isn’t. A shielded AMM is only valuable if the privacy guarantee is coherent end to end — which is exactly why this thread has been useful. Getting the design right matters more than shipping fast and have a product for the community in a larger scale.

Thank you @dismad

1 Like

What are the zk statements? In particular the part where the exchange rate is determined by the total liquidity. What binds the inputs to the actual total value in the pool…

Let me answer it one by one.

The ZK Statements
The circuit proves seven statements simultaneously. The verifier — the FROST signer group — checks the proof against these public inputs before contributing any signing share:
Public inputs (visible to the verifier, not to blockchain observers):

C_old — Poseidon commitment to old pool state
C_new — Poseidon commitment to new pool state
nf — nullifier of the trader’s input note
cm_out — note commitment for the trader’s output
min_out — minimum output the trader accepts

Private witness (known only to the prover, hidden from everyone else)
x_old, y_old — pool reserves before the trade
x_new, y_new — pool reserves after the trade
delta_x — amount the trader deposits
delta_y — amount the trader receives
note_old — the trader’s input Orchard note
sk — spending key authorizing it
r_old, r_new — blinding randomness for the commitments

The seven constraints the circuit enforces:

S1 — Pool state binding: Poseidon(x_old, y_old, r_old) = C_old

S2 — AMM invariant with fee: x_new × y_new ≥ x_old × y_old × (1 − fee)

S3 — Trade consistency: x_new = x_old + delta_x and y_new = y_old − delta_y

S4 — Slippage protection: delta_y ≥ min_out

S5 — New state commitment: Poseidon(x_new, y_new, r_new) = C_new

S6 — Input note validity: The nullifier nf is correctly derived from note_old and sk, and the prover demonstrably knows sk

S7 — Output note correctness: cm_out correctly commits to value delta_y payable to the trader’s Orchard address

Statements S2 and S4 require a range proof subgadget for the inequality constraints — ZPrivDEX uses the LookupRangeCheckConfig from halo2_gadgets, the same chip the Orchard circuit itself uses.

How the Exchange Rate Is Determined by Total Liquidity

The exchange rate in a constant-product AMM is the ratio x/y. The exact output for a given input is:

delta_y = (y_old × delta_x × (1 − fee)) / (x_old + delta_x × (1 − fee))

This means the larger the trade relative to the pool size, the more the reserves shift and the worse the rate. Liquidity depth — the absolute size of x and y — is what determines price impact. A pool with x=1,000,000 and y=1,000,000 gives a much better rate on a 1,000-unit trade than a pool with x=10,000 and y=10,000, even though both have the same x/y ratio.

In ZPrivDEX, x and y are hidden from public blockchain observers. The exchange rate calculation still happens — it is enforced by Statements S2 and S3 together. Statement S3 ties the reserve change to the specific trade amounts: x_new must equal x_old plus delta_x, and y_new must equal y_old minus delta_y. Statement S2 then verifies that those new reserves satisfy the invariant given those deltas. These two constraints together force delta_y to be exactly what the constant-product formula produces given the actual x_old and y_old — no more, no less.

The trader cannot compute this themselves because they do not know x and y. They receive a price quote from the pool coordinator, who knows the reserves through the pool’s full viewing key, and they set min_out as their execution floor. Statement S4 guarantees their actual execution meets that floor regardless of coordinator honesty — the proof either verifies against their stated min_out or it does not.

What binds the inputs to the actual total value in the pool…


This is the critical question and the answer requires being precise about Zcash’s architecture. Zcash does not have smart contracts or mutable on-chain storage in the Ethereum sense. Pool state binding works through two layers operating together.

Layer one: the Poseidon commitment.

C_old is a Poseidon hash commitment to x_old and y_old with a blinding factor. The Poseidon hash function is implemented in ZPrivDEX using the Pow5Chip from halo2_gadgets — the same implementation used in the Orchard circuit. Poseidon is collision-resistant in the Halo2 circuit context: finding two different pairs (x, y) that produce the same commitment is computationally infeasible. Statement S1 forces the prover to use the correct x_old and y_old — any other values produce a different hash and the proof fails verification.

But this raises the right follow-up: what guarantees C_old itself reflects the actual funds in the pool, and not a value the coordinator invented?

Layer two: FROST signer verification against actual notes.

The FROST signer group holds the pool’s full viewing key. This means each signer can independently scan the Orchard note commitment tree, decrypt every note at the pool’s unified address, and sum the note values to compute x and y directly from the actual Orchard notes sitting in the pool.

When the coordinator proposes a trade and provides a claimed C_old, each signer independently computes what C_old should be from their own viewing key scan:

expected C_old = Poseidon(x_from_scan, y_from_scan, r_old)

If the coordinator’s claimed C_old does not match what each signer derives from their scan of actual pool notes, they refuse to contribute a FROST signing share. The ceremony requires 5 valid shares. Without them the transaction cannot complete and cannot broadcast.

The binding chain is therefore:

Actual Orchard notes at pool address
(FROST signers decrypt using full viewing key)
Each signer independently computes x and y from real note values
(each signer derives expected pool commitment)
Signers verify: coordinator’s C_old = Poseidon(x_from_scan, y_from_scan, r)
(ZK proof binds witness to C_old via Poseidon collision resistance)
Circuit proves trade consistency and invariant preservation
(5-of-9 signers sign only after proof verifies against their derived state)
Transaction broadcasts with C_new encoding the updated reserves

For this binding to break, an attacker would need to simultaneously break Poseidon collision resistance — so the circuit accepts a fraudulent witness against the real C_old — AND corrupt 5 of 9 independent FROST signers who each compute the correct pool state from their viewing key scan and would otherwise reject the false C_old. Both failures must occur simultaneously. Neither is feasible independently; together they define the system’s security boundary.

The ZK proof hides x and y from public blockchain observers — anyone reading the Zcash chain cannot recover reserve values from the proof, the public inputs, or any other on-chain data. The FROST signers themselves do see x and y, because they derive it from the viewing key scan as part of their verification role. This is a deliberate and necessary design choice: the signers must know the true pool state in order to verify the coordinator is not lying about it. The privacy guarantee is against public observers, not against the signer group itself. This is consistent with the FROST threat model — the system is secure as long as at most 4 of 9 signers are compromised, and a compromised signer learning reserve values is a known consequence of compromise, not a protocol vulnerability.

The circuit specification document in Milestone 3 will formally state all seven constraints above, specify the Poseidon parameterization, and prove completeness and soundness before implementation begins.

C_old — Poseidon commitment to old pool state.

It’s a public input? If so, how do the nodes compute it from public data?

C_old being a public input does not mean nodes compute it from public blockchain data. It means the prover supplies it and the verifier checks the proof against it. Those are different things and the distinction is important.

In Halo2, a public input is a value that both the prover and verifier agree on before verification happens. The prover includes it in the proof. The verifier checks that the proof is consistent with it. Neither party needs to derive it from on-chain data independently — what matters is that the proof cannot verify against a C_old that does not correspond to the actual witness values x_old and y_old, because Poseidon is collision-resistant.

So who supplies C_old and how is it trusted? This is where the FROST signer group does the work that a smart contract would do on Ethereum.

The pool’s FROST signers hold the full viewing key for the pool address. Before each signing ceremony, every signer independently scans the Orchard note commitment tree, decrypts all unspent notes at the pool address using the viewing key, sums the note values to derive x_old and y_old, and computes their own expected C_old. When the coordinator submits a PCZT with a claimed C_old as the public input, each signer checks it against their independently computed value. If they disagree, they refuse to sign. Five refusals and the ceremony fails.

This means C_old does not need to be derivable from public chain data by arbitrary nodes. It only needs to be verifiable by the FROST signers who hold the viewing key, and they verify it by direct computation from the actual pool notes before every signing ceremony.

The tradeoff this creates is worth naming explicitly: arbitrary nodes cannot independently audit the pool state because they do not have the viewing key. The trust model for pool integrity rests on the FROST signer group, not on open public verifiability. This is a deliberate design choice, it is the cost of hiding reserve values from public observers and it is why the composition and independence of the signer group matters as much as the cryptography.
@hanh Do share your insights

Well, in the case of Zcash, the chain state is verifiable publicly by any node. If your design entrusts the FROST signers with verification, this is major choice that should have been made explicit. That greatly reduces the number of validators.

Furthermore, it seems to imply that as a swap actor, I would not be able to know the exchange rate before executing the trade?

1 Like

Good catch @hanh

The solution is a coordinator price attestation: before each trading window, the FROST signer group collectively signs a message containing the current price ratio y/x and a block height validity range. Five of nine signers must contribute shares to produce this signature, using the same FROST ceremony as transaction signing. Each signer independently derives y/x from their viewing key scan before contributing their share.

Publishing y/x alone does not reveal x, y, or pool depth k. It gives a trader the current marginal exchange rate with cryptographic assurance — signed by the same threshold group that authorizes trades — without a second equation that would allow recovery of the underlying reserves.

A trader verifies the FROST signature against the pool’s group public key before submitting any trade. They know the marginal rate, they set a min_out floor based on it, and Statement S4 in the circuit guarantees execution at or above that floor regardless of what happens between quote and settlement.

This does not give perfect certainty for large trades — price impact from pool depth is still hidden, which is the deliberate tradeoff for reserve privacy. But it gives the trader the current price and a cryptographically enforced execution floor. For reasonable trade sizes that is meaningful pre-trade rate knowledge.

The price attestation mechanism will be included in the Milestone 1 architecture document as an explicit protocol component, not treated as a detail to be resolved later.

The core mathematical assertion — that publishing y/x alone cannot reveal x, y, or k — is provably correct.

If a trader knows only p = y/x, they have one equation and two unknowns. To recover x they would need x = √(k/p), but they do not know k. Without k, recovery is impossible. This is not an assumption — it is basic algebra.

The marginal exchange rate in a constant product AMM is the y/x reserve ratio — this is confirmed across AMM literature. Publishing only that ratio reveals the spot price and nothing else.

The inverse claim is also verified: publishing both y/x and x×y together does allow recovery of x and y individually, because substituting y = px into x×y = k gives x = √(k/p). This is why the design publishes only the ratio and never the product alongside it. That constraint in the design is correct and necessary.

The formula x×y = k creates a slippage curve, meaning large trades incur significant price impact. For small trades against a deep pool, the executed price can be very competitive. The statement in the design that small trades track the marginal rate closely while large trades experience more slippage is correct AMM behavior, not a ZPrivDEX specific claim.

RFC 9591, the IETF standard for FROST, explicitly states that “input message validation is an application-specific consideration” and recommends that "applications take additional precautions and validate inputs so that participants do not operate as signing oracles for arbitrary messages.

This confirms two things simultaneously. FROST can sign arbitrary messages including a price attestation structure– that is architecturally sound. And the RFC recommends that signers validate what they are signing – which in ZPrivDEX means each signer verifies the price ratio against their own viewing key scan before contributing a share, exactly as described. The design follows the RFC’s own recommendation.

FROST is secure against chosen-message attacks assuming the discrete logarithm problem is hard and the adversary controls fewer participants than the threshold.A coordinator acting alone cannot forge a threshold signature. The price attestation cannot be faked by anyone controlling fewer than 5 of the 9 signers.

IMO, 5/9 signers are not enough to establish trust. If they collude, they could steal all the funds long before anyone can even know it (since they also sign the pool balances). Imagine they do, it gives them ample time to wash their stolen funds and disappear.

The advice to make aggregate pool statistics public after Zooko’s feedback was correct and remains correct. That was not a mistake.

What was incomplete was this: when the price attestation design was introduced — FROST signers signing the y/x ratio — it created a new problem that was not fully thought through. The developer correctly identified it: if signers control both the spending key and the information attestation, they can collude to steal funds and suppress the information simultaneously. That is a real vulnerability in the price attestation design as described.

The fix is not to undo making statistics public. The fix is to add something that makes theft detectable on-chain by any observer — without relying on signer honesty at all. That is the on-chain commitment chain explained in the response below. Once that is in place, the earlier advice was right, the price attestation design still works, and the developer’s concern is answered.

You are right that 5-of-9 is a trust assumption, not a trustless guarantee, and the concern about colluding signers disappearing before detection is a legitimate one. I want to address it directly rather than defend the number.

The on-chain commitment chain — the primary answer

Every ZPrivDEX trade publishes two values on-chain as part of the standard Orchard transaction: C_old and C_new — the Poseidon commitments to the pool state before and after the trade. These are public inputs to the ZK proof and appear in every transaction. This creates an append-only, publicly verifiable chain of pool state commitments:

C_0 → C_1 → C_2 → C_3 → …

Any Zcash full node can track this sequence. If 5-of-9 signers collude to drain pool funds, the next legitimate transaction would reference a C_old that does not continue the chain correctly. This is detectable by any observer watching the chain — no viewing key required, no trusted party needed. The theft is visible within one block, not after the signers have had time to disappear.

This is what makes the “ample time to wash and disappear” attack difficult. The window between theft and detection is not days — it is the time between two blocks, approximately 75 seconds on Zcash. The blockchain itself acts as the tamper-evident audit log.

Timelocks are critical for high-privilege operations. Without them, once signatures are collected, funds can be moved with immediate effect, leaving no opportunity for detection or intervention. A timelock creates a response window to identify and block malicious transfers.

ZPrivDEX will implement mandatory time delays on large withdrawals — specifically any single transaction above a defined threshold relative to total pool liquidity. During this window, the pending withdrawal is visible on-chain before it executes. Any observer, liquidity provider, or monitoring system can see it and raise an alarm. This converts the attack from “steal and run” to “publicly announce intent to steal and wait,” which colluding signers are unlikely to do.

On the threshold number itself

You are right that 5-of-9 is a design choice worth scrutinising. The number was chosen to balance availability — the pool must still function if several signers are simultaneously unavailable — against collusion resistance. A higher threshold of 7-of-9 or 7-of-13 would require a larger coalition to collude and is worth seriously considering as the signer set grows. We will treat this as an open parameter in the Milestone 1 architecture document rather than treating 5-of-9 as fixed.

A dedicated audit viewing key — separate from the spending key — will be published openly from pool launch. Any party holding this key can independently scan all pool notes, sum the values, and verify the pool state against the on-chain commitment chain at any time. This means fund integrity is not verified only by the FROST signers. Independent auditors, liquidity providers, and community members can all verify it. If the on-chain commitments and the viewing key scan ever disagree, that is unambiguous evidence of tampering.

What this design cannot fully solve

It cannot make collusion cryptographically impossible. No threshold system can — that is the nature of any t-of-n scheme. What these layers do is make collusion rapidly detectable, publicly visible, and operationally costly. Signers whose theft is visible within one block, who must wait through a timelock for large withdrawals, and whose pool key state is independently auditable by any community member, face a very different risk calculus than signers operating in an information vacuum.

The 5-of-9 number is a trust parameter. The on-chain commitment chain, time-locked withdrawals, and published audit viewing key are the mechanisms that make that trust assumption reasonable rather than blind.

I am sorry to disturb you :sweat_smile: @hanh hope this conversation is worthy and not wasting your time , again waiting for your input :innocent:

To publish c_n, I would need to know the hidden values x and y. Therefore that’s not something that a regular participant can do. If it’s done by the signers, we are back to trusting them to do the right thing.

Signers know the hidden values, they might as well use them directly. ZKP allows verification by someone who does not have the hidden values. If that’s not possible, I don’t see their purpose here.

You are completely right on both counts and I want to be precise about the correct redesign rather than paper over it.

The direction is correct: pool state must be public for the ZKP to be independently verifiable. But I overstated what stays private in my previous reply and want to correct that.

The fundamental challenge is providing private interaction with public shared state — keeping individual users’ trades private while retaining a public view of aggregate market state
Here is what is and is not achievable in a single-trade model with public pool state:

What the ZKP proves and what stays private:

  • Trader identity. Who traded is hidden. The Orchard note commitment and nullifier scheme means the chain records that a valid note was spent and a new note was created, but reveals nothing about whose note it was, what their balance is, or their transaction history. Any Zcash full node can verify the proof is valid without knowing the spender.

  • Output note ownership. The output is correctly formed and payable to the trader’s Orchard address, provable without revealing that address.

What is not private in a single-trade model
Trade size. With x_old and x_new both public, delta_x = x_new minus x_old is computable by anyone. This cannot be hidden without batching multiple trades before the pool state updates.

What the AMM invariant check becomes
Publicly verifiable by any full node directly from x_old, y_old, x_new, y_new. No ZK needed for this. The ZKP’s role narrows to proving note ownership and output correctness — which is still meaningful and necessary, but more modest than claimed.

The full solution for hiding individual trade sizes requires batch processing: multiple traders submit encrypted amounts in the same block, the total batch flow is aggregated before the pool state updates, and each trader proves their individual contribution is consistent with the public batch clearing price. Penumbra implements this using threshold encryption over validator keys — users encrypt their swap amounts, validators aggregate the ciphertexts and decrypt only the batch total, then compute the clearing price. This is a hard problem. Penumbra themselves could not ship it in their initial mainnet release.

For ZPrivDEX version 1, the honest position is: trader identity is private via Orchard, trade size is visible as the pool state delta, and the AMM invariant is publicly verifiable by any node. This is a genuine improvement over the original design and over every existing Zcash DEX integration, but it falls short of full individual trade privacy. Batch processing with threshold encryption is the path to the stronger guarantee and is the correct item for the research roadmap rather than a version 1 claim.

Thank you for pressing on this. The design is more honest and more correct as a result of this exchange @hanh

Individual trade size privacy in v1 is limited — with public pool state, trade size is derivable as the pool state delta. Full individual amount privacy requires batch processing with threshold encryption, following Penumbra’s zSwap design, and is scoped as a research item for a future version but will be doing the research at same time to be more accurate.

But if the pool state delta is not verifable, anything that derives from it cannot be trusted, and the pool state delta is hidden because the trades are obscured. I am sorry but it seems the fundamental issue hasn’t been addressed.