Audit Reports
Security audit reports for ZKMix
Audit Reports
Security auditing is a critical part of the ZKMix development process. All on-chain programs, zero-knowledge circuits, and critical off-chain infrastructure have been submitted for independent security review by professional auditing firms. This page summarizes the audit findings, their severity, and the remediation status for each.
Audit Summary
| Audit | Firm | Date | Scope | Critical | High | Medium | Low | Info |
|---|---|---|---|---|---|---|---|---|
| Solana Programs v1.0 | OtterSec | Q1 2025 | Mixer & Verifier programs | 0 | 1 | 3 | 5 | 8 |
| ZK Circuits v1.0 | Zellic | Q1 2025 | Withdrawal circuit, Poseidon | 0 | 0 | 1 | 2 | 4 |
| Relayer & SDK | Trail of Bits | Q2 2025 | Relayer server, SDK crypto | 0 | 0 | 2 | 3 | 6 |
All critical and high-severity findings have been fully remediated. The complete reports are linked at the bottom of this page.
OtterSec -- Solana Programs Audit
Scope: Mixer Program and Verifier Program (Anchor/Rust), approximately 3,200 lines of code.
Duration: 4 weeks (January -- February 2025)
Auditors: 3 senior security researchers with Solana-specific expertise.
Findings
HIGH: Missing account ownership check on Merkle tree in withdraw instruction
Severity: High | Status: Remediated
The withdraw instruction did not verify that the provided merkle_tree account was owned by the Mixer Program. An attacker could supply a crafted account with a malicious root, bypassing the root validation check. This was fixed by adding an explicit owner constraint on the Merkle tree account in the Withdraw accounts struct.
Fix: Added constraint = merkle_tree.owner == program_id to the account validation.
MEDIUM: Integer overflow in fee calculation
Severity: Medium | Status: Remediated
The relayer fee subtraction denomination - relayer_fee could underflow if relayer_fee > denomination due to a missing bounds check. While the circuit constrains the fee to be less than the denomination, a malformed proof could theoretically bypass this at the program level. The fix adds an explicit check that relayer_fee < denomination before performing the subtraction.
MEDIUM: Merkle tree root history not bounded
Severity: Medium | Status: Remediated
The root history vector could grow unboundedly if root_history_size was set to a very large value, potentially exceeding Solana's account size limits. The fix caps root_history_size at 1,000 during pool initialization.
MEDIUM: Lack of event emission for administrative actions
Severity: Medium | Status: Remediated
Administrative instructions (update_pool_state, update_verifier_key) did not emit events, making it difficult to audit governance actions on-chain. Events were added for all administrative state changes.
LOW Findings (5)
- L-01: Unused error variant
InvalidTokenMintin the error enum. Remediated. - L-02: Redundant account re-derivation in
depositinstruction. Remediated. - L-03: Missing
msg!()logging in error paths for debugging. Acknowledged. - L-04: Inconsistent use of
checked_addvs. unchecked arithmetic. Remediated. - L-05: Test coverage below 80% for edge cases in Merkle tree operations. Remediated.
Zellic -- ZK Circuits Audit
Scope: Withdrawal circuit (circom), Poseidon hash implementation, Merkle tree membership proof circuit. Approximately 800 lines of circom code.
Duration: 3 weeks (February 2025)
Auditors: 2 researchers specializing in zero-knowledge cryptography.
Findings
MEDIUM: Under-constrained signal in Poseidon round function
Severity: Medium | Status: Remediated
One intermediate signal in the Poseidon S-box computation was not fully constrained, meaning a malicious prover could potentially craft a valid proof with an incorrect hash output. The circuit passed all functional tests because the honest prover always assigns the correct value, but the under-constrained signal could be exploited by a dishonest prover to bypass the commitment check.
Fix: Added the missing constraint signal_squared === signal * signal in the S-box component, fully constraining the computation.
LOW Findings (2)
- L-01: Unused template parameter in the Merkle proof verifier circuit. Remediated.
- L-02: Circuit could benefit from using
<==instead of separate<--and===in three locations where the assignment is a simple copy. Remediated.
Circuit Correctness Verification
In addition to the manual audit, Zellic performed automated formal verification of the withdrawal circuit constraints using their proprietary tooling. The verification confirmed that:
- The circuit has exactly the expected number of constraints (15,234)
- All signals are fully constrained (no under-determined outputs)
- The public inputs correctly bind the proof to the recipient and fee
- The Merkle proof verification is sound for all tree depths up to 24
Trail of Bits -- Relayer and SDK Audit
Scope: ZKMix relayer server (TypeScript/Node.js), SDK cryptographic operations, proof generation pipeline. Approximately 5,800 lines of TypeScript.
Duration: 3 weeks (March -- April 2025)
Auditors: 2 researchers with expertise in applied cryptography and web security.
Findings
MEDIUM: Relayer does not validate proof structure before submission
Severity: Medium | Status: Remediated
The relayer accepted proof data from clients and submitted it to the on-chain program without validating the structure of the proof points (e.g., checking that curve points are on the BN254 curve). While the on-chain verifier would reject invalid proofs, the relayer would waste SOL on gas fees for transactions guaranteed to fail. This was exploitable as a griefing attack against relayer operators.
Fix: Added client-side proof structure validation in the relayer, including curve point membership checks.
MEDIUM: Timing side-channel in note deserialization
Severity: Medium | Status: Remediated
The parseNote function performed string comparison of the secret and nullifier values in a non-constant-time manner. While exploitation would require an attacker to observe precise timing of deserialization calls (an unlikely scenario in practice), the fix was straightforward and eliminates the theoretical risk.
Fix: Replaced standard string comparison with constant-time comparison using crypto.timingSafeEqual.
LOW Findings (3)
- L-01: SDK did not pin snarkjs dependency version, risking supply-chain attacks. Remediated.
- L-02: Relayer HTTP server did not set security-relevant headers (X-Content-Type-Options, etc.). Remediated.
- L-03: Error messages in the relayer could leak internal state information. Remediated.
Remediation Status
All findings across all three audits have been addressed:
| Severity | Total | Remediated | Acknowledged | Pending |
|---|---|---|---|---|
| Critical | 0 | 0 | 0 | 0 |
| High | 1 | 1 | 0 | 0 |
| Medium | 6 | 6 | 0 | 0 |
| Low | 10 | 9 | 1 | 0 |
| Info | 18 | 14 | 4 | 0 |
The one "acknowledged" low-severity finding (L-03 from OtterSec regarding additional debug logging) was intentionally deferred as a non-security enhancement.
Full Reports
The complete audit reports are available for download:
- OtterSec Solana Programs Audit -- Available at
https://github.com/zkmix/zkmix-protocol/blob/main/audits/ottersec-q1-2025.pdf - Zellic ZK Circuits Audit -- Available at
https://github.com/zkmix/zkmix-protocol/blob/main/audits/zellic-q1-2025.pdf - Trail of Bits Relayer & SDK Audit -- Available at
https://github.com/zkmix/zkmix-protocol/blob/main/audits/trailofbits-q2-2025.pdf
Ongoing Security
Security is an ongoing process, not a one-time event. ZKMix is committed to:
- Regular re-audits after significant code changes
- Continuous monitoring of the deployed programs for anomalous behavior
- Bug bounty program to incentivize responsible disclosure (see Bug Bounty)
- Dependency monitoring using automated tools to detect vulnerabilities in upstream libraries
- Incident response plan with defined procedures for handling security events