All 28 vulnerabilities from the security audit have been addressed.
- All private keys encrypted at rest (Secure Enclave + AES-GCM-256)
- Secure Enclave used for key operations (SecureKeyStorage.swift)
- Multi-peer consensus implemented (consensusThreshold = 5)
- Sapling proofs verified locally (LocalTxProver)
- BIP39 mnemonic backup & restore (24-word seed phrases via FFI)
- No sensitive data in logs (DEBUG_LOGGING flag)
- Network traffic encrypted (P2P + Tor)
- Memory protection for spending keys (SecureData wrapper + Rust zeroing)
- App lock with biometric auth (Face ID / Touch ID)
- Inactivity timeout auto-lock (configurable)
- SQLCipher full database encryption
- Field-level encryption for sensitive data
- Nullifier hashing for privacy
- Transaction type obfuscation
- Secure memo deletion
NEVER:
- Store unencrypted private keys
- Trust a single network peer
- Skip proof verification
- Disable SSL verification
- Use hardcoded credentials
- Log sensitive data in production
ALWAYS:
- Use Secure Enclave for spending key operations
- Require multi-peer consensus for queries
- Verify Sapling proofs locally
- Backup before encryption operations
| Platform | Method | Key Derivation |
|---|---|---|
| iOS Device | Secure Enclave | Hardware EC key |
| iOS Simulator | AES-GCM-256 | HKDF(UDID + salt) |
| macOS | AES-GCM-256 | HKDF(Hardware UUID + salt) |
- Full AES-256 database encryption
- Key derived from device ID + salt via HKDF-SHA256
- Automatic migration for existing databases
- AES-GCM-256 for sensitive fields
- Encrypted: diversifier, rcm, memo, witness
- 12-byte nonce + 16-byte auth tag per field
- Spending keys decrypted ONLY in Rust
zipherx_build_transaction_encrypted()accepts encrypted keysecure_zero()uses volatile writes + compiler fence- Decrypted key zeroed immediately after use
- Consensus threshold: 5 peers must agree
- Fake height detection: reject heights >1000 blocks ahead of cached
- Auto-ban: 7 days for peers reporting fake data
- HeaderStore validation for all chain heights
- VUL-001: Consensus threshold increased to 5
- VUL-002: Encryption mandatory, no plaintext fallback
- VUL-003: Equihash PoW verification enabled
- VUL-004: Multi-input transactions supported
- VUL-005: Passcode required when biometric disabled
- VUL-006: P2P-first chain height (InsightAPI is fallback)
- VUL-007: SQLCipher required, no plaintext DB
- VUL-008: Explicit memory zeroing for keys
- VUL-010: 7-day peer ban duration
- VUL-011: Per-peer rate limiting
- VUL-018: Shared constants file
- VUL-020: Memo validation (512 byte limit)
- VUL-024: Dust output detection (10,000 zatoshis)
- VUL-009: Nullifiers hashed before storage
- VUL-014: Annual key rotation policy
- VUL-015: Transaction type obfuscation
- VUL-016: Secure memo deletion
macOS builds run with Hardened Runtime enabled and full library validation. The entitlement set has been minimized through testing and audit — only genuinely required entitlements remain.
| Entitlement | Value | Justification |
|---|---|---|
com.apple.security.app-sandbox |
false |
Required: Full Node subprocess execution (zclassicd), P2P inbound connections, filesystem access for blockchain data outside app container |
com.apple.security.files.user-selected.read-write |
true |
Required: User-initiated file operations (wallet export, backup) |
com.apple.security.network.client |
true |
Required: Outbound P2P connections to Zclassic network peers |
com.apple.security.network.server |
true |
Required: Inbound P2P connections from Zclassic network peers |
keychain-access-groups |
Team ID scoped | Required: Wallet spending key storage in macOS Keychain |
| Entitlement | Removed In | Evidence |
|---|---|---|
com.apple.security.cs.allow-unsigned-executable-memory |
v4.2.2 | Groth16 zk-SNARK proof generation (heaviest crypto path) verified working without it. Rust FFI static library (libzipherx_ffi.a) contains no JIT or mmap/mprotect calls. |
com.apple.security.cs.disable-library-validation |
v4.2.2 | ZipherXFFI is a statically linked library (.a), not a dynamic framework — library validation does not apply. All embedded dylibs (Debug-only) are signed with the same Team ID. |
com.apple.security.device.audio-input |
v4.2.2 | Voice calls not included in beta release. |
| Entitlement | Value | Notes |
|---|---|---|
keychain-access-groups |
Team ID scoped | Spending key storage |
UIBackgroundModes |
fetch |
Background sync only. audio mode removed in v4.2.2. |
App Sandbox (com.apple.security.app-sandbox: true) is a long-term goal. Current blockers:
- Full Node mode requires
Process()execution of bundledzclassicdbinary - Blockchain data stored at
~/Library/Application Support/ZipherX/(outside app container) - Migration to container paths + security-scoped bookmarks required
Compensating controls while non-sandboxed:
- Hardened Runtime enabled with full library validation
- DYLD injection guard (fail-closed in Release, see MAC-HARDEN-1 below)
- No dynamic plugin loading
- All frameworks statically linked and signed
- Code signed and notarized by Apple
- Inbound P2P connections rate-limited with peer banning
The DMG packaging pipeline should assert that removed entitlements do not reappear:
#!/bin/bash
# Verify entitlements after notarization
ENTITLEMENTS=$(codesign --display --entitlements :- ZipherXMac.app 2>&1)
# These must NOT be present
for BANNED in "allow-unsigned-executable-memory" "disable-library-validation" "device.audio-input"; do
if echo "$ENTITLEMENTS" | grep -q "$BANNED"; then
echo "FAIL: Banned entitlement found: $BANNED"
exit 1
fi
done
echo "PASS: Entitlements are clean"Prevents environment-based dynamic library injection (DYLD_INSERT_LIBRARIES, DYLD_LIBRARY_PATH, DYLD_FRAMEWORK_PATH) — a common macOS malware and red-team technique.
- Release builds: fail closed with user-facing alert and
exit(173)on detection of high-signalDYLD_*variables. Lower-signal fallback variables are silently scrubbed. - Debug builds: all variables silently scrubbed to preserve Xcode/Instruments workflows.
- Placement: file-level static initializer in
ZipherXApp.swift— runs before@main, before SwiftUI init, before Rust FFI loads. - Complements: Hardened Runtime + library validation ON + no
allow-unsigned-executable-memory.
Both allow-unsigned-executable-memory and disable-library-validation removed and verified by exercising the heaviest crypto path (Groth16 proof generation, 3.7s multi-spend transaction) and multi-peer consensus sync with no crashes.
Residual risk: macOS remains non-sandboxed with inbound networking capability. Mitigations: CI entitlement gates, listener hardening (rate limiting + peer bans), sandbox roadmap.
audio background mode and microphone usage description removed from iOS (Info.plist, project.yml). Only fetch background mode remains for periodic sync.
App no longer locks when user backgrounds during mnemonic backup (e.g., switching to Notes/Photos to save seed phrase). Privacy overlay still activates in app switcher. Lock skipped only when isMnemonicBackupPending is true (wallet just created, no balance to protect).
iOS displays a warning banner when the ~2 GB boost file download is running on cellular data. The warning recommends switching to WiFi for faster speeds and to avoid data charges. The download is not blocked — it works on cellular with full resume support (HTTP Range headers, 30s stall timeout, exponential backoff up to 8 retries). If the network switches mid-download, the Rust download engine detects the stall, retries, and resumes from the last byte written.
Four-layer defense against transaction tampering:
| Layer | Attack | Defense |
|---|---|---|
| VUL-UI-002 | Clipboard-swap (malware replaces address after paste) | Address snapshot at confirmation, re-verified before signing |
| VUL-UI-003 | Vanity address (attacker generates similar-looking address) | Segmented address display highlighting the unique middle section + SHA-256 fingerprint |
| VUL-UI-004 | Amount/fee manipulation | TX preview fingerprint (address + amount + fee) snapshotted and re-verified |
| Input validation | Invisible Unicode injection | Non-ASCII characters rejected in address field |
- Full audit:
docs/SECURITY_AUDIT_FULL_2025-12-04.html - Previous audit:
docs/SECURITY_AUDIT_REPORT.html
// Keys wrapped in SecureData for automatic zeroing
class SecureData {
deinit {
// memset_s zeroes memory on deallocation
}
}fn secure_zero(data: &mut [u8]) {
for byte in data.iter_mut() {
unsafe { ptr::write_volatile(byte, 0); }
}
std::sync::atomic::compiler_fence(Ordering::SeqCst);
}