t2z
libraries for transparent β shielded txs (pczt)
Created on 3rd December 2025
β’
t2z
libraries for transparent β shielded txs (pczt)
The problem t2z solves
Zcash offers best-in-class privacy through shielded transactions, but most users hold funds in transparent addresses, whether from exchange withdrawals, mining payouts, or legacy wallets. These transparent balances are fully public, just like Bitcoin.
Moving funds from transparent to shielded (T2Z) currently requires:
- Running a full Zcash node, or
- Using specific wallet software with built-in proving capabilities
This creates friction. Developers building wallets, exchanges, or payment services have no lightweight way to offer "shield your funds" functionality without heavy infrastructure.
What T2Z Solves
T2Z is a portable SDK for transparent-to-shielded Zcash transactions.
It packages everything needed to construct, prove, and sign T2Z transactions into a library that runs anywhere:
| Platform | Integration |
|---|---|
| Browsers | WebAssembly (3.7 MB) |
| Node.js | WebAssembly |
| Go | UniFFI bindings |
| Kotlin/Android | UniFFI bindings |
| iOS/Swift | UniFFI bindings |
No Downloads, No Trusted Setup
Unlike Sapling (which requires ~50 MB parameter files from a trusted setup ceremony), T2Z uses Orchard with Halo 2βthe proving key is built programmatically. Your users never download anything extra.
External Signing Support
T2Z implements ZIP 374 (PCZT)βa standard format for partially constructed transactions. This enables:
- Hardware wallet integration β get the sighash, sign externally, attach signature
- Multi-party workflows β different entities can handle construction, proving, and signing
- HSM/custodial setups β keys never leave secure environments
Use Cases
π¦ Exchanges & Custodians
Offer a "Shield Funds" button. Users withdraw to transparent, then one-click shield to Orchard. Reduces transparent address reuse and improves user privacy.
π± Mobile Wallets
Add T2Z via Kotlin/Swift bindings. Let users shield transparent balances received from exchanges without running a full node.
π Web Wallets & dApps
Run T2Z directly in the browser. Users can shield funds without installing anythingβjust a web page.
π Privacy-Preserving Payments
Accept transparent payments (for compatibility), then automatically shield to your Orchard address. Incoming payment sources remain unlinkable to your shielded balance.
π οΈ Developer Tooling
Build transaction construction services, testing infrastructure, or wallet backends with a clean, audited API that handles the ZK complexity for you.
Example Flow
1. User has 1.5 ZEC in transparent address (from Coinbase withdrawal) 2. Your app calls T2Z to construct a shielding transaction 3. T2Z builds the PCZT, generates Orchard proofs (~10s first time, cached after) 4. User signs (or hardware wallet signs) 5. Broadcast β funds now in shielded Orchard address 6. Balance is private, unlinkable to the original transparent deposit
Why Not Just Use zcashd?
| zcashd | T2Z | |
|---|---|---|
| Runs in browser | β | β |
| Mobile-friendly | β | β |
| Parameter downloads | ~50 MB | None |
| Full node required | Yes | No |
| External signing | Limited | Full PCZT support |
| Binary size | ~100 MB | ~3.7 MB (WASM) |
Security Model
- No private keys stored β T2Z is stateless; you provide keys per-operation
- Signature verification β
append_signature
verifies before accepting - Pre-sign verification β
verify_before_signing
detects PCZT tampering - ZIP compliance β implements ZIP 244 (sighash), ZIP 317 (fees), ZIP 374 (PCZT)
T2Z makes Zcash privacy accessible everywhere without infrastructure overhead or trusted setup ceremonies.
Implemented per spec from Kris Nuttycombe
Challenges I ran into
WASM Compilation Hell
Getting Zcash crypto to compile to WebAssembly was harder than expected.
secp256k1 has C code. That C code needs to compile to WASM, which means you need wasi-libc and a proper LLVM toolchain. On macOS, the system clang doesn't work. I spent two days fighting cryptic linker errors before realizing every piece of the toolchain needs to be Homebrew LLVM:
brew install llvm export CC=/opt/homebrew/opt/llvm/bin/clang export AR=/opt/homebrew/opt/llvm/bin/llvm-ar
Halo 2 needs threads. The proving system uses parallel computation, which requires WASM atomics. Standard Rust doesn't enable these, so you need nightly with special flags:
# .cargo/config.toml [target.wasm32-unknown-unknown] rustflags = ["-C", "target-feature=+atomics,+bulk-memory,+mutable-globals"] [unstable] build-std = ["panic_abort", "std"]
This rebuilds the Rust standard library with atomics. It's still unstable in late 2025. No way around nightly for now.
Browsers block SharedArrayBuffer by default. Even after getting it to compile, nothing worked until I added security headers:
Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp
Three hours of debugging "SharedArrayBuffer is not defined" before I found this.
The PCZT API Gap
ZIP 374 defines PCZT for passing around partially-signed transactions. I needed two things for hardware wallet support:
- Get the sighash for external signing
- Attach the signature afterward
The
pczt
crate doesn't expose either. TheSigner
role signs internally. Thepartial_signatures
field is private.My workaround: shadow structs.
PCZT uses postcard for serialization. I created structs that match the exact internal layout:
#[derive(Serialize, Deserialize)] pub struct TransparentInputShadow { pub prevout_txid: [u8; 32], pub prevout_index: u32, // ... fields in exact order ... pub partial_signatures: BTreeMap<[u8; 33], Vec<u8>>, // the field I needed // ... }
Now I can deserialize a PCZT, modify
partial_signatures
, and re-serialize. It's fragile since any upstream changes break it silently, but it works. I added round-trip tests to catch breakage.For sighash computation, I dug into
zcash_primitives
and foundv5_signature_hash
is public. Building the right inputs took some spec reading, but it worked.Summary
| Problem | Fix | Time wasted |
|---|---|---|
| secp256k1 C code | Homebrew LLVM + wasi-libc | 2 days |
| WASM atomics | Nightly + build-std | 1 day |
| SharedArrayBuffer | HTTP headers | 3 hours |
| PCZT private fields | Shadow structs | 1 day |
The Zcash crates are designed for full nodes and wallets, not "bring your own signing" scenarios. Until the APIs open up, shadow structs and nightly toolchains are the answer.
Tracks Applied (9)
General Bounty
Network School
Privacy Infrastructure & Developer Tools
Electric Coin Company
Private Payments & Transactions
Osmosis
Privacy Infrastructure & Developer Tools
Zcash Community Grants
Self-Custody & Wallet Innovation
Unstoppable Wallet
Privacy Infrastructure & Developer Tools
Raybot
Generic Bounty
Mintlify
General Bounty
Project Tachyon
Private Payments & Transactions
Star Fun
Technologies used
Cheer Project
Cheering for a project means supporting a project you like with as little as 0.0025 ETH. Right now, you can Cheer using ETH on Arbitrum, Optimism and Base.
