chalice is my design for censorship-resitant cross-chain mixer-bridge.
originally i worked on hideyour.cash which was a groth16 mixer deployed on near. we developed the necessary libraries and deployed the first zk verifier on near. however that was very close to the tornado.cash sanction, and sentiment was tense around that time. we even developed a few compliance solutions, namely withdrawal screening via on-chain kyt (freezing funds in case of fraud), and deposit-screening by pki credentials.
recent designs provide better solutions than what we did back then (e.g. privacy pools allows for rage-quit instead of freezing funds; some allow users to collude to selective deanonymize certain actors; many protocols support better, more widely available, zk credentials such as zkpassport)
the cornerstone of chalice is the idea of cross-chain anonimity sets. through an mpc network such as near's, which provides chain signatures based on smart-contract logic, we can embue privacy potential into any transparent transfer on some chain, tapping into a bigger anonimity set.
our key example is how we can create a private intent that freezes zcash that can be claimed by proving a transaction on solana.
- originator and counterparty agree on an intent constituted by: recipient chain/address, counterparty, token pair, quote
- originator locks his zcash by sending it to an address controlled by near mpc
- counterparty waits for confirmation of the funds being locked on zcash
- counterparty transfers to the recepient the amount specified on the quote (both data already committed to)
- near light-client catches up to zcash, bitcoin, ethereum current blocks
- near light-client eventually receives wormhole message of the current solana block-hash
- counterparty proves inclusion of transaction
- public inputs:
- { zcash, bitcoin, ethereum, solana } last N block hashes
- exclusion list (e.g. sanctioned addresses)
- private inputs:
- original intent
- inclusion proof of intent commitment on zcash
- inclusion proof of counterparty transaction on solana
- circuit verifies:
- inclusion proofs well-formed
- locked funds of commitment tx matches the intent
- counterparty tx matches the intent
- no address is included in the exclusion list
- public inputs:
- near mpc emits a chain-signature, allowing counterparty to take the funds
as a result you can trade your zcash for any other transparent token, on any supported chain, without leaving any trace that your new transparent funds where bought with coins traceable to a mixer.
so not only can you onboard to zcash without leaving a trail, you can offboard off of zcash in a new address not tied to privacy protocols at all. and even more:
- you can simply use this as a private cross-chain escrow, but still trade transparently (maybe with stealth addresses)
- you can use zcash to fund a brand new wallet on any chain (you could further specify that you only want recently minted coins!)
- you can trade tokens between mixers on any chains (allowing you to boost anonimity in one of them)
- funds have to be locked for some time, counterparty may try to squeeze free option out of originator, effectively censoring (ironic huh? but this has a solution)
- compliance can still be applied, the only new primitive is connecting different chains through private intents, but on the destination chains the smart-contracts are still normal mixers which can borrow from any of the aforementioned complicance solutions.
- we are using near as the smart-contract layer for other chains which may not support such features (i.e. zk verification) such as zcash and bitcoin. were near mpc to be hacked, the chain signatures could be emitted without going through the verification step on the smart-contract. we still can use their scripting capabilities to introduce hack resistance mechanisms (e.g. freezing, allowing users to rage-quit in time to escape the hack).
- we could make it so claiming the note from the pool is a different signature that doesn't reveal the intent, all withdrawals take a long time, but withdrawals revealing their intent commitment go through instantly (a rage-quit in contrast would be the specific case where it would go back to the originator, if it failed deposit screening for example)
Blockchain was meant to be censorship-resistant, and yet when Tornado Cash got sanctioned, many validators were censoring the app. All offramps were censoring its users.
Chalice fixes this by making it so every transaction, on every chain, is on the anonymity set. No one can censor or track this. The anonymity set is instantly too big.
You can swap your private coin for a public one leaving no trace, anywhere. All thanks to NEAR MPC Chain Signatures + Light-Clients.
Use-cases:
- buy ZCash with Solana without anyone knowing you are doing privacy transactions
- offboard from ZCash to pay for real-life stuff on Solana/Bitcoin without anyone realizing your money came from a privacy chain
- coordinate transactions between multiple different chains leaving no trace, for example to farm anonymity for some other mixer
NEAR Intents is great, we all love it — but it leaks too much.
By leveraging just the right pieces from it — NEAR MPC Chain Signatures + ZCash Light-Client — we can provide a similar experience but with a way better privacy story.
Not only that but we are effectively adding mixer functionality to chains which don't necessarily support smart-contracts (i.e. Bitcoin), making it effectively a Bitcoin mixer too.
For more technical details, read our README. It has technical diagrams and explanations.
My team built a mixer, the first ZK app on NEAR, back in 2022 (very close to the Tornado sanction) for the NEARCON 22 hackathon. We built compliance into it... but that didn't make anyone less scared to use it.
That honestly was such a huge letdown for us, we spent so fucking long building the circuits and the verifiers, and thinking withdrawal screening + other techniques fixed the North Korean hacking threat-model but none of that mattered
Not only were people tracked and censored, that also meant the anonymity set was worse quality. Only criminals wanted to use it, users were left with no privacy options. Of course nowadays it's different: regulations changed, we have privacy pools, other compliant options etc... but what if?
That's why I built Chalice. Our privacy shouldn't hinge on what ifs. And this is not a cypherpunk delusion: Chalice can support the same compliance solutions as privacy pools, zkp2p and others
You just trust NEAR MPC and in return for it you get the anonymity set as large as you want to disclose it as.
Censorship... Fixed. Compliance? Uncompromised.
had to port UltraHonk Verifier to wasm32-unknown-unknown so it could verify directly on NEAR (using Aurora was an alternative but that would incur in extra latency by the cross-contract call and also it wasn't clear it was going to take less gas, and it was close to the limit)
this is very special to me because 3 years ago on NEARCON 22 me and my team (hack-a-chain) shipped the Groth16 Verifier for NEAR. Years later the only verifiers deployed are still my team's Groth16 and PlonK verifiers, but now I'm adding UltraHonk to the list
i think this was the first project to verify a zk solana inclusion proof of all transactions. i had to use wormhole queries to sync block hashes to NEAR, and then run a geyzer plugin to send all the entry hashes that could be proved on the contract, and assemble a merkle tree out of it.
this solana merkle tree allows us to make zk proof of inclusion of any solana transaction, and then we combine it with the zcash (and potentially bitcoin) merkle tree too to provide cross-chain anonymity set
had to build a web multi-chain wallet with proving capabilities, so the demo would be seamless showcasing how streamlined the UX for this approach could get.
I didn't want to open up a vector of attack where people would snoop on other people trying to get their private intents matched without committing to fill them on the other side
that's why I wanted to build a match-making engine inside a TEE, which's the perfect fit to provide this transient privacy and match-making. Not even the TEE could learn about intents that don't get filled.
Users just provide hints by sharing their quotes for the pairs they are interested at, but since the quotes never get publicized (only the ZCash intent originator amount), nothing leaks that could link to the on-chain identity of the users.
Too many chains synchronizing their light-clients at different speeds, it would be impossible for a user to assemble the merkle tree locally, generate the proof, and get it included before the merkle root changes.
It required a threefold fix:
- a standard incremental merkle tree (constant-size)
- a ring buffer for recent roots
- a prefix trick: every chain's merkle tree gets a specific prefix in the merkle tree. For my taker txs anonymity set I currently have Bitcoin and Solana so I had Bitcoin be 0 and Solana be 1. To add more chains we could have longer prefixes (e.g. Solana becomes 10 and Eth becomes 11)
As I explain on this tweet if you want to have a ZK (private) proving system that proves on mobile and verifies on-chain, well... Tough luck.
I think the only other alternative to Aztec's Noir would have been Miden. A great benefit of Miden is that it already compiles the verifier to wasm32-unknown-unknown, so it probably works with NEAR natively. Also it's actually a zkVM so for my branching multi-chain anonymity set it would have probably have saved some proving time... Who knows?
That being said I was offput by the potentially big memory requirement for the proofs, but didn't bother benchmarking. I took the maybe weird looking decision of preferring to port the verifier rather than going off to benchmark Miden and stuff, but honestly maybe Miden would've worked more easily for this use.
I opted for Barretenberg/UltraHonk because I knew that barring porting the verifier, everything else pretty much worked.
Install these tools (all available via package managers):
# Required
rustup # rust toolchain (1.86.0 for NEAR contracts)
cargo-near # NEAR contract build tool
just # Command runner (like make)
noir/nargo # Noir language & compiler
barretenberg # ZK proof backend (bb CLI)
solana # Solana CLI tools
node/npm # JavaScript runtime (v18+)
# Optional (for full dev environment)
docker # For ZCash nodequick install:
# rust + near tools
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
rustup toolchain install 1.86.0
cargo install cargo-near
cargo install just # or: brew install just
# noir + barretenberg
curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash
noirup
# solana (optional - only needed for test-noir-simple)
sh -c "$(curl -sSfL https://release.solana.com/stable/install)"just test # all rust tests
just test-verifier # ultrahonk verifier only
just noir-test # noir circuits onlytests the core components: bn254 field arithmetic, pairing operations, ultrahonk verification algorithm, and noir circuit logic.
just dual-fullgenerates and verifies a proof that includes both a zcash transaction in a block merkle tree and a solana transaction in an entry merkle tree. ~532k gates, fully self-contained test.
requires solana validator with geyser plugin streaming entry data to near contracts, then verifying inclusion proofs in noir circuits.
# start environment (zcash, solana validator with geyser, near sandbox)
just devenv-start
# run integration test (typescript + near + noir, all matching)
just devenv-test
# or test just the noir inclusion proofs with real solana data
just test-noir-simplethis tests the complete flow: geyser plugin captures solana entries → typescript computes merkle roots → near contract verifies same merkle roots → noir circuit proves inclusion → all three implementations produce identical results.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ ZCash │ │ Solana │ │ Noir │
│ (Private) │ │ (Fast) │ │ (Proofs) │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ │ │
└───────────────────┴───────────────────┘
│
┌──────▼──────┐
│ NEAR │
│ (Verify) │
└─────────────┘
-
UltraHonk Verifier (
crates/near-ultrahonk-verifier/)- rust port of Barretenberg's Solidity verifier
- Uses NEAR's alt_bn128 precompiles (add, mul, pairing)
- Supports Keccak transcript for compatibility
-
Slot Receiver Contract (
crates/slot-receiver-contract/)- Receives Solana slot data via Wormhole Queries
- Verifies entry merkle trees
- Stores merkle roots for inclusion proofs
-
ZCash Indexer (
crates/zcash-indexer-contract/)- Indexes ZCash/Bitcoin transactions
- Verifies block merkle proofs
- Syncs via BTC light client
-
Noir Circuits (
noir-circuits/)inclusion-nr/: Single-chain merkle inclusiondual-inclusion-nr/: ZCash + Solana dual proof- Test circuits for verification
-
Geyser Plugin (
crates/solana-entry-indexer/)- Streams Solana PoH entries in real-time
- Captures entry hashes and transaction data
- Outputs to JSONL for syncing
# Build all rust crates
just check
# Build NEAR contracts
just near-build-all
# Build Noir circuits
just noir-execute # Inclusion circuit
just dual-execute # Dual inclusion circuit# Run all rust tests
just test
# Test simple Noir proof on NEAR
just test-noir-simple
# Run specific tests
just test-verifier # UltraHonk verifier tests
just test-indexer # ZCash indexer tests
just noir-test # Noir circuit tests
# Integration tests
just test-integration # NEAR contract integration
just devenv-test # Full stack integration# View all available commands
just
# Clean build artifacts
just clean
# Show project structure
just treechalice/
├── crates/
│ ├── near-ultrahonk-verifier/ # Core ZKP verifier (Rust)
│ ├── verifier-contract/ # NEAR UltraHonk contract
│ ├── slot-receiver-contract/ # Solana entry receiver
│ ├── zcash-indexer-contract/ # ZCash transaction indexer
│ ├── solana-entry-indexer/ # Geyser plugin
│ └── near-bn254-pairing/ # BN254 precompile wrappers
├── noir-circuits/
│ ├── inclusion-nr/ # Merkle inclusion circuit
│ ├── dual-inclusion-nr/ # Dual-chain inclusion
│ └── test-circuits/ # Test circuits
├── devenv/
│ ├── start-dev-env.sh # Orchestration script
│ ├── scripts/ts/ # TypeScript integration tests
│ └── SPEC.md # Technical specification
├── Justfile # Build commands
└── README.md # This file
devenv/SPEC.md- Full technical specificationdocs/ULTRAHONK_VERIFIER_PORT.md- Porting journeyentry_merkle.md- Entry merkle tree specification- Individual crate READMEs for detailed API docs
- ✅ UltraHonk verifier working on NEAR
- ✅ Entry merkle trees verified across all implementations
- ✅ Integration tests passing
- ✅ Dual inclusion circuit (532k gates)
- ✅ Wormhole Queries integration
- ⏳ PoH chain verification (under investigation)
- 🔜 Production deployment
- 🔜 Frontend dApp
MIT
