← Blog

ZPay Enterprise: Building a Privacy-First Web3 Wallet with Rust and Zcash Orchard

2025-12-15

rustweb3zcashprivacyhalo2wallet

ZPay Enterprise: Building a Privacy-First Web3 Wallet with Rust and Zcash Orchard

In the world of enterprise finance, privacy isn't a luxury — it's a requirement. When a company pays vendors, manages payroll, or moves treasury funds on-chain, every transaction is broadcast to the entire world on public blockchains like Ethereum. Competitors can track spending patterns, employees can see each other's salaries, and attackers can identify high-value targets. This is fundamentally incompatible with how businesses operate.

ZPay Enterprise was born from this problem. It's a full-stack, multi-chain wallet that combines the programmability of Ethereum with the cryptographic privacy guarantees of Zcash's Orchard protocol — all wrapped in an enterprise-ready platform with role-based access control, audit logging, and encrypted key management.

In this post, I'll walk through the architecture, the cryptographic internals, and the engineering decisions behind ZPay Enterprise.

Why Enterprises Need Privacy Wallets

Public blockchains create a paradox for businesses: they provide trustless settlement but zero confidentiality. Consider these scenarios:

  • Payroll: Every employee's compensation is visible to anyone who maps wallet addresses to identities.
  • Vendor payments: Competitors can reverse-engineer your supply chain by tracing on-chain payments.
  • Treasury management: Large holdings make you a target for social engineering and exploits.
  • M&A activity: On-chain movements can signal deals before they're announced.

Traditional solutions like mixers are inadequate — they offer probabilistic privacy, not cryptographic guarantees, and they carry regulatory risk. Zcash's shielded pool, built on zero-knowledge proofs, provides mathematically provable privacy while remaining compliant through selective disclosure via viewing keys.

ZPay Enterprise bridges this gap: use Ethereum for DeFi and ERC20 tokens, use Zcash Orchard for confidential transfers, and manage it all through a single enterprise dashboard.

Technical Architecture

The system follows a clean three-tier architecture:

┌─────────────────────────────┐
│   React 19 Frontend (SPA)   │
│   TailwindCSS + Vite        │
├─────────────────────────────┤
│   Rust Backend (Actix-web)  │
│   REST API + WebSocket      │
├─────────────────────────────┤
│   MySQL 8.0 + Redis Cache   │
│   Encrypted Key Storage     │
└─────────────────────────────┘
        │              │
   Ethereum RPC    Zcash lightwalletd

Why Rust for the backend? In a wallet system, the backend handles private keys, constructs transactions, and manages cryptographic operations. Memory safety isn't optional — a single buffer overflow could leak keys. Rust's ownership model eliminates entire classes of vulnerabilities at compile time, and its performance characteristics mean we can run Halo 2 proof generation without spinning up GPU infrastructure.

Why Actix-web? It consistently benchmarks as one of the fastest HTTP frameworks across any language. For a wallet API that needs to handle concurrent transaction signing and proof generation, throughput matters. Actix's actor model also maps naturally to per-wallet isolation.

The frontend is a React 19 SPA with the new concurrent features, using TailwindCSS for styling. It communicates with the backend exclusively through a typed REST API, with WebSocket connections for real-time balance updates and transaction confirmations.

Multi-Chain Wallet Design

ZPay Enterprise supports both Ethereum and Zcash through a modular chain abstraction layer. Each chain is implemented as a separate module behind a common trait:

#[async_trait]
pub trait ChainWallet: Send + Sync {
    async fn create_wallet(&self, user_id: &str) -> Result<WalletInfo>;
    async fn get_balance(&self, address: &str) -> Result<Balance>;
    async fn send_transaction(&self, req: TransferRequest) -> Result<TxHash>;
    async fn get_transaction_history(&self, address: &str) -> Result<Vec<Transaction>>;
}

pub struct EthereumWallet {
    provider: Arc<Provider<Http>>,
    chain_id: u64,
    key_store: Arc<EncryptedKeyStore>,
}

pub struct ZcashWallet {
    lightwalletd_url: String,
    network: NetworkType,
    key_store: Arc<EncryptedKeyStore>,
}

This design means adding a new chain (say, Solana or a Bitcoin variant) requires implementing a single trait without touching any existing code. The wallet router dispatches requests based on a chain identifier in the API:

#[post("/api/v1/{chain}/transfer")]
async fn transfer(
    path: web::Path<String>,
    body: web::Json<TransferRequest>,
    wallets: web::Data<WalletRegistry>,
) -> Result<HttpResponse> {
    let chain = path.into_inner();
    let wallet = wallets.get(&chain)?;
    let tx_hash = wallet.send_transaction(body.into_inner()).await?;
    Ok(HttpResponse::Ok().json(TransferResponse { tx_hash }))
}

Zcash Orchard Protocol: Halo 2 Zero-Knowledge Proofs

The heart of ZPay Enterprise's privacy capabilities is the Zcash Orchard protocol. Unlike earlier Zcash protocols (Sprout and Sapling), Orchard uses Halo 2 — a zero-knowledge proof system that requires no trusted setup. This is critical for enterprise adoption: no ceremony participants to trust, no toxic waste to worry about.

How Shielded Transactions Work

In a shielded transaction, the sender proves they own enough funds and that the transaction is valid — without revealing the sender, receiver, or amount. The proof is verified by every node on the network, but the underlying data remains encrypted.

Orchard uses the Pallas/Vesta elliptic curve cycle, which enables efficient recursive proof composition. Each note (the Zcash equivalent of a UTXO) is committed using a Sinsemilla hash and stored in a Merkle tree. Spending a note requires producing a Halo 2 proof that:

  1. The note exists in the commitment tree (membership proof)
  2. The spender knows the spending key (authorization proof)
  3. The nullifier is correctly derived (double-spend prevention)
  4. Input values equal output values plus fees (balance proof)

Four Transfer Modes

ZPay Enterprise supports all four Zcash transfer modes, giving enterprises flexibility in how they manage privacy:

| Mode | From | To | Privacy Level | Use Case | |------|------|----|---------------|----------| | T→T | Transparent | Transparent | None (public) | Regulatory compliance, auditable transfers | | T→Z | Transparent | Shielded | Receiver hidden | Receiving confidential payments | | Z→Z | Shielded | Shielded | Full privacy | Internal transfers, payroll | | Z→T | Shielded | Transparent | Sender hidden | Payments to public addresses |

The implementation handles the complexity of each mode transparently:

pub async fn execute_transfer(
    &self,
    from: &Address,
    to: &Address,
    amount: Amount,
    memo: Option<Memo>,
) -> Result<TxId> {
    match (from.is_shielded(), to.is_shielded()) {
        (false, false) => self.transparent_transfer(from, to, amount).await,
        (false, true)  => self.shielding_transfer(from, to, amount, memo).await,
        (true, true)   => self.shielded_transfer(from, to, amount, memo).await,
        (true, false)  => self.deshielding_transfer(from, to, amount).await,
    }
}

For Z→Z transfers, the system constructs an Orchard bundle with the appropriate actions, generates the Halo 2 proof (which takes approximately 2-5 seconds on modern hardware), and broadcasts the transaction through lightwalletd.

Security Design

Encrypted Private Key Storage

Private keys are never stored in plaintext. ZPay Enterprise uses AES-256-GCM authenticated encryption with per-key random nonces:

pub struct EncryptedKeyStore {
    master_key: [u8; 32],  // Derived from HSM or KMS
    db: Arc<MySqlPool>,
}

impl EncryptedKeyStore {
    pub fn encrypt_key(&self, private_key: &[u8]) -> Result<EncryptedKey> {
        let nonce = generate_random_nonce();  // 96-bit random
        let cipher = Aes256Gcm::new(GenericArray::from_slice(&self.master_key));
        let ciphertext = cipher.encrypt(
            GenericArray::from_slice(&nonce),
            private_key,
        )?;
        Ok(EncryptedKey { nonce, ciphertext })
    }

    pub fn decrypt_key(&self, encrypted: &EncryptedKey) -> Result<Vec<u8>> {
        let cipher = Aes256Gcm::new(GenericArray::from_slice(&self.master_key));
        let plaintext = cipher.decrypt(
            GenericArray::from_slice(&encrypted.nonce),
            &encrypted.ciphertext[..],
        )?;
        Ok(plaintext)
    }
}

The master key itself is derived from an external KMS (AWS KMS or HashiCorp Vault in production). Keys are decrypted only in memory, only when needed for signing, and the plaintext is zeroized immediately after use via the zeroize crate.

RBAC Permission Control

Enterprise wallets need granular access control. ZPay Enterprise implements role-based access control with four default roles:

  • Admin: Full system access, key management, role assignment
  • Treasurer: Can initiate and approve transfers above threshold
  • Operator: Can initiate transfers below threshold, view balances
  • Auditor: Read-only access to transaction history and audit logs

Every API endpoint is guarded by a middleware that checks the caller's role against the required permission:

#[derive(Debug, Clone)]
pub enum Permission {
    WalletCreate,
    WalletView,
    TransferInitiate,
    TransferApprove,
    AuditLogView,
    UserManage,
}

pub async fn require_permission(
    req: &HttpRequest,
    permission: Permission,
) -> Result<UserContext> {
    let token = extract_jwt(req)?;
    let user = validate_token(&token)?;
    if !user.has_permission(&permission) {
        return Err(Error::Forbidden);
    }
    Ok(user)
}

ERC20 Token Management

On the Ethereum side, ZPay Enterprise provides first-class support for major ERC20 tokens: USDT, USDC, DAI, and WETH. The system maintains a token registry with contract addresses, decimals, and ABI information for each supported network (mainnet, Sepolia, Arbitrum, etc.).

Token transfers use the standard ERC20 transfer function, with the backend constructing and signing the transaction:

pub async fn transfer_erc20(
    &self,
    token: &TokenInfo,
    from: &Address,
    to: &Address,
    amount: U256,
) -> Result<TxHash> {
    let contract = ERC20Contract::new(token.address, self.provider.clone());
    let tx = contract.transfer(to, amount);
    let gas = self.estimate_gas_eip1559(&tx).await?;
    let signed = self.sign_transaction(from, tx.gas(gas)).await?;
    let pending = self.provider.send_raw_transaction(signed).await?;
    Ok(pending.tx_hash())
}

EIP-1559 Gas Estimation

ZPay Enterprise implements EIP-1559 gas estimation with a priority fee strategy optimized for enterprise use — reliable confirmation within 1-2 blocks without overpaying:

pub async fn estimate_gas_eip1559(&self, tx: &TypedTransaction) -> Result<GasEstimate> {
    let base_fee = self.provider.get_base_fee().await?;
    let max_priority_fee = self.get_priority_fee_estimate().await?;
    let max_fee = base_fee * 2 + max_priority_fee; // 2x buffer for base fee volatility

    Ok(GasEstimate {
        max_fee_per_gas: max_fee,
        max_priority_fee_per_gas: max_priority_fee,
        gas_limit: self.provider.estimate_gas(tx).await? * 120 / 100, // 20% buffer
    })
}

Enterprise Features

Audit Logging

Every significant action in ZPay Enterprise is logged to an immutable audit trail. This includes wallet creation, balance queries, transfer initiation, approval workflows, and configuration changes. Each log entry captures the actor, action, target, timestamp, IP address, and a SHA-256 hash chaining to the previous entry (creating a tamper-evident log):

pub struct AuditEntry {
    pub id: u64,
    pub timestamp: DateTime<Utc>,
    pub actor_id: String,
    pub action: AuditAction,
    pub target: String,
    pub details: serde_json::Value,
    pub ip_address: IpAddr,
    pub prev_hash: String,
    pub entry_hash: String,
}

Multi-Signature Approval Workflows

For transfers above configurable thresholds, ZPay Enterprise supports M-of-N approval workflows. A treasurer initiates the transfer, and it enters a pending state until the required number of approvers sign off. This maps directly to how enterprises manage financial controls.

Conclusion

Building ZPay Enterprise reinforced a core belief: privacy and compliance are not opposing forces. With Zcash's viewing keys, enterprises can maintain full privacy for day-to-day operations while providing auditors and regulators with selective access when required. The Halo 2 proof system eliminates trusted setup concerns, and Rust's safety guarantees provide the foundation for handling cryptographic material responsibly.

The combination of Ethereum's token ecosystem with Zcash's shielded pool creates a powerful toolkit for enterprise treasury management. As regulatory clarity around privacy-preserving technologies improves, systems like ZPay Enterprise will become essential infrastructure for on-chain business operations.

Check out the full source code at github.com/robustfengbin/zpay-enterprise.